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