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