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