898e4dd02705ea831fc0750bfa14f527fb0c2464
[debian/jabref.git] / src / java / net / sf / jabref / EntryEditor.java
1 /*
2  * Copyright (C) 2003 Morten O. Alver, Nizar N. Batada
3  *
4  * All programs in this directory and subdirectories are published under the GNU
5  * General Public License as described below.
6  *
7  * This program is free software; you can redistribute it and/or modify it under
8  * the terms of the GNU General Public License as published by the Free Software
9  * Foundation; either version 2 of the License, or (at your option) any later
10  * version.
11  *
12  * This program is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14  * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
15  * details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19  * Place, Suite 330, Boston, MA 02111-1307 USA
20  *
21  * Further information about the GNU GPL is available at:
22  * http://www.gnu.org/copyleft/gpl.ja.html
23  *
24  */
25 package net.sf.jabref;
26
27 import java.awt.*;
28 import java.awt.datatransfer.StringSelection;
29 import java.awt.dnd.*;
30 import java.awt.event.*;
31 import java.beans.*;
32 import java.io.*;
33 import java.util.*;
34 import java.util.List;
35 import java.util.logging.Logger;
36
37 import javax.swing.*;
38 import javax.swing.event.*;
39 import javax.swing.text.JTextComponent;
40
41 import net.sf.jabref.export.LatexFieldFormatter;
42 import net.sf.jabref.groups.*;
43 import net.sf.jabref.imports.BibtexParser;
44 import net.sf.jabref.labelPattern.LabelPatternUtil;
45 import net.sf.jabref.undo.*;
46 import net.sf.jabref.external.ExternalFilePanel;
47 import net.sf.jabref.journals.JournalAbbreviations;
48 import net.sf.jabref.gui.date.*;
49 import net.sf.jabref.gui.AutoCompleter;
50 import com.jgoodies.forms.builder.DefaultFormBuilder;
51 import com.jgoodies.forms.layout.FormLayout;
52
53 /**
54  * GUI component that allows editing of the fields of a BibtexEntry. EntryEditor
55  * also registers itself as a VetoableChangeListener, receiving events whenever
56  * a field of the entry changes, enabling the text fields to update themselves
57  * if the change is made from somewhere else.
58  */
59 public class EntryEditor extends JPanel implements VetoableChangeListener {
60
61     // A reference to the entry this object works on.
62     private BibtexEntry entry;
63
64     BibtexEntryType type;
65
66     // The action concerned with closing the window.
67     CloseAction closeAction;
68
69     // The action that deletes the current entry, and closes the editor.
70     DeleteAction deleteAction = new DeleteAction();
71
72     // The action concerned with copying the BibTeX key to the clipboard.
73     CopyKeyAction copyKeyAction;
74
75     // The action concerned with copying the BibTeX key to the clipboard.
76     AbstractAction nextEntryAction = new NextEntryAction();
77
78     // Actions for switching to next/previous entry.
79     AbstractAction prevEntryAction = new PrevEntryAction();
80
81     // The action concerned with storing a field value.
82     public StoreFieldAction storeFieldAction;
83
84     // The actions concerned with switching the panels.
85     SwitchLeftAction switchLeftAction = new SwitchLeftAction();
86
87     SwitchRightAction switchRightAction = new SwitchRightAction();
88
89     // The action which generates a bibtexkey for this entry.
90     public GenerateKeyAction generateKeyAction;
91
92     SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction();
93
94     JPanel mainPanel = new JPanel();
95
96     JPanel srcPanel = new JPanel();
97
98     EntryEditorTab genPan, optPan, reqPan, absPan;
99
100     JTextField bibtexKey;
101
102     FieldTextField tf;
103
104     JTextArea source;
105
106     JToolBar tlb;
107
108     JTabbedPane tabbed = new JTabbedPane(); // JTabbedPane.RIGHT);
109
110     JLabel lab;
111
112     TypeLabel typeLabel;
113
114     JabRefFrame frame;
115
116     BasePanel panel;
117
118     EntryEditor ths = this;
119
120     HashSet contentSelectors = new HashSet();
121
122     Logger logger = Logger.getLogger(EntryEditor.class.getName());
123
124     boolean updateSource = true; // This can be set to false to stop the
125                                     // source
126
127     List tabs = new ArrayList();
128
129     // text area from gettin updated. This is used in cases where the source
130     // couldn't be parsed, and the user is given the option to edit it.
131     boolean lastSourceAccepted = true; // This indicates whether the last
132
133     // attempt
134     // at parsing the source was successful. It is used to determine whether the
135     // dialog should close; it should stay open if the user received an error
136     // message about the source, whatever he or she chose to do about it.
137     String lastSourceStringAccepted = null; // This is used to prevent double
138
139     // fields.
140     // These values can be used to calculate the preferred height for the form.
141     // reqW starts at 1 because it needs room for the bibtex key field.
142     private int sourceIndex = -1; // The index the source panel has in tabbed.
143
144     JabRefPreferences prefs;
145
146     HelpAction helpAction;
147
148     UndoAction undoAction = new UndoAction();
149
150     RedoAction redoAction = new RedoAction();
151
152     TabListener tabListener = new TabListener();
153
154     public EntryEditor(JabRefFrame frame_, BasePanel panel_, BibtexEntry entry_) {
155
156         frame = frame_;
157         panel = panel_;
158         entry = entry_;
159         prefs = Globals.prefs;
160         type = entry.getType();
161
162         entry.addPropertyChangeListener(this);
163
164         helpAction = new HelpAction(frame.helpDiag, GUIGlobals.entryEditorHelp, "Help");
165         closeAction = new CloseAction();
166         copyKeyAction = new CopyKeyAction();
167         generateKeyAction = new GenerateKeyAction(frame);
168         storeFieldAction = new StoreFieldAction();
169
170         BorderLayout bl = new BorderLayout();
171         setLayout(bl);
172         setupToolBar();
173         setupFieldPanels();
174         setupSourcePanel();
175         add(tabbed, BorderLayout.CENTER);
176         tabbed.addChangeListener(tabListener);
177         if (prefs.getBoolean("showSource") && prefs.getBoolean("defaultShowSource"))
178             tabbed.setSelectedIndex(sourceIndex);
179
180         updateAllFields();
181     }
182
183     private void setupFieldPanels() {
184         tabbed.removeAll();
185         tabs.clear();
186         String[] fields = entry.getRequiredFields();
187
188         List fieldList = null;
189         if (fields != null)
190             fieldList = java.util.Arrays.asList(fields);
191         reqPan = new EntryEditorTab(frame, panel, fieldList, this, true, Globals.lang("Required fields"));
192         tabbed.addTab(Globals.lang("Required fields"), GUIGlobals.getImage("required"), reqPan
193             .getPane(), Globals.lang("Show required fields"));
194         tabs.add(reqPan);
195
196         if ((entry.getOptionalFields() != null) && (entry.getOptionalFields().length >= 1)) {
197             optPan = new EntryEditorTab(frame, panel, java.util.Arrays.asList(entry.getOptionalFields()), this,
198                 false, Globals.lang("Optional fields"));
199             tabbed.addTab(Globals.lang("Optional fields"), GUIGlobals.getImage("optional"), optPan
200                 .getPane(), Globals.lang("Show optional fields"));
201             tabs.add(optPan);
202         }
203
204         EntryEditorTabList tabList = Globals.prefs.getEntryEditorTabList();
205         for (int i = 0; i < tabList.getTabCount(); i++) {
206             EntryEditorTab newTab = new EntryEditorTab(frame, panel, tabList.getTabFields(i), this, false,
207                 tabList.getTabName(i));
208             tabbed.addTab(tabList.getTabName(i), GUIGlobals.getImage("general"), newTab.getPane());
209             tabs.add(newTab);
210         }
211
212         srcPanel.setName(Globals.lang("BibTeX source"));
213         if (Globals.prefs.getBoolean("showSource")) {
214             tabbed.addTab(Globals.lang("BibTeX source"), GUIGlobals.getImage("source"), srcPanel,
215                 Globals.lang("Show/edit BibTeX source"));
216             tabs.add(srcPanel);
217         }
218         sourceIndex = tabs.size() - 1; // Set the sourceIndex variable.
219         srcPanel.setFocusCycleRoot(true);
220     }
221
222     public BibtexEntryType getType() {
223         return type;
224     }
225
226     public BibtexEntry getEntry() {
227         return entry;
228     }
229     
230     public BibtexDatabase getDatabase(){
231         return panel.getDatabase();
232     }
233
234     private void setupToolBar() {
235         tlb = new JToolBar(JToolBar.VERTICAL);
236
237         // tlb.setMargin(new Insets(2,2,2,2));
238         tlb.setMargin(new Insets(0, 0, 0, 2));
239
240         // The toolbar carries all the key bindings that are valid for the whole
241         // window.
242         // tlb.setBackground(GUIGlobals.lightGray);//Color.white);
243         ActionMap am = tlb.getActionMap();
244         InputMap im = tlb.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
245
246         im.put(prefs.getKey("Close entry editor"), "close");
247         am.put("close", closeAction);
248         im.put(prefs.getKey("Entry editor, store field"), "store");
249         am.put("store", storeFieldAction);
250         im.put(prefs.getKey("Autogenerate BibTeX keys"), "generateKey");
251         am.put("generateKey", generateKeyAction);
252         /*
253          * im.put(prefs.getKey("Entry editor, previous panel"), "left");
254          * im.put(prefs.getKey("Entry editor, previous panel 2"), "left");
255          * am.put("left", switchLeftAction); im.put(prefs.getKey("Entry editor,
256          * next panel"), "right"); im.put(prefs.getKey("Entry editor, next panel
257          * 2"), "right"); am.put("right", switchRightAction);
258          */
259         im.put(prefs.getKey("Entry editor, previous entry"), "prev");
260         am.put("prev", prevEntryAction);
261         im.put(prefs.getKey("Entry editor, next entry"), "next");
262         am.put("next", nextEntryAction);
263         im.put(prefs.getKey("Undo"), "undo");
264         am.put("undo", undoAction);
265         im.put(prefs.getKey("Redo"), "redo");
266         am.put("redo", redoAction);
267         im.put(prefs.getKey("Help"), "help");
268         am.put("help", helpAction);
269
270         tlb.setFloatable(false);
271         tlb.add(closeAction);
272
273         setLabel();
274         tlb.add(typeLabel);
275
276         // tlb.addSeparator();
277         // tlb.add(copyKeyAction);
278         tlb.addSeparator();
279         tlb.add(generateKeyAction);
280         tlb.addSeparator();
281
282         // tlb.add(undoAction);
283         // tlb.add(redoAction);
284         tlb.add(deleteAction);
285         tlb.add(prevEntryAction);
286
287         tlb.add(nextEntryAction);
288         tlb.addSeparator();
289         tlb.add(helpAction);
290
291         Component[] comps = tlb.getComponents();
292
293         for (int i = 0; i < comps.length; i++)
294             ((JComponent) comps[i]).setOpaque(false);
295
296         add(tlb, BorderLayout.WEST);
297     }
298
299     private void setLabel() {
300         typeLabel = new TypeLabel(entry.getType().getName());
301     }
302
303     /**
304      * Rebuild the field tabs. This is called e.g. when a new content selector
305      * has been added.
306      */
307     public void rebuildPanels() {
308         // Remove change listener, because the rebuilding causes meaningless
309         // events and trouble:
310         tabbed.removeChangeListener(tabListener);
311         
312         setupFieldPanels();
313         // Add the change listener again:
314         tabbed.addChangeListener(tabListener);
315         revalidate();
316         repaint();
317     }
318
319     /**
320      * getExtra checks the field name against BibtexFields.getFieldExtras(name).
321      * If the name has an entry, the proper component to be shown is created and
322      * returned. Otherwise, null is returned. In addition, e.g. listeners can be
323      * added to the field editor, even if no component is returned.
324      * 
325      * @param string
326      *            Field name
327      * @return Component to show, or null if none.
328      */
329     public JComponent getExtra(String string, final FieldEditor ed) {
330
331         // fieldName and parameter string identically ????
332         final String fieldName = ed.getFieldName();
333
334         String s = BibtexFields.getFieldExtras(string);
335
336         // timestamp or a other field with datepicker command
337         if ((fieldName.equals(Globals.prefs.get("timeStampField")))
338             || ((s != null) && s.equals("datepicker"))) {
339             // double click AND datefield => insert the current date (today)
340             ((JTextArea) ed).addMouseListener(new MouseAdapter() {
341                 public void mouseClicked(MouseEvent e) {
342                     if (e.getClickCount() == 2) // double click
343                     {
344                         String date = Util.easyDateFormat();
345                         ed.setText(date);
346                     }
347                 }
348             });
349
350             // insert a datepicker, if the extras field contains this command
351             if ((s != null) && s.equals("datepicker")) {
352                 DatePickerButton datePicker = new DatePickerButton(ed);
353                 return datePicker.getDatePicker();
354             }
355         }
356
357         if ((s != null) && s.equals("external")) {
358
359             // Add external viewer listener for "pdf" and "url" fields.
360             ((JComponent) ed).addMouseListener(new ExternalViewerListener());
361
362             return null;
363         } else if ((s != null) && s.equals("journalNames")) {
364             // Add controls for switching between abbreviated and full journal
365             // names.
366             // If this field also has a FieldContentSelector, we need to combine
367             // these.
368             JPanel controls = new JPanel();
369             controls.setLayout(new BorderLayout());
370             if (panel.metaData.getData(Globals.SELECTOR_META_PREFIX + ed.getFieldName()) != null) {
371                 FieldContentSelector ws = new FieldContentSelector(frame, panel, frame, ed,
372                     panel.metaData, storeFieldAction, false, ", ");
373                 contentSelectors.add(ws);
374                 controls.add(ws, BorderLayout.NORTH);
375             }
376             controls.add(JournalAbbreviations.getNameSwitcher(this, ed, panel.undoManager),
377                 BorderLayout.SOUTH);
378             return controls;
379         } else if (panel.metaData.getData(Globals.SELECTOR_META_PREFIX + ed.getFieldName()) != null) {
380             FieldContentSelector ws = new FieldContentSelector(frame, panel, frame, ed,
381                 panel.metaData, storeFieldAction, false,
382                 (ed.getFieldName().equals("author") ? " and " : ", "));
383             contentSelectors.add(ws);
384
385             return ws;
386         } else if ((s != null) && s.equals("browse")) {
387             JButton but = new JButton(Globals.lang("Browse"));
388             ((JComponent) ed).addMouseListener(new ExternalViewerListener());
389
390             // but.setBackground(GUIGlobals.lightGray);
391             but.addActionListener(new ActionListener() {
392                 public void actionPerformed(ActionEvent e) {
393                     String dir = ed.getText();
394
395                     if (dir.equals(""))
396                         dir = prefs.get(fieldName + Globals.FILETYPE_PREFS_EXT, "");
397
398                     String chosenFile = Globals.getNewFile(frame, new File(dir), "." + fieldName,
399                         JFileChooser.OPEN_DIALOG, false);
400
401                     if (chosenFile != null) {
402                         File newFile = new File(chosenFile); // chooser.getSelectedFile();
403                         ed.setText(newFile.getPath());
404                         prefs.put(fieldName + Globals.FILETYPE_PREFS_EXT, newFile.getPath());
405                         updateField(ed);
406                     }
407                 }
408             });
409
410             return but;
411             // } else if ((s != null) && s.equals("browsePdf")) {
412         } else if ((s != null) && (s.equals("browseDoc") || s.equals("browseDocZip"))) {
413
414             final String ext = "." + fieldName.toLowerCase();
415             final OpenFileFilter off;
416             if (s.equals("browseDocZip"))
417                 off = new OpenFileFilter(new String[] { ext, ext + ".gz", ext + ".bz2" });
418             else
419                 off = new OpenFileFilter(new String[] { ext });
420
421             ExternalFilePanel pan = new ExternalFilePanel(frame, panel.metaData(), this, fieldName,
422                 off, ed);
423             return pan;
424         }
425         /*
426          * else if ((s != null) && s.equals("browsePs")) { ExternalFilePanel pan =
427          * new ExternalFilePanel(frame, this, "ps", off, ed); return pan; }
428          */
429         else if ((s != null) && s.equals("url")) {
430             ((JComponent) ed).setDropTarget(new DropTarget((Component) ed,
431                 DnDConstants.ACTION_NONE, new SimpleUrlDragDrop(ed, storeFieldAction)));
432
433             return null;
434         }
435
436         else if ((s != null) && (s.equals("setOwner"))) {
437             JButton button = new JButton(Globals.lang("Auto"));
438             button.addActionListener(new ActionListener() {
439                 public void actionPerformed(ActionEvent actionEvent) {
440                     ed.setText(Globals.prefs.get("defaultOwner"));
441                     storeFieldAction.actionPerformed(new ActionEvent(ed, 0, ""));
442                 }
443             });
444             return button;
445         }
446         else
447             return null;
448     }
449
450     private void setupSourcePanel() {
451         source = new JTextArea();/* {
452             private boolean antialias = Globals.prefs.getBoolean("antialias");
453
454             public void paint(Graphics g) {
455                 Graphics2D g2 = (Graphics2D) g;
456                 if (antialias)
457                     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
458                         RenderingHints.VALUE_ANTIALIAS_ON);
459                 super.paint(g2);
460             }
461         };*/
462
463         //DefaultFormBuilder builder = new DefaultFormBuilder
464         //        (srcPanel, new FormLayout( "fill:pref:grow", "fill:pref:grow"));
465         source.setEditable(true); // prefs.getBoolean("enableSourceEditing"));
466         source.setLineWrap(true);
467         source.setTabSize(GUIGlobals.INDENT);
468         source.addFocusListener(new FieldEditorFocusListener());
469         // Add the global focus listener, so a menu item can see if this field
470         // was focused when
471         // an action was called.
472         source.addFocusListener(Globals.focusListener);
473         source.setFont(new Font("Monospaced", Font.PLAIN, Globals.prefs.getInt("fontSize")));
474         setupJTextComponent(source);
475         updateSource();
476
477         JScrollPane sp = new JScrollPane(source, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
478             JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
479         //builder.append(sp);
480         
481         srcPanel.setLayout(new BorderLayout());
482         srcPanel.add(sp, BorderLayout.CENTER);
483
484     }
485
486     public void updateSource() {
487         if (updateSource) {
488             StringWriter sw = new StringWriter(200);
489
490             try {
491                 entry.write(sw, new net.sf.jabref.export.LatexFieldFormatter(), false);
492
493                 String srcString = sw.getBuffer().toString();
494                 source.setText(srcString);
495                 lastSourceStringAccepted = srcString;
496             } catch (IOException ex) {
497                 source.setText(ex.getMessage() + "\n\n" + 
498                                         Globals.lang("Correct the entry, and "
499                     + "reopen editor to display/edit source."));
500                 source.setEditable(false);
501             }
502
503
504         }
505     }
506
507     /**
508      * NOTE: This method is only used for the source panel, not for the
509      * other tabs. Look at EntryEditorTab for the setup of text components
510      * in the other tabs.
511      */
512     public void setupJTextComponent(JTextComponent ta) {
513
514
515         // Set up key bindings and focus listener for the FieldEditor.
516         InputMap im = ta.getInputMap(JComponent.WHEN_FOCUSED);
517         ActionMap am = ta.getActionMap();
518
519         // im.put(KeyStroke.getKeyStroke(GUIGlobals.closeKey), "close");
520         // am.put("close", closeAction);
521         im.put(prefs.getKey("Entry editor, store field"), "store");
522         am.put("store", storeFieldAction);
523
524         im.put(prefs.getKey("Entry editor, next panel"), "right");
525         im.put(prefs.getKey("Entry editor, next panel 2"), "right");
526         am.put("right", switchRightAction);
527
528         im.put(prefs.getKey("Entry editor, previous panel"), "left");
529         im.put(prefs.getKey("Entry editor, previous panel 2"), "left");
530         am.put("left", switchLeftAction);
531
532         im.put(prefs.getKey("Help"), "help");
533         am.put("help", helpAction);
534         im.put(prefs.getKey("Save database"), "save");
535         am.put("save", saveDatabaseAction);
536
537         im.put(Globals.prefs.getKey("Next tab"), "nexttab");
538         am.put("nexttab", frame.nextTab);
539         im.put(Globals.prefs.getKey("Previous tab"), "prevtab");
540         am.put("prevtab", frame.prevTab);
541         try {
542             HashSet keys = new HashSet(ta
543                 .getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
544             keys.clear();
545             keys.add(AWTKeyStroke.getAWTKeyStroke("pressed TAB"));
546             ta.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keys);
547             keys = new HashSet(ta
548                 .getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
549             keys.clear();
550             keys.add(KeyStroke.getKeyStroke("shift pressed TAB"));
551             ta.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, keys);
552         } catch (Throwable t) {
553             System.err.println(t);
554         }
555
556         ta.addFocusListener(new FieldListener());
557     }
558
559     public void requestFocus() {
560         activateVisible();
561     }
562
563     private void activateVisible() {
564         Object activeTab = tabs.get(tabbed.getSelectedIndex());
565
566         if (activeTab instanceof EntryEditorTab)
567             ((EntryEditorTab) activeTab).activate();
568         else
569             new FocusRequester(source);
570         // ((JComponent)activeTab).requestFocus();
571     }
572
573     /**
574      * Reports the enabled status of the editor, as set by setEnabled()
575      */
576     public boolean isEnabled() {
577         return source.isEnabled();
578     }
579
580     /**
581      * Sets the enabled status of all text fields of the entry editor.
582      */
583     public void setEnabled(boolean enabled) {
584         for (Iterator i = tabs.iterator(); i.hasNext();) {
585             Object o = i.next();
586             if (o instanceof EntryEditorTab) {
587                 ((EntryEditorTab) o).setEnabled(enabled);
588             }
589         }
590         source.setEnabled(enabled);
591
592     }
593
594     /**
595      * Centers the given row, and highlights it.
596      * 
597      * @param row
598      *            an <code>int</code> value
599      */
600     private void scrollTo(int row) {
601         panel.mainTable.setRowSelectionInterval(row, row);
602         panel.mainTable.ensureVisible(row);
603     }
604
605     /**
606      * Makes sure the current edit is stored.
607      */
608     public void storeCurrentEdit() {
609         Component comp = Globals.focusListener.getFocused();
610         if ((comp instanceof FieldEditor) && this.isAncestorOf(comp)) {
611             storeFieldAction.actionPerformed(new ActionEvent(comp, 0, ""));
612         }
613     }
614
615     /**
616      * Returns the index of the active (visible) panel.
617      * 
618      * @return an <code>int</code> value
619      */
620     public int getVisiblePanel() {
621         return tabbed.getSelectedIndex();
622     }
623
624     /** Returns the name of the currently selected component. */
625     public String getVisiblePanelName() {
626         return tabbed.getSelectedComponent().getName();
627     }
628
629     /**
630      * Sets the panel with the given index visible.
631      * 
632      * @param i
633      *            an <code>int</code> value
634      */
635     public void setVisiblePanel(int i) {
636         tabbed.setSelectedIndex(Math.min(i, tabbed.getTabCount() - 1));
637     }
638
639     public void setVisiblePanel(String name) {
640         for (int i = 0; i < tabbed.getTabCount(); ++i) {
641             if (name.equals(tabbed.getComponent(i).getName())) {
642                 tabbed.setSelectedIndex(i);
643                 return;
644             }
645         }
646         if (tabbed.getTabCount() > 0)
647             tabbed.setSelectedIndex(0);
648     }
649
650     /**
651      * Updates this editor to show the given entry, regardless of type
652      * correspondence.
653      * 
654      * @param be
655      *            a <code>BibtexEntry</code> value
656      */
657     public synchronized void switchTo(BibtexEntry be) {
658         if (entry == be)
659             return;
660
661         // Util.pr("EntryEditor.switchTo(BibtexEntry): "+entry.getCiteKey());
662         // Util.pr("::EntryEditor.switchTo(BibtexEntry): "+this.type.getName());
663         storeCurrentEdit();
664
665         // Remove this instance as property listener for the entry:
666         entry.removePropertyChangeListener(this);
667
668         // Register as property listener for the new entry:
669         be.addPropertyChangeListener(this);
670
671         entry = be;
672
673         updateAllFields();
674         validateAllFields();
675         updateSource();
676         panel.showing = be;
677
678     }
679
680     /**
681      * Returns false if the contents of the source panel has not been validated,
682      * true othervise.
683      */
684     public boolean lastSourceAccepted() {
685         if (tabbed.getSelectedComponent() == srcPanel)
686             storeSource(false);
687
688         return lastSourceAccepted;
689     }
690
691     /*
692      * public boolean storeSourceIfNeeded() { if (tabbed.getSelectedIndex() ==
693      * sourceIndex) return storeSource(); else return true; }
694      */
695     public boolean storeSource(boolean showError) {
696         // Store edited bibtex code.
697         BibtexParser bp = new BibtexParser(new java.io.StringReader(source.getText()));
698
699         try {
700             BibtexDatabase db = bp.parse().getDatabase();
701
702             if (db.getEntryCount() > 1)
703                 throw new Exception("More than one entry found.");
704
705             if (db.getEntryCount() < 1)
706                 throw new Exception("No entries found.");
707
708             NamedCompound compound = new NamedCompound(Globals.lang("source edit"));
709             BibtexEntry nu = db.getEntryById((String) db.getKeySet().iterator().next());
710             String id = entry.getId();
711             String
712             // oldKey = entry.getCiteKey(),
713             newKey = nu.getCiteKey();
714             boolean anyChanged = false;
715             boolean duplicateWarning = false;
716             boolean emptyWarning = newKey == null || newKey.equals("");
717
718             if (panel.database.setCiteKeyForEntry(id, newKey)) {
719                 duplicateWarning = true;
720
721                 // First, remove fields that the user have removed.
722             }
723
724             Object[] fields = entry.getAllFields();
725
726             for (int i = 0; i < fields.length; i++) {
727                 if (BibtexFields.isDisplayableField(fields[i].toString())) {
728                     if (nu.getField(fields[i].toString()) == null) {
729                         compound.addEdit(new UndoableFieldChange(entry, fields[i].toString(), entry
730                             .getField(fields[i].toString()), (Object) null));
731                         entry.clearField(fields[i].toString());
732                         anyChanged = true;
733                     }
734                 }
735             }
736
737             // Then set all fields that have been set by the user.
738             fields = nu.getAllFields();
739
740             for (int i = 0; i < fields.length; i++) {
741                 if (entry.getField(fields[i].toString()) != nu.getField(fields[i].toString())) {
742                     String toSet = (String) nu.getField(fields[i].toString());
743
744                     // Test if the field is legally set.
745                     (new LatexFieldFormatter()).format(toSet, fields[i].toString());
746
747                     compound.addEdit(new UndoableFieldChange(entry, fields[i].toString(), entry
748                         .getField(fields[i].toString()), toSet));
749                     entry.setField(fields[i].toString(), toSet);
750                     anyChanged = true;
751                 }
752             }
753
754             compound.end();
755
756             if (!anyChanged)
757                 return true;
758
759             panel.undoManager.addEdit(compound);
760
761             /*
762              * if (((oldKey == null) && (newKey != null)) || ((oldKey != null) &&
763              * (newKey == null)) || ((oldKey != null) && (newKey != null) &&
764              * !oldKey.equals(newKey))) { }
765              */
766             if (duplicateWarning) {
767                 warnDuplicateBibtexkey();
768             } else if (emptyWarning && showError) {
769                 warnEmptyBibtexkey();
770             } else {
771                 panel.output(Globals.lang("Stored entry") + ".");
772             }
773
774             lastSourceStringAccepted = source.getText();
775             updateAllFields();
776             lastSourceAccepted = true;
777             updateSource = true;
778
779             // TODO: does updating work properly after source stored?
780             // panel.tableModel.remap();
781             // panel.entryTable.repaint();
782             // panel.refreshTable();
783             panel.markBaseChanged();
784
785             return true;
786         } catch (Throwable ex) {
787             // ex.printStackTrace();
788             // The source couldn't be parsed, so the user is given an
789             // error message, and the choice to keep or revert the contents
790             // of the source text field.
791             updateSource = false;
792             lastSourceAccepted = false;
793             tabbed.setSelectedComponent(srcPanel);
794
795             if (showError) {
796                 Object[] options = { Globals.lang("Edit"),
797                     Globals.lang("Revert to original source") };
798
799                 int answer = JOptionPane.showOptionDialog(frame, Globals.lang("Error: ") + ex.getMessage(),
800                     Globals.lang("Problem with parsing entry"), JOptionPane.YES_NO_OPTION,
801                     JOptionPane.ERROR_MESSAGE, null, options, options[0]);
802
803                 if (answer != 0) {
804                     updateSource = true;
805                     updateSource();
806                 }
807             }
808
809             return false;
810         }
811     }
812
813     public void setField(String fieldName, String newFieldData) {
814
815         for (Iterator i = tabs.iterator(); i.hasNext();) {
816             Object o = i.next();
817             if (o instanceof EntryEditorTab) {
818                 ((EntryEditorTab) o).updateField(fieldName, newFieldData);
819             }
820         }
821
822     }
823
824     /**
825      * Sets all the text areas according to the shown entry.
826      */
827     public void updateAllFields() {
828         for (Iterator i = tabs.iterator(); i.hasNext();) {
829             Object o = i.next();
830             if (o instanceof EntryEditorTab) {
831                 ((EntryEditorTab) o).setEntry(entry);
832             }
833         }
834     }
835
836     /**
837      * Removes the "invalid field" color from all text areas.
838      */
839     public void validateAllFields() {
840         for (Iterator i = tabs.iterator(); i.hasNext();) {
841             Object o = i.next();
842             if (o instanceof EntryEditorTab) {
843                 ((EntryEditorTab) o).validateAllFields();
844             }
845         }
846     }
847
848     public void updateAllContentSelectors() {
849         if (contentSelectors.size() > 0) {
850             for (Iterator i = contentSelectors.iterator(); i.hasNext();)
851                 ((FieldContentSelector) i.next()).rebuildComboBox();
852         }
853     }
854
855     /*
856      * Update the JTextArea when a field has changed.
857      * 
858      * (non-Javadoc)
859      * 
860      * @see java.beans.VetoableChangeListener#vetoableChange(java.beans.PropertyChangeEvent)
861      */
862     public void vetoableChange(PropertyChangeEvent e) {
863         String newValue = ((e.getNewValue() != null) ? e.getNewValue().toString() : "");
864         setField(e.getPropertyName(), newValue);
865     }
866
867     public void updateField(final Object source) {
868         storeFieldAction.actionPerformed(new ActionEvent(source, 0, ""));
869     }
870
871     private class TypeLabel extends JPanel {
872         private String label;
873
874         public TypeLabel(String type) {
875             label = type;
876             addMouseListener(new MouseAdapter() {
877                 public void mouseClicked(MouseEvent e) {
878                     boolean ctrlClick = prefs.getBoolean("ctrlClick");
879
880                     if ((e.getButton() == MouseEvent.BUTTON3)
881                         || (ctrlClick && (e.getButton() == MouseEvent.BUTTON1) && e.isControlDown())) {
882                         JPopupMenu typeMenu = new JPopupMenu();
883
884                         // typeMenu.addSeparator();
885                         for (Iterator i = BibtexEntryType.ALL_TYPES.keySet().iterator(); i
886                             .hasNext();)
887                             typeMenu.add(new ChangeTypeAction(BibtexEntryType.getType((String) i
888                                 .next()), panel));
889
890                         typeMenu.show(ths, e.getX(), e.getY());
891                     }
892                 }
893             });
894         }
895
896         public void paint(Graphics g) {
897             Graphics2D g2 = (Graphics2D) g;
898             g2.setColor(GUIGlobals.validFieldColor);
899             g2.setFont(GUIGlobals.typeNameFont);
900
901             FontMetrics fm = g2.getFontMetrics();
902             int width = fm.stringWidth(label);
903             g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
904             g2.rotate(-Math.PI / 2, 0, 0);
905             g2.drawString(label, -width - 7, 28);
906         }
907     }
908
909     class FieldListener extends FocusAdapter {
910         /*
911          * Focus listener that fires the storeFieldAction when a FieldTextArea
912          * loses focus.
913          */
914         public void focusGained(FocusEvent e) {
915         }
916
917         public void focusLost(FocusEvent e) {
918             // Util.pr("Lost focus "+e.getSource().toString().substring(0,30));
919             if (!e.isTemporary())
920                 updateField(e.getSource());
921         }
922     }
923
924     class TabListener implements ChangeListener {
925         public void stateChanged(ChangeEvent e) {
926
927             SwingUtilities.invokeLater(new Runnable() {
928                 public void run() {
929                     activateVisible();
930                 }
931             });
932
933             // After the initial event train has finished, we tell the editor
934             // tab to update all
935             // its fields. This makes sure they are updated even if the tab we
936             // just left contained one
937             // or more of the same fields as this one:
938             SwingUtilities.invokeLater(new Runnable() {
939                 public void run() {
940                     Object activeTab = tabs.get(tabbed.getSelectedIndex());
941                     if (activeTab instanceof EntryEditorTab)
942                         ((EntryEditorTab) activeTab).updateAll();
943                 }
944             });
945
946         }
947     }
948
949     class DeleteAction extends AbstractAction {
950         public DeleteAction() {
951             super(Globals.lang("Delete"), GUIGlobals.getImage("delete"));
952             putValue(SHORT_DESCRIPTION, Globals.lang("Delete entry"));
953         }
954
955         public void actionPerformed(ActionEvent e) {
956             // Show confirmation dialog if not disabled:
957             boolean goOn = panel.showDeleteConfirmationDialog(1);
958
959             if (!goOn)
960                 return;
961
962             panel.entryEditorClosing(EntryEditor.this);
963             panel.database.removeEntry(entry.getId());
964             panel.markBaseChanged();
965             panel.undoManager.addEdit(new UndoableRemoveEntry(panel.database, entry, panel));
966             panel.output(Globals.lang("Deleted") + " " + Globals.lang("entry"));
967         }
968     }
969
970     class CloseAction extends AbstractAction {
971         public CloseAction() {
972             super(Globals.lang("Close window"), GUIGlobals.getImage("close"));
973             putValue(SHORT_DESCRIPTION, Globals.lang("Close window"));
974         }
975
976         public void actionPerformed(ActionEvent e) {
977             if (tabbed.getSelectedComponent() == srcPanel) {
978                 updateField(source);
979                 if (lastSourceAccepted)
980                     panel.entryEditorClosing(EntryEditor.this);
981             } else
982                 panel.entryEditorClosing(EntryEditor.this);
983         }
984     }
985
986     class CopyKeyAction extends AbstractAction {
987         public CopyKeyAction() {
988             super("Copy BibTeX key to clipboard");
989             putValue(SHORT_DESCRIPTION, "Copy BibTeX key to clipboard (Ctrl-K)");
990             // putValue(MNEMONIC_KEY, GUIGlobals.copyKeyCode);
991         }
992
993         public void actionPerformed(ActionEvent e) {
994             String s = (String) (entry.getField(BibtexFields.KEY_FIELD));
995             StringSelection ss = new StringSelection(s);
996
997             if (s != null)
998                 Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, ss);
999         }
1000     }
1001
1002     public class StoreFieldAction extends AbstractAction {
1003         public StoreFieldAction() {
1004             super("Store field value");
1005             putValue(SHORT_DESCRIPTION, "Store field value");
1006         }
1007
1008         public void actionPerformed(ActionEvent e) {
1009
1010             if (e.getSource() instanceof FieldTextField) {
1011                 // Storage from bibtex key field.
1012                 FieldTextField fe = (FieldTextField) e.getSource();
1013                 String oldValue = entry.getCiteKey();
1014                 String newValue = fe.getText();
1015
1016                 if (newValue.equals(""))
1017                     newValue = null;
1018
1019                 if (((oldValue == null) && (newValue == null))
1020                     || ((oldValue != null) && (newValue != null) && oldValue.equals(newValue)))
1021                     return; // No change.
1022
1023                 // Make sure the key is legal:
1024                 String cleaned = Util.checkLegalKey(newValue);
1025                 if ((cleaned != null) && !cleaned.equals(newValue)) {
1026                     JOptionPane.showMessageDialog(frame, Globals.lang("Invalid BibTeX key"),
1027                         Globals.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
1028                     fe.setBackground(GUIGlobals.invalidFieldBackground);
1029                     return;
1030                 } else {
1031                     fe.setBackground(/*
1032                                          * fe.getTextComponent().hasFocus() ?
1033                                          * GUIGlobals.activeEditor :
1034                                          */
1035                     GUIGlobals.validFieldBackground);
1036                 }
1037
1038                 boolean isDuplicate = panel.database.setCiteKeyForEntry(entry.getId(), newValue);
1039
1040                 if (newValue != null) {
1041                     if (isDuplicate)
1042                         warnDuplicateBibtexkey();
1043                     else
1044                         panel.output(Globals.lang("BibTeX key is unique."));
1045                 } else { // key is null/empty
1046                     warnEmptyBibtexkey();
1047                 }
1048
1049                 // Add an UndoableKeyChange to the baseframe's undoManager.
1050                 panel.undoManager.addEdit(new UndoableKeyChange(panel.database, entry.getId(),
1051                     oldValue, newValue));
1052
1053                 if ((newValue != null) && (newValue.length() > 0))
1054                     // fe.setLabelColor(GUIGlobals.validFieldColor);
1055                     fe.setBackground(GUIGlobals.validFieldBackground);
1056                 else
1057                     // fe.setLabelColor(GUIGlobals.nullFieldColor);
1058                     fe.setBackground(GUIGlobals.validFieldBackground);
1059
1060                 updateSource();
1061                 panel.markBaseChanged();
1062             }
1063             else if (e.getSource() instanceof FieldEditor) {
1064                 String toSet = null;
1065                 FieldEditor fe = (FieldEditor) e.getSource();
1066                 boolean set;
1067                 // Trim the whitespace off this value
1068                 String currentText = fe.getText();
1069                 String trim = currentText.trim();
1070                 if (trim.length() > 0) {
1071                     toSet = trim;
1072                 }
1073
1074                 // We check if the field has changed, since we don't want to
1075                 // mark the base as changed unless we have a real change.
1076                 if (toSet == null) {
1077                     if (entry.getField(fe.getFieldName()) == null)
1078                         set = false;
1079                     else
1080                         set = true;
1081                 } else {
1082                     if ((entry.getField(fe.getFieldName()) != null)
1083                         && toSet.equals(entry.getField(fe.getFieldName()).toString()))
1084                         set = false;
1085                     else
1086                         set = true;
1087                 }
1088
1089                 if (set) {
1090                     try {
1091                         // The following statement attempts to write the
1092                         // new contents into a StringWriter, and this will
1093                         // cause an IOException if the field is not
1094                         // properly formatted. If that happens, the field
1095                         // is not stored and the textarea turns red.
1096                         if (toSet != null)
1097                             (new LatexFieldFormatter()).format(toSet, fe.getFieldName());
1098
1099                         Object oldValue = entry.getField(fe.getFieldName());
1100
1101                         if (toSet != null)
1102                             entry.setField(fe.getFieldName(), toSet);
1103                         else
1104                             entry.clearField(fe.getFieldName());
1105
1106                         if ((toSet != null) && (toSet.length() > 0))
1107                             // fe.setLabelColor(GUIGlobals.validFieldColor);
1108                             fe.setBackground(GUIGlobals.validFieldBackground);
1109                         else
1110                             // fe.setLabelColor(GUIGlobals.nullFieldColor);
1111                             fe.setBackground(GUIGlobals.validFieldBackground);
1112
1113                         // See if we need to update an AutoCompleter instance:
1114                         AutoCompleter aComp = panel.getAutoCompleter(fe.getFieldName());
1115                         if (aComp != null)
1116                             aComp.addAll(toSet);
1117
1118                         // Add an UndoableFieldChange to the baseframe's
1119                         // undoManager.
1120                         panel.undoManager.addEdit(new UndoableFieldChange(entry, fe.getFieldName(),
1121                             oldValue, toSet));
1122                         updateSource();
1123                         panel.markBaseChanged();
1124
1125                         // TODO: is this a safe solution to keep selection on
1126                         // entry?
1127                         SwingUtilities.invokeLater(new Runnable() {
1128                             public void run() {
1129                                 panel.highlightEntry(entry);
1130                             }
1131                         });
1132
1133                     } catch (IllegalArgumentException ex) {
1134                         JOptionPane.showMessageDialog(frame, Globals.lang("Error: ") + ex.getMessage(), Globals
1135                             .lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
1136                         fe.setBackground(GUIGlobals.invalidFieldBackground);
1137                     }
1138                 } else {
1139                     // set == false
1140                     // We set the field and label color.
1141                     fe.setBackground(GUIGlobals.validFieldBackground);
1142                 }
1143             } else if ((source.isEditable())
1144                 && (!source.getText().equals(lastSourceStringAccepted))) {
1145                 boolean accepted = storeSource(true);
1146
1147                 if (accepted) {
1148                 }
1149             }
1150         }
1151     }
1152
1153     class SwitchLeftAction extends AbstractAction {
1154         public SwitchLeftAction() {
1155             super("Switch to the panel to the left");
1156         }
1157
1158         public void actionPerformed(ActionEvent e) {
1159             // System.out.println("switch left");
1160             int i = tabbed.getSelectedIndex();
1161             tabbed.setSelectedIndex(((i > 0) ? (i - 1) : (tabbed.getTabCount() - 1)));
1162
1163             activateVisible();
1164         }
1165     }
1166
1167     class SwitchRightAction extends AbstractAction {
1168         public SwitchRightAction() {
1169             super("Switch to the panel to the right");
1170         }
1171
1172         public void actionPerformed(ActionEvent e) {
1173             // System.out.println("switch right");
1174             int i = tabbed.getSelectedIndex();
1175             tabbed.setSelectedIndex((i < (tabbed.getTabCount() - 1)) ? (i + 1) : 0);
1176             activateVisible();
1177
1178         }
1179     }
1180
1181     class NextEntryAction extends AbstractAction {
1182         public NextEntryAction() {
1183             super(Globals.lang("Next entry"), GUIGlobals.getImage("down"));
1184
1185             putValue(SHORT_DESCRIPTION, Globals.lang("Next entry"));
1186         }
1187
1188         public void actionPerformed(ActionEvent e) {
1189
1190             int thisRow = panel.mainTable.findEntry(entry);
1191             int newRow = -1;
1192
1193             if ((thisRow + 1) < panel.database.getEntryCount())
1194                 newRow = thisRow + 1;
1195             else if (thisRow > 0)
1196                 newRow = 0;
1197             else
1198                 return; // newRow is still -1, so we can assume the database has
1199                         // only one entry.
1200
1201             scrollTo(newRow);
1202             panel.mainTable.setRowSelectionInterval(newRow, newRow);
1203
1204         }
1205     }
1206
1207     class PrevEntryAction extends AbstractAction {
1208         public PrevEntryAction() {
1209             super(Globals.lang("Previous entry"), GUIGlobals.getImage("up"));
1210
1211             putValue(SHORT_DESCRIPTION, Globals.lang("Previous entry"));
1212         }
1213
1214         public void actionPerformed(ActionEvent e) {
1215             int thisRow = panel.mainTable.findEntry(entry);
1216             int newRow = -1;
1217
1218             if ((thisRow - 1) >= 0)
1219                 newRow = thisRow - 1;
1220             else if (thisRow != (panel.database.getEntryCount() - 1))
1221                 newRow = panel.database.getEntryCount() - 1;
1222             else
1223                 return; // newRow is still -1, so we can assume the database has
1224                         // only one entry.
1225             // id = panel.tableModel.getIdForRow(newRow);
1226             // switchTo(id);
1227
1228             scrollTo(newRow);
1229             panel.mainTable.setRowSelectionInterval(newRow, newRow);
1230
1231         }
1232     }
1233
1234     class GenerateKeyAction extends AbstractAction {
1235         JabRefFrame parent;
1236
1237         BibtexEntry selectedEntry;
1238
1239         public GenerateKeyAction(JabRefFrame parentFrame) {
1240             super(Globals.lang("Generate BibTeX key"), GUIGlobals.getImage("makeKey"));
1241             parent = parentFrame;
1242
1243             // selectedEntry = newEntry ;
1244             putValue(SHORT_DESCRIPTION, Globals.lang("Generate BibTeX key"));
1245
1246             // putValue(MNEMONIC_KEY, GUIGlobals.showGenKeyCode);
1247         }
1248
1249         public void actionPerformed(ActionEvent e) {
1250             // 1. get Bitexentry for selected index (already have)
1251             // 2. run the LabelMaker by it
1252             try {
1253                 // this updates the table automatically, on close, but not
1254                 // within the tab
1255                 Object oldValue = entry.getField(BibtexFields.KEY_FIELD);
1256
1257                 // entry = frame.labelMaker.applyRule(entry, panel.database) ;
1258                 LabelPatternUtil.makeLabel(prefs.getKeyPattern(), panel.database, entry);
1259
1260                 // Store undo information:
1261                 panel.undoManager.addEdit(new UndoableKeyChange(panel.database, entry.getId(),
1262                     (String) oldValue, (String) entry.getField(BibtexFields.KEY_FIELD)));
1263
1264                 // here we update the field
1265                 String bibtexKeyData = (String) entry.getField(BibtexFields.KEY_FIELD);
1266
1267                 // set the field named for "bibtexkey"
1268                 setField(BibtexFields.KEY_FIELD, bibtexKeyData);
1269                 updateSource();
1270                 panel.markBaseChanged();
1271             } catch (Throwable t) {
1272                 System.err.println("error setting key: " + t);
1273             }
1274         }
1275     }
1276
1277     class UndoAction extends AbstractAction {
1278         public UndoAction() {
1279             super("Undo", GUIGlobals.getImage("undo"));
1280             putValue(SHORT_DESCRIPTION, "Undo");
1281         }
1282
1283         public void actionPerformed(ActionEvent e) {
1284             try {
1285                 panel.runCommand("undo");
1286             } catch (Throwable ex) {
1287             }
1288         }
1289     }
1290
1291     class RedoAction extends AbstractAction {
1292         public RedoAction() {
1293             super("Undo", GUIGlobals.getImage("redo"));
1294             putValue(SHORT_DESCRIPTION, "Redo");
1295         }
1296
1297         public void actionPerformed(ActionEvent e) {
1298             try {
1299                 panel.runCommand("redo");
1300             } catch (Throwable ex) {
1301             }
1302         }
1303     }
1304
1305     class SaveDatabaseAction extends AbstractAction {
1306         public SaveDatabaseAction() {
1307             super("Save database");
1308         }
1309
1310         public void actionPerformed(ActionEvent e) {
1311             Object activeTab = tabs.get(tabbed.getSelectedIndex());
1312             if (activeTab instanceof EntryEditorTab) {
1313                 // Normal panel.
1314                 EntryEditorTab fp = (EntryEditorTab) activeTab;
1315                 updateField(fp.getActive());
1316             } else
1317                 // Source panel.
1318                 updateField(activeTab);
1319
1320             try {
1321                 panel.runCommand("save");
1322             } catch (Throwable ex) {
1323             }
1324         }
1325     }
1326
1327     class ExternalViewerListener extends MouseAdapter {
1328         public void mouseClicked(MouseEvent evt) {
1329             if (evt.getClickCount() == 2) {
1330                 FieldTextArea tf = (FieldTextArea) evt.getSource();
1331
1332                 if (tf.getText().equals(""))
1333                     return;
1334
1335                 tf.selectAll();
1336
1337                 String link = tf.getText(); // get selected ? String
1338
1339                 // getSelectedText()
1340                 try {
1341                     Util.openExternalViewer(panel.metaData(), link, tf.getFieldName());
1342                 } catch (IOException ex) {
1343                     System.err.println("Error opening file.");
1344                 }
1345             }
1346         }
1347     }
1348
1349     class ChangeTypeAction extends AbstractAction {
1350         BibtexEntryType type;
1351
1352         BasePanel panel;
1353
1354         public ChangeTypeAction(BibtexEntryType type, BasePanel bp) {
1355             super(type.getName());
1356             this.type = type;
1357             panel = bp;
1358         }
1359
1360         public void actionPerformed(ActionEvent evt) {
1361             panel.changeType(entry, type);
1362         }
1363     }
1364
1365     /**
1366      * Scans all groups.
1367      * 
1368      * @return true if the specified entry is contained in any ExplicitGroup,
1369      *         false otherwise.
1370      */
1371     private boolean containedInExplicitGroup(BibtexEntry entry) {
1372         AbstractGroup[] matchingGroups = panel.getGroupSelector().getGroupTreeRoot()
1373             .getMatchingGroups(entry);
1374         for (int i = 0; i < matchingGroups.length; ++i) {
1375             if (matchingGroups[i] instanceof ExplicitGroup)
1376                 return true;
1377         }
1378         return false;
1379     }
1380
1381     private void warnDuplicateBibtexkey() {
1382         panel.output(Globals.lang("Warning") + ": " + Globals.lang("Duplicate BibTeX key."));
1383
1384         if (prefs.getBoolean("dialogWarningForDuplicateKey")) {
1385             // JZTODO lyrics
1386             CheckBoxMessage jcb = new CheckBoxMessage(Globals.lang("Warning") + ": "
1387                 + Globals.lang("Duplicate BibTeX key. Grouping may not work for this entry."),
1388                 Globals.lang("Disable this warning dialog"), false);
1389             JOptionPane.showMessageDialog(frame, jcb, Globals.lang("Warning"),
1390                 JOptionPane.WARNING_MESSAGE);
1391
1392             if (jcb.isSelected())
1393                 prefs.putBoolean("dialogWarningForDuplicateKey", false);
1394         }
1395     }
1396
1397     private void warnEmptyBibtexkey() {
1398         // JZTODO lyrics
1399         panel.output(Globals.lang("Warning") + ": " + Globals.lang("Empty BibTeX key."));
1400
1401         if (prefs.getBoolean("dialogWarningForEmptyKey")) {
1402             // JZTODO lyrics
1403             CheckBoxMessage jcb = new CheckBoxMessage(Globals.lang("Warning") + ": "
1404                 + Globals.lang("Empty BibTeX key. Grouping may not work for this entry."), Globals
1405                 .lang("Disable this warning dialog"), false);
1406             JOptionPane.showMessageDialog(frame, jcb, Globals.lang("Warning"),
1407                 JOptionPane.WARNING_MESSAGE);
1408
1409             if (jcb.isSelected())
1410                 prefs.putBoolean("dialogWarningForEmptyKey", false);
1411         }
1412     }
1413
1414 }