0857133d7f3943414cd47cad0b8000d67b4453cb
[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           new ImageIcon(GUIGlobals.showReqIconFile), 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               new ImageIcon(GUIGlobals.showOptIconFile), 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), new ImageIcon(GUIGlobals.showGenIconFile), 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               new ImageIcon(GUIGlobals.sourceIconFile), 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          entry = be;
645         updateAllFields();
646         validateAllFields();
647         updateSource();
648         panel.showing = be;
649
650     }
651
652   /**
653    * Returns false if the contents of the source panel has not been validated,
654    * true othervise.
655    */
656   public boolean lastSourceAccepted() {
657     //Util.pr("Sourceaccepted ....");
658     if (tabbed.getSelectedComponent() == srcPanel)
659       storeSource(false);
660
661     return lastSourceAccepted;
662   }
663
664   /*
665    * public boolean storeSourceIfNeeded() { if (tabbed.getSelectedIndex() ==
666    * sourceIndex) return storeSource(); else return true; }
667    */
668   public boolean storeSource(boolean showError) {
669     // Store edited bibtex code.
670     BibtexParser bp = new BibtexParser(new java.io.StringReader(source.getText()));
671
672     try {
673       BibtexDatabase db = bp.parse().getDatabase();
674
675       if (db.getEntryCount() > 1)
676         throw new Exception("More than one entry found.");
677
678       if (db.getEntryCount() < 1)
679         throw new Exception("No entries found.");
680
681       NamedCompound compound = new NamedCompound(Globals.lang("source edit"));
682       BibtexEntry nu = db.getEntryById((String) db.getKeySet().iterator().next());
683       String id = entry.getId();
684       String
685       //oldKey = entry.getCiteKey(),
686       newKey = nu.getCiteKey();
687       boolean anyChanged = false;
688       boolean duplicateWarning = false;
689       boolean emptyWarning = newKey == null || newKey.equals("");
690
691       if (panel.database.setCiteKeyForEntry(id, newKey)) {
692         duplicateWarning = true;
693
694         // First, remove fields that the user have removed.
695       }
696
697       Object[] fields = entry.getAllFields();
698
699       for (int i = 0; i < fields.length; i++) {
700         if (BibtexFields.isDisplayableField(fields[i].toString())) {
701           if (nu.getField(fields[i].toString()) == null) {
702             compound.addEdit(new UndoableFieldChange(entry, fields[i].toString(),
703                 entry.getField(fields[i].toString()), (Object) null));
704             entry.clearField(fields[i].toString());
705             anyChanged = true;
706           }
707         }
708       }
709
710       // Then set all fields that have been set by the user.
711       fields = nu.getAllFields();
712
713       for (int i = 0; i < fields.length; i++) {
714         if (entry.getField(fields[i].toString()) != nu.getField(fields[i].toString())) {
715           String toSet = (String) nu.getField(fields[i].toString());
716
717           // Test if the field is legally set.
718           (new LatexFieldFormatter()).format(toSet, fields[i].toString());
719
720           compound.addEdit(new UndoableFieldChange(entry, fields[i].toString(),
721               entry.getField(fields[i].toString()), toSet));
722           entry.setField(fields[i].toString(), toSet);
723           anyChanged = true;
724         }
725       }
726
727       compound.end();
728
729       if (!anyChanged)
730         return true;
731
732       panel.undoManager.addEdit(compound);
733
734       /*
735        * if (((oldKey == null) && (newKey != null)) || ((oldKey != null) &&
736        * (newKey == null)) || ((oldKey != null) && (newKey != null) &&
737        * !oldKey.equals(newKey))) { }
738        */
739       if (duplicateWarning) {
740         warnDuplicateBibtexkey();
741       } else if (emptyWarning && showError) {
742         warnEmptyBibtexkey();
743       } else {
744         panel.output(Globals.lang("Stored entry") + ".");
745       }
746
747       lastSourceStringAccepted = source.getText();
748       updateAllFields();
749       lastSourceAccepted = true;
750       updateSource = true;
751
752         // TODO: does updating work properly after source stored?
753       //  panel.tableModel.remap();
754       //  panel.entryTable.repaint();
755       //panel.refreshTable();
756       panel.markBaseChanged();
757
758       return true;
759     } catch (Throwable ex) {
760       //ex.printStackTrace();
761       // The source couldn't be parsed, so the user is given an
762       // error message, and the choice to keep or revert the contents
763       // of the source text field.
764       updateSource = false;
765       lastSourceAccepted = false;
766       tabbed.setSelectedComponent(srcPanel);
767
768       if (showError) {
769         Object[] options =
770           { Globals.lang("Edit"), Globals.lang("Revert to original source") };
771
772         int answer =
773           JOptionPane.showOptionDialog(frame, "Error: " + ex.getMessage(),
774             Globals.lang("Problem with parsing entry"), JOptionPane.YES_NO_OPTION,
775             JOptionPane.ERROR_MESSAGE, null, options, options[0]);
776
777         if (answer != 0) {
778           updateSource = true;
779           updateSource();
780         }
781       }
782
783       return false;
784     }
785   }
786
787
788   public void setField(String fieldName, String newFieldData) {
789
790       for (Iterator i=tabs.iterator(); i.hasNext();) {
791       Object o = i.next();
792       if (o instanceof EntryEditorTab) {
793           ((EntryEditorTab)o).updateField(fieldName, newFieldData);
794       }
795       }
796
797   }
798
799
800   /**
801    * Sets all the text areas according to the shown entry.
802    */
803   public void updateAllFields() {
804       //System.out.println("EntryEditor.updateAllFields()");
805       for (Iterator i=tabs.iterator(); i.hasNext();) {
806           Object o = i.next();
807           if (o instanceof EntryEditorTab) {
808               ((EntryEditorTab)o).setEntry(entry);
809           }
810       }
811   }
812
813   /**
814    * Removes the "invalid field" color from all text areas.
815    */
816   public void validateAllFields() {
817       for (Iterator i=tabs.iterator(); i.hasNext();) {
818       Object o = i.next();
819       if (o instanceof EntryEditorTab) {
820           ((EntryEditorTab)o).validateAllFields();
821       }
822       }
823   }
824
825   public void updateAllContentSelectors() {
826     if (contentSelectors.size() > 0) {
827       for (Iterator i = contentSelectors.iterator(); i.hasNext();)
828         ((FieldContentSelector) i.next()).updateList();
829     }
830   }
831
832   // Update the JTextArea when a field has changed.
833   public void vetoableChange(PropertyChangeEvent e) {
834     String newValue = ((e.getNewValue() != null) ? e.getNewValue().toString() : "");
835     setField(e.getPropertyName(), newValue);
836
837     //Util.pr(e.getPropertyName());
838   }
839
840
841   public void updateField(final Object source) {
842       storeFieldAction.actionPerformed(new ActionEvent(source, 0, ""));
843   }
844
845   private class TypeLabel extends JPanel {
846     private String label;
847
848     public TypeLabel(String type) {
849       label = type;
850       addMouseListener(new MouseAdapter() {
851           public void mouseClicked(MouseEvent e) {
852             boolean ctrlClick = prefs.getBoolean("ctrlClick");
853
854             if ((e.getButton() == MouseEvent.BUTTON3)
855                 || (ctrlClick && (e.getButton() == MouseEvent.BUTTON1)
856                 && e.isControlDown())) {
857               JPopupMenu typeMenu = new JPopupMenu();
858
859
860               //typeMenu.addSeparator();
861               for (Iterator i = BibtexEntryType.ALL_TYPES.keySet().iterator();
862                    i.hasNext();)
863                 typeMenu.add(new ChangeTypeAction(BibtexEntryType.getType(
864                       (String) i.next()), panel));
865
866               typeMenu.show(ths, e.getX(), e.getY());
867             }
868           }
869         });
870     }
871
872     public void paint(Graphics g) {
873       Graphics2D g2 = (Graphics2D) g;
874       g2.setColor(GUIGlobals.validFieldColor);
875       g2.setFont(GUIGlobals.typeNameFont);
876
877       FontMetrics fm = g2.getFontMetrics();
878       int width = fm.stringWidth(label);
879       g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
880         RenderingHints.VALUE_ANTIALIAS_ON);
881       g2.rotate(-Math.PI / 2, 0, 0);
882       g2.drawString(label, -width - 7, 28);
883     }
884   }
885
886   class FieldListener extends FocusAdapter {
887     /*
888      * Focus listener that fires the storeFieldAction when a FieldTextArea loses
889      * focus.
890      */
891     public void focusGained(FocusEvent e) {
892     }
893
894     public void focusLost(FocusEvent e) {
895       //Util.pr("Lost focus "+e.getSource().toString().substring(0,30));
896       if (!e.isTemporary())
897         updateField(e.getSource());
898     }
899   }
900
901
902   class TabListener implements ChangeListener {
903     public void stateChanged(ChangeEvent e) {
904
905         SwingUtilities.invokeLater(new Runnable() {
906             public void run() {
907                 activateVisible();
908             }
909         });
910
911
912         // After the initial event train has finished, we tell the editor tab to update all
913         // its fields. This makes sure they are updated even if the tab we just left contained one
914         // or more of the same fields as this one:
915         SwingUtilities.invokeLater(new Runnable() {
916             public void run() {
917                 Object activeTab = tabs.get(tabbed.getSelectedIndex());
918                 if (activeTab instanceof EntryEditorTab)
919                     ((EntryEditorTab)activeTab).updateAll();
920             }
921         });
922
923     }
924   }
925
926     class DeleteAction extends AbstractAction {
927     public DeleteAction() {
928       super(Globals.lang("Delete"), new ImageIcon(GUIGlobals.removeIconFile));
929       putValue(SHORT_DESCRIPTION, Globals.lang("Delete entry"));
930     }
931
932     public void actionPerformed(ActionEvent e) {
933       // Show confirmation dialog if not disabled:
934       boolean goOn = panel.showDeleteConfirmationDialog(1);
935
936       if (!goOn)
937         return;
938
939       panel.entryEditorClosing(EntryEditor.this);
940       panel.database.removeEntry(entry.getId());
941       panel.markBaseChanged();
942       panel.undoManager.addEdit(new UndoableRemoveEntry(panel.database, entry, panel));
943       panel.output(Globals.lang("Deleted") + " " + Globals.lang("entry"));
944     }
945   }
946
947   class CloseAction extends AbstractAction {
948     public CloseAction() {
949       super(Globals.lang("Close window"), new ImageIcon(GUIGlobals.closeIconFile));
950       putValue(SHORT_DESCRIPTION, Globals.lang("Close window"));
951     }
952
953     public void actionPerformed(ActionEvent e) {
954         if (tabbed.getSelectedComponent() == srcPanel) {
955             updateField(source);
956             if (lastSourceAccepted)
957             panel.entryEditorClosing(EntryEditor.this);
958         } else
959             panel.entryEditorClosing(EntryEditor.this);
960     }
961   }
962
963   class CopyKeyAction extends AbstractAction {
964     public CopyKeyAction() {
965       super("Copy BibTeX key to clipboard", new ImageIcon(GUIGlobals.copyKeyIconFile));
966       putValue(SHORT_DESCRIPTION, "Copy BibTeX key to clipboard (Ctrl-K)");
967
968       //putValue(MNEMONIC_KEY, GUIGlobals.copyKeyCode);
969     }
970
971     public void actionPerformed(ActionEvent e) {
972       String s = (String) (entry.getField(KEY_PROPERTY));
973       StringSelection ss = new StringSelection(s);
974
975       if (s != null)
976         Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, ss);
977     }
978   }
979
980   public class StoreFieldAction extends AbstractAction {
981     public StoreFieldAction() {
982       super("Store field value");
983       putValue(SHORT_DESCRIPTION, "Store field value");
984     }
985
986     public void actionPerformed(ActionEvent e) {
987
988       if (e.getSource() instanceof FieldTextArea) {
989         String toSet = null;
990         FieldEditor fe = (FieldEditor) e.getSource();
991         boolean set;
992         // Trim the whitespace off this value
993         fe.setText(fe.getText().trim());
994
995         if (fe.getText().length() > 0) {
996           toSet = fe.getText();
997         }
998
999
1000         // We check if the field has changed, since we don't want to
1001         // mark the
1002         // base as changed unless we have a real change.
1003         if (toSet == null) {
1004           if (entry.getField(fe.getFieldName()) == null)
1005             set = false;
1006           else
1007             set = true;
1008         } else {
1009           if ((entry.getField(fe.getFieldName()) != null)
1010               && toSet.equals(entry.getField(fe.getFieldName()).toString()))
1011             set = false;
1012           else
1013             set = true;
1014         }
1015
1016         if (set) {
1017           try {
1018             // The following statement attempts to write the
1019             // new contents into a StringWriter, and this will
1020             // cause an IOException if the field is not
1021             // properly formatted. If that happens, the field
1022             // is not stored and the textarea turns red.
1023             if (toSet != null)
1024               (new LatexFieldFormatter()).format(toSet, fe.getFieldName());
1025
1026             Object oldValue = entry.getField(fe.getFieldName());
1027
1028             if (toSet != null)
1029               entry.setField(fe.getFieldName(), toSet);
1030             else
1031               entry.clearField(fe.getFieldName());
1032
1033             if ((toSet != null) && (toSet.length() > 0))
1034               //fe.setLabelColor(GUIGlobals.validFieldColor);
1035               fe.setBackground(GUIGlobals.validFieldBackground);
1036             else
1037               //fe.setLabelColor(GUIGlobals.nullFieldColor);
1038               fe.setBackground(GUIGlobals.validFieldBackground);
1039
1040             // Add an UndoableFieldChange to the baseframe's
1041             // undoManager.
1042             panel.undoManager.addEdit(new UndoableFieldChange(entry, fe.getFieldName(),
1043                 oldValue, toSet));
1044             updateSource();
1045             panel.markBaseChanged();
1046
1047               // TODO: is this a safe solution to keep selection on entry?
1048               SwingUtilities.invokeLater(new Runnable() {
1049                   public void run() {
1050                       panel.highlightEntry(entry);
1051                   }
1052               });
1053
1054
1055           } catch (IllegalArgumentException ex) {
1056             JOptionPane.showMessageDialog(frame, "Error: " + ex.getMessage(),
1057               Globals.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
1058             fe.setBackground(GUIGlobals.invalidFieldBackground);
1059           }
1060         }
1061         else {
1062           // set == false
1063           // We set the field and label color.
1064           fe.setBackground(GUIGlobals.validFieldBackground);
1065
1066           /*
1067            * fe.setLabelColor((toSet == null) ? GUIGlobals.nullFieldColor :
1068            * GUIGlobals.validFieldColor);
1069            */
1070         }
1071       } else if (e.getSource() instanceof FieldTextField) {
1072         // Storage from bibtex key field.
1073         FieldTextField fe = (FieldTextField) e.getSource();
1074         String oldValue = entry.getCiteKey();
1075         String newValue = fe.getText();
1076
1077         if (newValue.equals(""))
1078           newValue = null;
1079
1080         if (((oldValue == null) && (newValue == null))
1081             || ((oldValue != null) && (newValue != null) && oldValue.equals(newValue)))
1082           return; // No change.
1083
1084         // Make sure the key is legal:
1085         String cleaned = Util.checkLegalKey(newValue);
1086         if ((cleaned != null) && !cleaned.equals(newValue)) {
1087             JOptionPane.showMessageDialog(frame, Globals.lang("Invalid BibTeX key"),
1088               Globals.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
1089             fe.setBackground(GUIGlobals.invalidFieldBackground);
1090             return;
1091         } else {
1092             fe.setBackground(/*fe.getTextComponent().hasFocus() ?
1093                     GUIGlobals.activeEditor :*/
1094                     GUIGlobals.validFieldBackground);
1095         }
1096
1097         boolean isDuplicate = panel.database.setCiteKeyForEntry(entry.getId(), newValue);
1098
1099         if (newValue != null) {
1100           if (isDuplicate)
1101               warnDuplicateBibtexkey();
1102           else
1103             panel.output(Globals.lang("BibTeX key is unique."));
1104         } else { // key is null/empty
1105             warnEmptyBibtexkey();
1106         }
1107
1108         // Add an UndoableKeyChange to the baseframe's undoManager.
1109         panel.undoManager.addEdit(new UndoableKeyChange(panel.database, entry.getId(),
1110             oldValue, newValue));
1111
1112         if ((newValue != null) && (newValue.length() > 0))
1113           //fe.setLabelColor(GUIGlobals.validFieldColor);
1114           fe.setBackground(GUIGlobals.validFieldBackground);
1115         else
1116           //fe.setLabelColor(GUIGlobals.nullFieldColor);
1117           fe.setBackground(GUIGlobals.validFieldBackground);
1118
1119         updateSource();
1120         panel.markBaseChanged();
1121       } else if ((source.isEditable())
1122           && (!source.getText().equals(lastSourceStringAccepted))) {
1123         boolean accepted = storeSource(true);
1124
1125         if (accepted) {
1126         }
1127       }
1128     }
1129   }
1130
1131   class SwitchLeftAction extends AbstractAction {
1132     public SwitchLeftAction() {
1133       super("Switch to the panel to the left");
1134     }
1135
1136     public void actionPerformed(ActionEvent e) {
1137         //System.out.println("switch left");
1138       int i = tabbed.getSelectedIndex();
1139       tabbed.setSelectedIndex(((i > 0) ? (i - 1) : (tabbed.getTabCount() - 1)));
1140
1141       activateVisible();
1142     }
1143   }
1144
1145   class SwitchRightAction extends AbstractAction {
1146     public SwitchRightAction() {
1147       super("Switch to the panel to the right");
1148     }
1149
1150     public void actionPerformed(ActionEvent e) {
1151         //System.out.println("switch right");
1152       int i = tabbed.getSelectedIndex();
1153       tabbed.setSelectedIndex((i < (tabbed.getTabCount() - 1)) ? (i + 1) : 0);
1154       activateVisible();
1155
1156     }
1157   }
1158
1159   class NextEntryAction extends AbstractAction {
1160     public NextEntryAction() {
1161       super(Globals.lang("Next entry"), new ImageIcon(GUIGlobals.downIconFile));
1162
1163       putValue(SHORT_DESCRIPTION, Globals.lang("Next entry"));
1164     }
1165
1166     public void actionPerformed(ActionEvent e) {
1167
1168       int thisRow = panel.mainTable.findEntry(entry);
1169       String id = null;
1170       int newRow = -1;
1171
1172       if ((thisRow + 1) < panel.database.getEntryCount())
1173         newRow = thisRow + 1;
1174       else if (thisRow > 0)
1175         newRow = 0;
1176       else
1177         return; // newRow is still -1, so we can assume the database has only one entry.
1178
1179       scrollTo(newRow);
1180       panel.mainTable.setRowSelectionInterval(newRow, newRow);
1181
1182     }
1183   }
1184
1185   class PrevEntryAction extends AbstractAction {
1186     public PrevEntryAction() {
1187       super(Globals.lang("Previous entry"), new ImageIcon(GUIGlobals.upIconFile));
1188
1189       putValue(SHORT_DESCRIPTION, Globals.lang("Previous entry"));
1190     }
1191
1192     public void actionPerformed(ActionEvent e) {
1193       int thisRow = panel.mainTable.findEntry(entry);
1194       String id = null;
1195       int newRow = -1;
1196
1197       if ((thisRow - 1) >= 0)
1198         newRow = thisRow - 1;
1199       else if (thisRow != (panel.database.getEntryCount() - 1))
1200         newRow = panel.database.getEntryCount() - 1;
1201       else
1202         return; // newRow is still -1, so we can assume the database has only one entry.
1203       //id = panel.tableModel.getIdForRow(newRow);
1204       //switchTo(id);
1205
1206       scrollTo(newRow);
1207       panel.mainTable.setRowSelectionInterval(newRow, newRow);
1208
1209     }
1210   }
1211
1212   class GenerateKeyAction extends AbstractAction {
1213     JabRefFrame parent;
1214     BibtexEntry selectedEntry;
1215
1216     public GenerateKeyAction(JabRefFrame parentFrame) {
1217       super(Globals.lang("Generate BibTeX key"), new ImageIcon(GUIGlobals.genKeyIconFile));
1218       parent = parentFrame;
1219
1220       //            selectedEntry = newEntry ;
1221       putValue(SHORT_DESCRIPTION, Globals.lang("Generate BibTeX key"));
1222
1223       //        putValue(MNEMONIC_KEY, GUIGlobals.showGenKeyCode);
1224     }
1225
1226     public void actionPerformed(ActionEvent e) {
1227       // 1. get Bitexentry for selected index (already have)
1228       // 2. run the LabelMaker by it
1229       try {
1230         // this updates the table automatically, on close, but not
1231         // within the tab
1232         Object oldValue = entry.getField(BibtexFields.KEY_FIELD);
1233
1234         //entry = frame.labelMaker.applyRule(entry, panel.database) ;
1235         LabelPatternUtil.makeLabel(prefs.getKeyPattern(), panel.database, entry);
1236
1237         // Store undo information:
1238         panel.undoManager.addEdit(new UndoableKeyChange(panel.database, entry.getId(),
1239             (String) oldValue, (String) entry.getField(BibtexFields.KEY_FIELD)));
1240
1241         // here we update the field
1242         String bibtexKeyData = (String) entry.getField(BibtexFields.KEY_FIELD);
1243
1244         // set the field named for "bibtexkey"
1245         setField(BibtexFields.KEY_FIELD, bibtexKeyData);
1246         updateSource();
1247         panel.markBaseChanged();
1248       } catch (Throwable t) {
1249         System.err.println("error setting key: " + t);
1250       }
1251     }
1252   }
1253
1254   class UndoAction extends AbstractAction {
1255     public UndoAction() {
1256       super("Undo", new ImageIcon(GUIGlobals.undoIconFile));
1257       putValue(SHORT_DESCRIPTION, "Undo");
1258     }
1259
1260     public void actionPerformed(ActionEvent e) {
1261       try {
1262         panel.runCommand("undo");
1263       } catch (Throwable ex) {
1264       }
1265     }
1266   }
1267
1268   class RedoAction extends AbstractAction {
1269     public RedoAction() {
1270       super("Undo", new ImageIcon(GUIGlobals.redoIconFile));
1271       putValue(SHORT_DESCRIPTION, "Redo");
1272     }
1273
1274     public void actionPerformed(ActionEvent e) {
1275       try {
1276         panel.runCommand("redo");
1277       } catch (Throwable ex) {
1278       }
1279     }
1280   }
1281
1282   class SaveDatabaseAction extends AbstractAction {
1283     public SaveDatabaseAction() {
1284       super("Save database");
1285     }
1286
1287       public void actionPerformed(ActionEvent e) {
1288       Object activeTab = tabs.get(tabbed.getSelectedIndex());
1289       if (activeTab instanceof EntryEditorTab) {
1290           // Normal panel.
1291           EntryEditorTab fp = (EntryEditorTab)activeTab;
1292           updateField(fp.getActive());
1293       } else
1294           // Source panel.
1295           updateField(activeTab);
1296
1297       try {
1298           panel.runCommand("save");
1299       } catch (Throwable ex) {
1300       }
1301       }
1302   }
1303
1304   class ExternalViewerListener extends MouseAdapter {
1305     public void mouseClicked(MouseEvent evt) {
1306       if (evt.getClickCount() == 2) {
1307         FieldTextArea tf = (FieldTextArea) evt.getSource();
1308
1309         if (tf.getText().equals(""))
1310           return;
1311
1312         tf.selectAll();
1313
1314         String link = tf.getText(); // get selected ? String
1315
1316         // getSelectedText()
1317         try {
1318           Util.openExternalViewer(panel.metaData(), link, tf.getFieldName());
1319         } catch (IOException ex) {
1320           System.err.println("Error opening file.");
1321         }
1322       }
1323     }
1324   }
1325
1326   class ChangeTypeAction extends AbstractAction {
1327     BibtexEntryType type;
1328     BasePanel panel;
1329
1330     public ChangeTypeAction(BibtexEntryType type, BasePanel bp) {
1331       super(type.getName());
1332       this.type = type;
1333       panel = bp;
1334     }
1335
1336     public void actionPerformed(ActionEvent evt) {
1337       panel.changeType(entry, type);
1338     }
1339   }
1340
1341   /**
1342    * Scans all groups.
1343    * @return true if the specified entry is contained in any ExplicitGroup,
1344    * false otherwise.
1345    */
1346   private boolean containedInExplicitGroup(BibtexEntry entry) {
1347       AbstractGroup[] matchingGroups = panel.getGroupSelector().getGroupTreeRoot().
1348       getMatchingGroups(entry);
1349       for (int i = 0; i < matchingGroups.length; ++i) {
1350           if (matchingGroups[i] instanceof ExplicitGroup)
1351               return true;
1352       }
1353       return false;
1354   }
1355
1356   private void warnDuplicateBibtexkey() {
1357         panel.output(Globals.lang("Warning") + ": "
1358                 + Globals.lang("Duplicate BibTeX key."));
1359
1360         if (prefs.getBoolean("dialogWarningForDuplicateKey")) {
1361             // JZTODO lyrics
1362             CheckBoxMessage jcb = new CheckBoxMessage(Globals.lang("Warning")
1363                     + ": " + Globals.lang("Duplicate BibTeX key. Grouping may not work for this entry."),
1364                     Globals.lang("Disable this warning dialog"), false);
1365             JOptionPane.showMessageDialog(frame, jcb, Globals.lang("Warning"),
1366                     JOptionPane.WARNING_MESSAGE);
1367
1368             if (jcb.isSelected())
1369                 prefs.putBoolean("dialogWarningForDuplicateKey", false);
1370         }
1371     }
1372
1373   private void warnEmptyBibtexkey() {
1374       // JZTODO lyrics
1375       panel.output(Globals.lang("Warning") + ": "
1376               + Globals.lang("Empty BibTeX key."));
1377
1378       if (prefs.getBoolean("dialogWarningForEmptyKey")) {
1379           // JZTODO lyrics
1380           CheckBoxMessage jcb = new CheckBoxMessage(Globals.lang("Warning")
1381                   + ": " + Globals.lang("Empty BibTeX key. Grouping may not work for this entry."),
1382                   Globals.lang("Disable this warning dialog"), false);
1383           JOptionPane.showMessageDialog(frame, jcb, Globals.lang("Warning"),
1384                   JOptionPane.WARNING_MESSAGE);
1385
1386           if (jcb.isSelected())
1387               prefs.putBoolean("dialogWarningForEmptyKey", false);
1388       }
1389   }
1390
1391 }