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