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