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