ea5d9540369e877b068663598207f347e71e7449
[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.MainTableSelectionListener;
49
50 import java.text.*;
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 GUIGlobals.FIELD_EXTRAS. If the name
302    * has an entry, the proper component to be shown is created and returned.
303    * Otherwise, null is returned. In addition, e.g. listeners can be added to
304    * 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     Object o = GUIGlobals.FIELD_EXTRAS.get(string);
313     final String fieldName = editor.getFieldName();
314
315     //if (o == null)
316     //  return null;
317     String s = (String) o;
318     //addedByMoritz
319       if (fieldName.equals(Globals.prefs.get("timeStampField"))){
320     //if (fieldName.equals("dateadded")){
321         ((JTextArea) ed).addMouseListener(new MouseAdapter(){
322             public void mouseClicked(MouseEvent e){
323                 if(e.getClickCount()==2){
324                     String date = Util.easyDateFormat();
325                     ed.setText(date);
326                     //DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
327                     //ed.setText(df.format(new Date()));
328                 }
329             }
330         });
331     }
332     //END_OF addedByMoritz
333     if ((s != null) && s.equals("external")) {
334
335       // Add external viewer listener for "pdf" and "url" fields.
336       ((JComponent) editor).addMouseListener(new ExternalViewerListener());
337
338       return null;
339     }
340     else if ((s != null) && s.equals("journalNames")) {
341         // Add controls for switching between abbreviated and full journal names.
342         // If this field also has a FieldContentSelector, we need to combine these.
343         JPanel controls = new JPanel();
344         controls.setLayout(new BorderLayout());
345         if (panel.metaData.getData(Globals.SELECTOR_META_PREFIX
346           + editor.getFieldName()) != null) {
347             FieldContentSelector ws = new FieldContentSelector(frame, panel, frame, editor,
348               panel.metaData, storeFieldAction, false);
349             contentSelectors.add(ws);
350             controls.add(ws, BorderLayout.NORTH);
351         }
352         controls.add(JournalAbbreviations.getNameSwitcher(this, editor, panel.undoManager),
353                 BorderLayout.SOUTH);
354         return controls;
355     }
356     else if (panel.metaData.getData(Globals.SELECTOR_META_PREFIX
357           + editor.getFieldName()) != null) {
358       FieldContentSelector ws = new FieldContentSelector(frame, panel, frame, editor,
359               panel.metaData, storeFieldAction, false);
360       contentSelectors.add(ws);
361
362       return ws;
363     } else if ((s != null) && s.equals("browse")) {
364       JButton but = new JButton(Globals.lang("Browse"));
365       ((JComponent) editor).addMouseListener(new ExternalViewerListener());
366
367       //but.setBackground(GUIGlobals.lightGray);
368       but.addActionListener(new ActionListener() {
369           public void actionPerformed(ActionEvent e) {
370             String dir = ed.getText();
371
372             if (dir.equals(""))
373               dir = prefs.get(fieldName + Globals.FILETYPE_PREFS_EXT, "");
374
375             String chosenFile =
376               Globals.getNewFile(frame, prefs, new File(dir), "." + fieldName,
377                 JFileChooser.OPEN_DIALOG, false);
378
379             if (chosenFile != null) {
380               File newFile = new File(chosenFile); //chooser.getSelectedFile();
381               ed.setText(newFile.getPath());
382               prefs.put(fieldName + Globals.FILETYPE_PREFS_EXT, newFile.getPath());
383               updateField(ed);
384             }
385           }
386         });
387
388       return but;
389     //} else if ((s != null) && s.equals("browsePdf")) {
390     } else if ((s != null) && (s.equals("browseDoc") || s.equals("browseDocZip"))) {
391
392         final String ext = "."+fieldName.toLowerCase();
393         final OpenFileFilter off;
394         if (s.equals("browseDocZip"))
395             off = new OpenFileFilter(new String[] { ext, ext+".gz", ext+".bz2" });
396         else
397             off = new OpenFileFilter(new String[] { ext });
398
399         ExternalFilePanel pan = new ExternalFilePanel(frame, this, fieldName, off, ed);
400         return pan;
401     }
402     /*else if ((s != null) && s.equals("browsePs")) {
403         ExternalFilePanel pan = new ExternalFilePanel(frame, this, "ps", off, ed);
404         return pan;
405     }*/
406     else if ((s != null) && s.equals("url")) {
407       ((JComponent) editor).setDropTarget(new DropTarget((Component) editor,
408           DnDConstants.ACTION_NONE, new SimpleUrlDragDrop(editor, storeFieldAction)));
409
410       return null;
411     }
412
413     else
414       return null;
415   }
416
417   private void setupSourcePanel() {
418     source =
419       new JTextArea() {
420           private boolean antialias = Globals.prefs.getBoolean("antialias");
421           public void paint(Graphics g) {
422                 Graphics2D g2 = (Graphics2D)g;
423                 if (antialias)
424                     g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
425                 super.paint(g2);
426           }
427         };
428     con = new GridBagConstraints();
429     con.insets = new Insets(10, 10, 10, 10);
430     con.fill = GridBagConstraints.BOTH;
431     con.gridwidth = GridBagConstraints.REMAINDER;
432     con.gridheight = GridBagConstraints.REMAINDER;
433     con.weightx = 1;
434     con.weighty = 1;
435     srcPanel.setLayout(gbl);
436     source.setEditable(true); //prefs.getBoolean("enableSourceEditing"));
437     source.setLineWrap(true);
438     source.setTabSize(GUIGlobals.INDENT);
439     source.addFocusListener(new FieldEditorFocusListener());
440     // Add the global focus listener, so a menu item can see if this field
441     // was focused when
442     // an action was called.
443     source.addFocusListener(Globals.focusListener);
444     source.setFont(new Font("Monospaced", Font.PLAIN, Globals.prefs.getInt("fontSize")));
445     setupJTextComponent(source);
446     updateSource();
447
448     JScrollPane sp =
449       new JScrollPane(source, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
450         JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
451     gbl.setConstraints(sp, con);
452     srcPanel.add(sp);
453   }
454
455   public void updateSource() {
456     if (updateSource) {
457       StringWriter sw = new StringWriter(200);
458
459       try {
460         entry.write(sw, new net.sf.jabref.export.LatexFieldFormatter(), false);
461
462         String srcString = sw.getBuffer().toString();
463         source.setText(srcString);
464         lastSourceStringAccepted = srcString;
465       } catch (IOException ex) {
466         source.setText("Error: " + ex.getMessage() + "\n\n" + "Correct the entry, and "
467           + "reopen editor to display/edit source.");
468         source.setEditable(false);
469       }
470     }
471   }
472
473   public void setupJTextComponent(JTextComponent ta) {
474
475      /*
476       * NOTE: This method is only used for the source panel, not for the other tabs. Look at
477       * EntryEditorTab for the setup of text components in the other tabs.
478       */
479
480     /*
481     * if ((ta instanceof FieldTextArea) && (prefs.getBoolean("autoComplete"))) {
482     * FieldTextArea fta = (FieldTextArea)ta; Completer comp =
483     * baseFrame.getAutoCompleter(fta.getFieldName()); if (comp != null)
484     * fta.setAutoComplete(comp); }
485     */
486
487     // Set up key bindings and focus listener for the FieldEditor.
488     InputMap im = ta.getInputMap(JComponent.WHEN_FOCUSED);
489     ActionMap am = ta.getActionMap();
490
491     //im.put(KeyStroke.getKeyStroke(GUIGlobals.closeKey), "close");
492     //am.put("close", closeAction);
493     im.put(prefs.getKey("Entry editor, store field"), "store");
494     am.put("store", storeFieldAction);
495
496     im.put(prefs.getKey("Entry editor, next panel"), "right");
497     im.put(prefs.getKey("Entry editor, next panel 2"), "right");
498     am.put("right", switchRightAction);
499
500     im.put(prefs.getKey("Entry editor, previous panel"), "left");
501     im.put(prefs.getKey("Entry editor, previous panel 2"), "left");
502     am.put("left", switchLeftAction);
503
504     im.put(prefs.getKey("Help"), "help");
505     am.put("help", helpAction);
506     im.put(prefs.getKey("Save database"), "save");
507     am.put("save", saveDatabaseAction);
508
509     im.put(Globals.prefs.getKey("Next tab"), "nexttab");
510     am.put("nexttab", frame.nextTab);
511     im.put(Globals.prefs.getKey("Previous tab"), "prevtab");
512     am.put("prevtab", frame.prevTab);
513     try {
514       HashSet keys =
515         new HashSet(ta.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
516       keys.clear();
517       keys.add(AWTKeyStroke.getAWTKeyStroke("pressed TAB"));
518       ta.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keys);
519       keys =
520         new HashSet(ta.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
521       keys.clear();
522       keys.add(KeyStroke.getKeyStroke("shift pressed TAB"));
523       ta.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, keys);
524     } catch (Throwable t) {
525       System.err.println(t);
526     }
527
528     ta.addFocusListener(new FieldListener());
529   }
530
531   public void requestFocus() {
532       activateVisible();
533   }
534
535     private void activateVisible() {
536     Object activeTab = tabs.get(tabbed.getSelectedIndex());
537
538     if (activeTab instanceof EntryEditorTab)
539         ((EntryEditorTab)activeTab).activate();
540     else
541         new FocusRequester(source);
542         //((JComponent)activeTab).requestFocus();
543     }
544
545   /**
546    * Reports the enabled status of the editor, as set by setEnabled()
547    */
548   public boolean isEnabled() {
549     return source.isEnabled();
550   }
551
552   /**
553    * Sets the enabled status of all text fields of the entry editor.
554    */
555   public void setEnabled(boolean enabled) {
556       for (Iterator i=tabs.iterator(); i.hasNext();) {
557       Object o = i.next();
558       if (o instanceof EntryEditorTab) {
559           ((EntryEditorTab)o).setEnabled(enabled);
560       }
561       }
562       source.setEnabled(enabled);
563
564   }
565
566   /**
567    * Centers the given row, and highlights it.
568    *
569    * @param row
570    *          an <code>int</code> value
571    */
572   private void scrollTo(int row) {
573     panel.mainTable.setRowSelectionInterval(row, row);
574     panel.mainTable.ensureVisible(row);
575   }
576
577   /**
578     * Makes sure the current edit is stored.
579     */
580     public void storeCurrentEdit() {
581         Component comp = Globals.focusListener.getFocused();
582         if ((comp instanceof FieldEditor) && this.isAncestorOf(comp)) {
583             storeFieldAction.actionPerformed(new ActionEvent(comp, 0, ""));
584         }
585     }
586
587
588   /**
589    * Returns the index of the active (visible) panel.
590    *
591    * @return an <code>int</code> value
592    */
593   public int getVisiblePanel() {
594     return tabbed.getSelectedIndex();
595   }
596
597   /** Returns the name of the currently selected component. */
598   public String getVisiblePanelName() {
599       return tabbed.getSelectedComponent().getName();
600   }
601
602   /**
603    * Sets the panel with the given index visible.
604    *
605    * @param i
606    *          an <code>int</code> value
607    */
608   public void setVisiblePanel(int i) {
609       tabbed.setSelectedIndex(Math.min(i, tabbed.getTabCount() - 1));
610   }
611
612   public void setVisiblePanel(String name) {
613       for (int i = 0; i < tabbed.getTabCount(); ++i) {
614           if (name.equals(tabbed.getComponent(i).getName())) {
615               tabbed.setSelectedIndex(i);
616               return;
617           }
618       }
619       if (tabbed.getTabCount() > 0)
620           tabbed.setSelectedIndex(0);
621   }
622
623   /**
624    * Updates this editor to show the given entry, regardless of type
625    * correspondence.
626    *
627    * @param be
628    *          a <code>BibtexEntry</code> value
629    */
630   public synchronized void switchTo(BibtexEntry be) {
631       if (entry == be)
632         return;
633
634         //Util.pr("EntryEditor.switchTo(BibtexEntry): "+entry.getCiteKey());
635         //Util.pr("::EntryEditor.switchTo(BibtexEntry): "+this.type.getName());
636       storeCurrentEdit();
637          entry = be;
638         updateAllFields();
639         validateAllFields();
640         updateSource();
641         panel.showing = be;
642
643     }
644
645   /**
646    * Returns false if the contents of the source panel has not been validated,
647    * true othervise.
648    */
649   public boolean lastSourceAccepted() {
650     //Util.pr("Sourceaccepted ....");
651     if (tabbed.getSelectedComponent() == srcPanel)
652       storeSource(false);
653
654     return lastSourceAccepted;
655   }
656
657   /*
658    * public boolean storeSourceIfNeeded() { if (tabbed.getSelectedIndex() ==
659    * sourceIndex) return storeSource(); else return true; }
660    */
661   public boolean storeSource(boolean showError) {
662     // Store edited bibtex code.
663     BibtexParser bp = new BibtexParser(new java.io.StringReader(source.getText()));
664
665     try {
666       BibtexDatabase db = bp.parse().getDatabase();
667
668       if (db.getEntryCount() > 1)
669         throw new Exception("More than one entry found.");
670
671       if (db.getEntryCount() < 1)
672         throw new Exception("No entries found.");
673
674       NamedCompound compound = new NamedCompound(Globals.lang("source edit"));
675       BibtexEntry nu = db.getEntryById((String) db.getKeySet().iterator().next());
676       String id = entry.getId();
677       String
678       //oldKey = entry.getCiteKey(),
679       newKey = nu.getCiteKey();
680       boolean anyChanged = false;
681       boolean duplicateWarning = false;
682       boolean emptyWarning = newKey == null || newKey.equals("");
683
684       if (panel.database.setCiteKeyForEntry(id, newKey)) {
685         duplicateWarning = true;
686
687         // First, remove fields that the user have removed.
688       }
689
690       Object[] fields = entry.getAllFields();
691
692       for (int i = 0; i < fields.length; i++) {
693         if (GUIGlobals.isDisplayableField(fields[i].toString())) {
694           if (nu.getField(fields[i].toString()) == null) {
695             compound.addEdit(new UndoableFieldChange(entry, fields[i].toString(),
696                 entry.getField(fields[i].toString()), (Object) null));
697             entry.clearField(fields[i].toString());
698             anyChanged = true;
699           }
700         }
701       }
702
703       // Then set all fields that have been set by the user.
704       fields = nu.getAllFields();
705
706       for (int i = 0; i < fields.length; i++) {
707         if (entry.getField(fields[i].toString()) != nu.getField(fields[i].toString())) {
708           String toSet = (String) nu.getField(fields[i].toString());
709
710           // Test if the field is legally set.
711           (new LatexFieldFormatter()).format(toSet, fields[i].toString());
712
713           compound.addEdit(new UndoableFieldChange(entry, fields[i].toString(),
714               entry.getField(fields[i].toString()), toSet));
715           entry.setField(fields[i].toString(), toSet);
716           anyChanged = true;
717         }
718       }
719
720       compound.end();
721
722       if (!anyChanged)
723         return true;
724
725       panel.undoManager.addEdit(compound);
726
727       /*
728        * if (((oldKey == null) && (newKey != null)) || ((oldKey != null) &&
729        * (newKey == null)) || ((oldKey != null) && (newKey != null) &&
730        * !oldKey.equals(newKey))) { }
731        */
732       if (duplicateWarning) {
733         warnDuplicateBibtexkey();
734       } else if (emptyWarning && showError) {
735         warnEmptyBibtexkey();
736       } else {
737         panel.output(Globals.lang("Stored entry") + ".");
738       }
739
740       lastSourceStringAccepted = source.getText();
741       updateAllFields();
742       lastSourceAccepted = true;
743       updateSource = true;
744
745         // TODO: does updating work properly after source stored?
746       //  panel.tableModel.remap();
747       //  panel.entryTable.repaint();
748       //panel.refreshTable();
749       panel.markBaseChanged();
750
751       return true;
752     } catch (Throwable ex) {
753       //ex.printStackTrace();
754       // The source couldn't be parsed, so the user is given an
755       // error message, and the choice to keep or revert the contents
756       // of the source text field.
757       updateSource = false;
758       lastSourceAccepted = false;
759       tabbed.setSelectedComponent(srcPanel);
760
761       if (showError) {
762         Object[] options =
763           { Globals.lang("Edit"), Globals.lang("Revert to original source") };
764
765         int answer =
766           JOptionPane.showOptionDialog(frame, "Error: " + ex.getMessage(),
767             Globals.lang("Problem with parsing entry"), JOptionPane.YES_NO_OPTION,
768             JOptionPane.ERROR_MESSAGE, null, options, options[0]);
769
770         if (answer != 0) {
771           updateSource = true;
772           updateSource();
773         }
774       }
775
776       return false;
777     }
778   }
779
780
781   public void setField(String fieldName, String newFieldData) {
782
783       for (Iterator i=tabs.iterator(); i.hasNext();) {
784       Object o = i.next();
785       if (o instanceof EntryEditorTab) {
786           ((EntryEditorTab)o).updateField(fieldName, newFieldData);
787       }
788       }
789
790   }
791
792
793   /**
794    * Sets all the text areas according to the shown entry.
795    */
796   public void updateAllFields() {
797       //System.out.println("EntryEditor.updateAllFields()");
798       for (Iterator i=tabs.iterator(); i.hasNext();) {
799           Object o = i.next();
800           if (o instanceof EntryEditorTab) {
801               ((EntryEditorTab)o).setEntry(entry);
802           }
803       }
804   }
805
806   /**
807    * Removes the "invalid field" color from all text areas.
808    */
809   public void validateAllFields() {
810       for (Iterator i=tabs.iterator(); i.hasNext();) {
811       Object o = i.next();
812       if (o instanceof EntryEditorTab) {
813           ((EntryEditorTab)o).validateAllFields();
814       }
815       }
816   }
817
818   public void updateAllContentSelectors() {
819     if (contentSelectors.size() > 0) {
820       for (Iterator i = contentSelectors.iterator(); i.hasNext();)
821         ((FieldContentSelector) i.next()).updateList();
822     }
823   }
824
825   // Update the JTextArea when a field has changed.
826   public void vetoableChange(PropertyChangeEvent e) {
827     String newValue = ((e.getNewValue() != null) ? e.getNewValue().toString() : "");
828     setField(e.getPropertyName(), newValue);
829
830     //Util.pr(e.getPropertyName());
831   }
832
833
834   public void updateField(final Object source) {
835       storeFieldAction.actionPerformed(new ActionEvent(source, 0, ""));
836   }
837
838   private class TypeLabel extends JPanel {
839     private String label;
840
841     public TypeLabel(String type) {
842       label = type;
843       addMouseListener(new MouseAdapter() {
844           public void mouseClicked(MouseEvent e) {
845             boolean ctrlClick = prefs.getBoolean("ctrlClick");
846
847             if ((e.getButton() == MouseEvent.BUTTON3)
848                 || (ctrlClick && (e.getButton() == MouseEvent.BUTTON1)
849                 && e.isControlDown())) {
850               JPopupMenu typeMenu = new JPopupMenu();
851
852
853               //typeMenu.addSeparator();
854               for (Iterator i = BibtexEntryType.ALL_TYPES.keySet().iterator();
855                    i.hasNext();)
856                 typeMenu.add(new ChangeTypeAction(BibtexEntryType.getType(
857                       (String) i.next()), panel));
858
859               typeMenu.show(ths, e.getX(), e.getY());
860             }
861           }
862         });
863     }
864
865     public void paint(Graphics g) {
866       Graphics2D g2 = (Graphics2D) g;
867       g2.setColor(GUIGlobals.validFieldColor);
868       g2.setFont(GUIGlobals.typeNameFont);
869
870       FontMetrics fm = g2.getFontMetrics();
871       int width = fm.stringWidth(label);
872       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
873         RenderingHints.VALUE_ANTIALIAS_ON);
874       g2.rotate(-Math.PI / 2, 0, 0);
875       g2.drawString(label, -width - 7, 28);
876     }
877   }
878
879   class FieldListener extends FocusAdapter {
880     /*
881      * Focus listener that fires the storeFieldAction when a FieldTextArea loses
882      * focus.
883      */
884     public void focusGained(FocusEvent e) {
885     }
886
887     public void focusLost(FocusEvent e) {
888       //Util.pr("Lost focus "+e.getSource().toString().substring(0,30));
889       if (!e.isTemporary())
890         updateField(e.getSource());
891     }
892   }
893
894
895   class TabListener implements ChangeListener {
896     public void stateChanged(ChangeEvent e) {
897
898         SwingUtilities.invokeLater(new Runnable() {
899             public void run() {
900                 activateVisible();
901             }
902         });
903
904
905         // After the initial event train has finished, we tell the editor tab to update all
906         // its fields. This makes sure they are updated even if the tab we just left contained one
907         // or more of the same fields as this one:
908         SwingUtilities.invokeLater(new Runnable() {
909             public void run() {
910                 Object activeTab = tabs.get(tabbed.getSelectedIndex());
911                 if (activeTab instanceof EntryEditorTab)
912                     ((EntryEditorTab)activeTab).updateAll();
913             }
914         });
915
916     }
917   }
918
919     class DeleteAction extends AbstractAction {
920     public DeleteAction() {
921       super(Globals.lang("Delete"), new ImageIcon(GUIGlobals.removeIconFile));
922       putValue(SHORT_DESCRIPTION, Globals.lang("Delete entry"));
923     }
924
925     public void actionPerformed(ActionEvent e) {
926       // Show confirmation dialog if not disabled:
927       boolean goOn = panel.showDeleteConfirmationDialog(1);
928
929       if (!goOn)
930         return;
931
932       panel.entryEditorClosing(EntryEditor.this);
933       panel.database.removeEntry(entry.getId());
934       panel.markBaseChanged();
935       panel.undoManager.addEdit(new UndoableRemoveEntry(panel.database, entry, panel));
936       panel.output(Globals.lang("Deleted") + " " + Globals.lang("entry"));
937     }
938   }
939
940   class CloseAction extends AbstractAction {
941     public CloseAction() {
942       super(Globals.lang("Close window"), new ImageIcon(GUIGlobals.closeIconFile));
943       putValue(SHORT_DESCRIPTION, Globals.lang("Close window"));
944     }
945
946     public void actionPerformed(ActionEvent e) {
947         if (tabbed.getSelectedComponent() == srcPanel) {
948             updateField(source);
949             if (lastSourceAccepted)
950             panel.entryEditorClosing(EntryEditor.this);
951         } else
952             panel.entryEditorClosing(EntryEditor.this);
953     }
954   }
955
956   class CopyKeyAction extends AbstractAction {
957     public CopyKeyAction() {
958       super("Copy BibTeX key to clipboard", new ImageIcon(GUIGlobals.copyKeyIconFile));
959       putValue(SHORT_DESCRIPTION, "Copy BibTeX key to clipboard (Ctrl-K)");
960
961       //putValue(MNEMONIC_KEY, GUIGlobals.copyKeyCode);
962     }
963
964     public void actionPerformed(ActionEvent e) {
965       String s = (String) (entry.getField(KEY_PROPERTY));
966       StringSelection ss = new StringSelection(s);
967
968       if (s != null)
969         Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, ss);
970     }
971   }
972
973   public class StoreFieldAction extends AbstractAction {
974     public StoreFieldAction() {
975       super("Store field value");
976       putValue(SHORT_DESCRIPTION, "Store field value");
977     }
978
979     public void actionPerformed(ActionEvent e) {
980         //Util.pr("EntryEditor.StoreFieldAction: "+entry.getCiteKey());
981         //Util.pr("..EntryEditor.StoreFieldAction: "+this.toString());
982
983       if (e.getSource() instanceof FieldTextArea) {
984         String toSet = null;
985         FieldEditor fe = (FieldEditor) e.getSource();
986         boolean set;
987           //Util.pr("....EntryEditor.StoreFieldAction: "+fe.getFieldName());
988         //Util.pr("...."+fe.getText()+"....");
989         // Trim the whitespace off this value
990         fe.setText(fe.getText().trim());
991
992         if (fe.getText().length() > 0) {
993           toSet = fe.getText();
994
995           // We check if the field has changed, since we don't want to
996           // mark the
997           // base as changed unless we have a real change.
998         }
999
1000         if (toSet == null) {
1001           if (entry.getField(fe.getFieldName()) == null)
1002             set = false;
1003           else
1004             set = true;
1005         } else {
1006           if ((entry.getField(fe.getFieldName()) != null)
1007               && toSet.equals(entry.getField(fe.getFieldName()).toString()))
1008             set = false;
1009           else
1010             set = true;
1011         }
1012
1013         if (set) {
1014           try {
1015             // The following statement attempts to write the
1016             // new contents into a StringWriter, and this will
1017             // cause an IOException if the field is not
1018             // properly formatted. If that happens, the field
1019             // is not stored and the textarea turns red.
1020             if (toSet != null)
1021               (new LatexFieldFormatter()).format(toSet, fe.getFieldName());
1022
1023             Object oldValue = entry.getField(fe.getFieldName());
1024
1025             if (toSet != null)
1026               entry.setField(fe.getFieldName(), toSet);
1027             else
1028               entry.clearField(fe.getFieldName());
1029
1030             if ((toSet != null) && (toSet.length() > 0))
1031               //fe.setLabelColor(GUIGlobals.validFieldColor);
1032               fe.setBackground(GUIGlobals.validFieldBackground);
1033             else
1034               //fe.setLabelColor(GUIGlobals.nullFieldColor);
1035               fe.setBackground(GUIGlobals.validFieldBackground);
1036
1037             // Add an UndoableFieldChange to the baseframe's
1038             // undoManager.
1039             panel.undoManager.addEdit(new UndoableFieldChange(entry, fe.getFieldName(),
1040                 oldValue, toSet));
1041             updateSource();
1042             panel.markBaseChanged();
1043           } catch (IllegalArgumentException ex) {
1044             JOptionPane.showMessageDialog(frame, "Error: " + ex.getMessage(),
1045               Globals.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
1046             fe.setBackground(GUIGlobals.invalidFieldBackground);
1047           }
1048         }
1049         else {
1050           // set == false
1051           // We set the field and label color.
1052           fe.setBackground(GUIGlobals.validFieldBackground);
1053
1054           /*
1055            * fe.setLabelColor((toSet == null) ? GUIGlobals.nullFieldColor :
1056            * GUIGlobals.validFieldColor);
1057            */
1058         }
1059       } else if (e.getSource() instanceof FieldTextField) {
1060         // Storage from bibtex key field.
1061         FieldTextField fe = (FieldTextField) e.getSource();
1062         String oldValue = entry.getCiteKey();
1063         String newValue = fe.getText();
1064
1065         if (newValue.equals(""))
1066           newValue = null;
1067
1068         if (((oldValue == null) && (newValue == null))
1069             || ((oldValue != null) && (newValue != null) && oldValue.equals(newValue)))
1070           return; // No change.
1071
1072         // Make sure the key is legal:
1073         String cleaned = Util.checkLegalKey(newValue);
1074         if ((cleaned != null) && !cleaned.equals(newValue)) {
1075             JOptionPane.showMessageDialog(frame, Globals.lang("Invalid BibTeX key"),
1076               Globals.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
1077             fe.setBackground(GUIGlobals.invalidFieldBackground);
1078             return;
1079         } else {
1080             fe.setBackground(/*fe.getTextComponent().hasFocus() ?
1081                     GUIGlobals.activeEditor :*/
1082                     GUIGlobals.validFieldBackground);
1083         }
1084
1085         boolean isDuplicate = panel.database.setCiteKeyForEntry(entry.getId(), newValue);
1086
1087         if (newValue != null) {
1088           if (isDuplicate)
1089               warnDuplicateBibtexkey();
1090           else
1091             panel.output(Globals.lang("BibTeX key is unique."));
1092         } else { // key is null/empty
1093             warnEmptyBibtexkey();
1094         }
1095
1096         // Add an UndoableKeyChange to the baseframe's undoManager.
1097         panel.undoManager.addEdit(new UndoableKeyChange(panel.database, entry.getId(),
1098             oldValue, newValue));
1099
1100         if ((newValue != null) && (newValue.length() > 0))
1101           //fe.setLabelColor(GUIGlobals.validFieldColor);
1102           fe.setBackground(GUIGlobals.validFieldBackground);
1103         else
1104           //fe.setLabelColor(GUIGlobals.nullFieldColor);
1105           fe.setBackground(GUIGlobals.validFieldBackground);
1106
1107         updateSource();
1108         panel.markBaseChanged();
1109       } else if ((source.isEditable())
1110           && (!source.getText().equals(lastSourceStringAccepted))) {
1111         boolean accepted = storeSource(true);
1112
1113         if (accepted) {
1114         }
1115       }
1116     }
1117   }
1118
1119   class SwitchLeftAction extends AbstractAction {
1120     public SwitchLeftAction() {
1121       super("Switch to the panel to the left");
1122     }
1123
1124     public void actionPerformed(ActionEvent e) {
1125         //System.out.println("switch left");
1126       int i = tabbed.getSelectedIndex();
1127       tabbed.setSelectedIndex(((i > 0) ? (i - 1) : (tabbed.getTabCount() - 1)));
1128
1129       activateVisible();
1130     }
1131   }
1132
1133   class SwitchRightAction extends AbstractAction {
1134     public SwitchRightAction() {
1135       super("Switch to the panel to the right");
1136     }
1137
1138     public void actionPerformed(ActionEvent e) {
1139         //System.out.println("switch right");
1140       int i = tabbed.getSelectedIndex();
1141       tabbed.setSelectedIndex((i < (tabbed.getTabCount() - 1)) ? (i + 1) : 0);
1142       activateVisible();
1143
1144     }
1145   }
1146
1147   class NextEntryAction extends AbstractAction {
1148     public NextEntryAction() {
1149       super(Globals.lang("Next entry"), new ImageIcon(GUIGlobals.downIconFile));
1150
1151       putValue(SHORT_DESCRIPTION, Globals.lang("Next entry"));
1152     }
1153
1154     public void actionPerformed(ActionEvent e) {
1155
1156       int thisRow = panel.mainTable.findEntry(entry);
1157       String id = null;
1158       int newRow = -1;
1159
1160       if ((thisRow + 1) < panel.database.getEntryCount())
1161         newRow = thisRow + 1;
1162       else if (thisRow > 0)
1163         newRow = 0;
1164       else
1165         return; // newRow is still -1, so we can assume the database has only one entry.
1166
1167       scrollTo(newRow);
1168       panel.mainTable.setRowSelectionInterval(newRow, newRow);
1169
1170     }
1171   }
1172
1173   class PrevEntryAction extends AbstractAction {
1174     public PrevEntryAction() {
1175       super(Globals.lang("Previous entry"), new ImageIcon(GUIGlobals.upIconFile));
1176
1177       putValue(SHORT_DESCRIPTION, Globals.lang("Previous entry"));
1178     }
1179
1180     public void actionPerformed(ActionEvent e) {
1181       int thisRow = panel.mainTable.findEntry(entry);
1182       String id = null;
1183       int newRow = -1;
1184
1185       if ((thisRow - 1) >= 0)
1186         newRow = thisRow - 1;
1187       else if (thisRow != (panel.database.getEntryCount() - 1))
1188         newRow = panel.database.getEntryCount() - 1;
1189       else
1190         return; // newRow is still -1, so we can assume the database has only one entry.
1191       //id = panel.tableModel.getIdForRow(newRow);
1192       //switchTo(id);
1193
1194       scrollTo(newRow);
1195       panel.mainTable.setRowSelectionInterval(newRow, newRow);
1196
1197     }
1198   }
1199
1200   class GenerateKeyAction extends AbstractAction {
1201     JabRefFrame parent;
1202     BibtexEntry selectedEntry;
1203
1204     public GenerateKeyAction(JabRefFrame parentFrame) {
1205       super(Globals.lang("Generate BibTeX key"), new ImageIcon(GUIGlobals.genKeyIconFile));
1206       parent = parentFrame;
1207
1208       //            selectedEntry = newEntry ;
1209       putValue(SHORT_DESCRIPTION, Globals.lang("Generate BibTeX key"));
1210
1211       //        putValue(MNEMONIC_KEY, GUIGlobals.showGenKeyCode);
1212     }
1213
1214     public void actionPerformed(ActionEvent e) {
1215       // 1. get Bitexentry for selected index (already have)
1216       // 2. run the LabelMaker by it
1217       try {
1218         // this updates the table automatically, on close, but not
1219         // within the tab
1220         Object oldValue = entry.getField(GUIGlobals.KEY_FIELD);
1221
1222         //entry = frame.labelMaker.applyRule(entry, panel.database) ;
1223         LabelPatternUtil.makeLabel(prefs.getKeyPattern(), panel.database, entry);
1224
1225         // Store undo information:
1226         panel.undoManager.addEdit(new UndoableKeyChange(panel.database, entry.getId(),
1227             (String) oldValue, (String) entry.getField(GUIGlobals.KEY_FIELD)));
1228
1229         // here we update the field
1230         String bibtexKeyData = (String) entry.getField(Globals.KEY_FIELD);
1231
1232         // set the field named for "bibtexkey"
1233         setField(Globals.KEY_FIELD, bibtexKeyData);
1234         updateSource();
1235         panel.markBaseChanged();
1236       } catch (Throwable t) {
1237         System.err.println("error setting key: " + t);
1238       }
1239     }
1240   }
1241
1242   class UndoAction extends AbstractAction {
1243     public UndoAction() {
1244       super("Undo", new ImageIcon(GUIGlobals.undoIconFile));
1245       putValue(SHORT_DESCRIPTION, "Undo");
1246     }
1247
1248     public void actionPerformed(ActionEvent e) {
1249       try {
1250         panel.runCommand("undo");
1251       } catch (Throwable ex) {
1252       }
1253     }
1254   }
1255
1256   class RedoAction extends AbstractAction {
1257     public RedoAction() {
1258       super("Undo", new ImageIcon(GUIGlobals.redoIconFile));
1259       putValue(SHORT_DESCRIPTION, "Redo");
1260     }
1261
1262     public void actionPerformed(ActionEvent e) {
1263       try {
1264         panel.runCommand("redo");
1265       } catch (Throwable ex) {
1266       }
1267     }
1268   }
1269
1270   class SaveDatabaseAction extends AbstractAction {
1271     public SaveDatabaseAction() {
1272       super("Save database");
1273     }
1274
1275       public void actionPerformed(ActionEvent e) {
1276       Object activeTab = tabs.get(tabbed.getSelectedIndex());
1277       if (activeTab instanceof EntryEditorTab) {
1278           // Normal panel.
1279           EntryEditorTab fp = (EntryEditorTab)activeTab;
1280           updateField(fp.getActive());
1281       } else
1282           // Source panel.
1283           updateField(activeTab);
1284
1285       try {
1286           panel.runCommand("save");
1287       } catch (Throwable ex) {
1288       }
1289       }
1290   }
1291
1292   class ExternalViewerListener extends MouseAdapter {
1293     public void mouseClicked(MouseEvent evt) {
1294       if (evt.getClickCount() == 2) {
1295         FieldTextArea tf = (FieldTextArea) evt.getSource();
1296
1297         if (tf.getText().equals(""))
1298           return;
1299
1300         tf.selectAll();
1301
1302         String link = tf.getText(); // get selected ? String
1303
1304         // getSelectedText()
1305         try {
1306           Util.openExternalViewer(link, tf.getFieldName(), prefs);
1307         } catch (IOException ex) {
1308           System.err.println("Error opening file.");
1309         }
1310       }
1311     }
1312   }
1313
1314   class ChangeTypeAction extends AbstractAction {
1315     BibtexEntryType type;
1316     BasePanel panel;
1317
1318     public ChangeTypeAction(BibtexEntryType type, BasePanel bp) {
1319       super(type.getName());
1320       this.type = type;
1321       panel = bp;
1322     }
1323
1324     public void actionPerformed(ActionEvent evt) {
1325       panel.changeType(entry, type);
1326     }
1327   }
1328
1329   /**
1330    * Scans all groups.
1331    * @return true if the specified entry is contained in any ExplicitGroup,
1332    * false otherwise.
1333    */
1334   private boolean containedInExplicitGroup(BibtexEntry entry) {
1335       AbstractGroup[] matchingGroups = panel.getGroupSelector().getGroupTreeRoot().
1336       getMatchingGroups(entry);
1337       for (int i = 0; i < matchingGroups.length; ++i) {
1338           if (matchingGroups[i] instanceof ExplicitGroup)
1339               return true;
1340       }
1341       return false;
1342   }
1343
1344   private void warnDuplicateBibtexkey() {
1345         panel.output(Globals.lang("Warning") + ": "
1346                 + Globals.lang("Duplicate BibTeX key."));
1347
1348         if (prefs.getBoolean("dialogWarningForDuplicateKey")) {
1349             // JZTODO lyrics
1350             CheckBoxMessage jcb = new CheckBoxMessage(Globals.lang("Warning")
1351                     + ": " + Globals.lang("Duplicate BibTeX key. Grouping may not work for this entry."),
1352                     Globals.lang("Disable this warning dialog"), false);
1353             JOptionPane.showMessageDialog(frame, jcb, Globals.lang("Warning"),
1354                     JOptionPane.WARNING_MESSAGE);
1355
1356             if (jcb.isSelected())
1357                 prefs.putBoolean("dialogWarningForDuplicateKey", false);
1358         }
1359     }
1360
1361   private void warnEmptyBibtexkey() {
1362       // JZTODO lyrics
1363       panel.output(Globals.lang("Warning") + ": "
1364               + Globals.lang("Empty BibTeX key."));
1365
1366       if (prefs.getBoolean("dialogWarningForEmptyKey")) {
1367           // JZTODO lyrics
1368           CheckBoxMessage jcb = new CheckBoxMessage(Globals.lang("Warning")
1369                   + ": " + Globals.lang("Empty BibTeX key. Grouping may not work for this entry."),
1370                   Globals.lang("Disable this warning dialog"), false);
1371           JOptionPane.showMessageDialog(frame, jcb, Globals.lang("Warning"),
1372                   JOptionPane.WARNING_MESSAGE);
1373
1374           if (jcb.isSelected())
1375               prefs.putBoolean("dialogWarningForEmptyKey", false);
1376       }
1377   }
1378
1379 }