5d5dc1cfd27b8a049417009015aefa77f902e988
[debian/jabref.git] / src / main / java / net / sf / jabref / BasePanel.java
1 /*  Copyright (C) 2003-2012 JabRef contributors.
2     This program is free software; you can redistribute it and/or modify
3     it under the terms of the GNU General Public License as published by
4     the Free Software Foundation; either version 2 of the License, or
5     (at your option) any later version.
6
7     This program is distributed in the hope that it will be useful,
8     but WITHOUT ANY WARRANTY; without even the implied warranty of
9     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10     GNU General Public License for more details.
11
12     You should have received a copy of the GNU General Public License along
13     with this program; if not, write to the Free Software Foundation, Inc.,
14     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
15 */
16 package net.sf.jabref;
17
18 import java.awt.BorderLayout;
19 import java.awt.Color;
20 import java.awt.Component;
21 import java.awt.GridBagConstraints;
22 import java.awt.GridBagLayout;
23 import java.awt.Toolkit;
24 import java.awt.datatransfer.Clipboard;
25 import java.awt.datatransfer.ClipboardOwner;
26 import java.awt.datatransfer.DataFlavor;
27 import java.awt.datatransfer.StringSelection;
28 import java.awt.datatransfer.Transferable;
29 import java.awt.datatransfer.UnsupportedFlavorException;
30 import java.awt.event.ActionEvent;
31 import java.awt.event.KeyAdapter;
32 import java.awt.event.KeyEvent;
33 import java.io.File;
34 import java.io.IOException;
35 import java.io.StringReader;
36 import java.nio.charset.UnsupportedCharsetException;
37 import java.util.ArrayList;
38 import java.util.Arrays;
39 import java.util.Collection;
40 import java.util.HashMap;
41 import java.util.Iterator;
42 import java.util.List;
43 import java.util.Map;
44 import java.util.Set;
45 import java.util.Vector;
46 import java.util.logging.Logger;
47
48 import javax.swing.AbstractAction;
49 import javax.swing.BorderFactory;
50 import javax.swing.JComponent;
51 import javax.swing.JFileChooser;
52 import javax.swing.JOptionPane;
53 import javax.swing.JPanel;
54 import javax.swing.JSplitPane;
55 import javax.swing.JTextArea;
56 import javax.swing.SwingUtilities;
57 import javax.swing.tree.TreePath;
58 import javax.swing.undo.CannotRedoException;
59 import javax.swing.undo.CannotUndoException;
60
61 import net.sf.jabref.DatabaseChangeEvent.ChangeType;
62 import net.sf.jabref.autocompleter.AbstractAutoCompleter;
63 import net.sf.jabref.autocompleter.AutoCompleterFactory;
64 import net.sf.jabref.autocompleter.NameFieldAutoCompleter;
65 import net.sf.jabref.collab.ChangeScanner;
66 import net.sf.jabref.collab.FileUpdateListener;
67 import net.sf.jabref.collab.FileUpdatePanel;
68 import net.sf.jabref.export.ExportToClipboardAction;
69 import net.sf.jabref.export.FileActions;
70 import net.sf.jabref.export.SaveDatabaseAction;
71 import net.sf.jabref.export.SaveException;
72 import net.sf.jabref.export.SaveSession;
73 import net.sf.jabref.export.layout.Layout;
74 import net.sf.jabref.export.layout.LayoutHelper;
75 import net.sf.jabref.external.AttachFileAction;
76 import net.sf.jabref.external.AutoSetExternalFileForEntries;
77 import net.sf.jabref.external.ExternalFileMenuItem;
78 import net.sf.jabref.external.ExternalFileType;
79 import net.sf.jabref.external.FindFullTextAction;
80 import net.sf.jabref.external.RegExpFileSearch;
81 import net.sf.jabref.external.SynchronizeFileField;
82 import net.sf.jabref.external.WriteXMPAction;
83 import net.sf.jabref.groups.GroupSelector;
84 import net.sf.jabref.groups.GroupTreeNode;
85 import net.sf.jabref.gui.*;
86 import net.sf.jabref.imports.AppendDatabaseAction;
87 import net.sf.jabref.imports.BibtexParser;
88 import net.sf.jabref.imports.SPIRESFetcher;
89 import net.sf.jabref.journals.AbbreviateAction;
90 import net.sf.jabref.journals.UnabbreviateAction;
91 import net.sf.jabref.labelPattern.LabelPatternUtil;
92 import net.sf.jabref.labelPattern.SearchFixDuplicateLabels;
93 import net.sf.jabref.search.NoSearchMatcher;
94 import net.sf.jabref.search.SearchMatcher;
95 import net.sf.jabref.specialfields.Printed;
96 import net.sf.jabref.specialfields.Priority;
97 import net.sf.jabref.specialfields.Quality;
98 import net.sf.jabref.specialfields.Rank;
99 import net.sf.jabref.specialfields.ReadStatus;
100 import net.sf.jabref.specialfields.Relevance;
101 import net.sf.jabref.specialfields.SpecialFieldAction;
102 import net.sf.jabref.specialfields.SpecialFieldDatabaseChangeListener;
103 import net.sf.jabref.specialfields.SpecialFieldValue;
104 import net.sf.jabref.sql.DBConnectDialog;
105 import net.sf.jabref.sql.DBExporterAndImporterFactory;
106 import net.sf.jabref.sql.DBStrings;
107 import net.sf.jabref.sql.DbConnectAction;
108 import net.sf.jabref.sql.SQLUtil;
109 import net.sf.jabref.sql.exporter.DBExporter;
110 import net.sf.jabref.undo.CountingUndoManager;
111 import net.sf.jabref.undo.NamedCompound;
112 import net.sf.jabref.undo.UndoableChangeType;
113 import net.sf.jabref.undo.UndoableInsertEntry;
114 import net.sf.jabref.undo.UndoableKeyChange;
115 import net.sf.jabref.undo.UndoableRemoveEntry;
116 import net.sf.jabref.wizard.text.gui.TextInputDialog;
117 import ca.odell.glazedlists.FilterList;
118 import ca.odell.glazedlists.event.ListEvent;
119 import ca.odell.glazedlists.event.ListEventListener;
120 import ca.odell.glazedlists.matchers.Matcher;
121
122 import com.jgoodies.forms.builder.DefaultFormBuilder;
123 import com.jgoodies.forms.layout.FormLayout;
124 import com.jgoodies.uif_lite.component.UIFSplitPane;
125
126 public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener {
127     private static Logger logger = Logger.getLogger(BasePanel.class.getName());
128
129     public final static int SHOWING_NOTHING=0, SHOWING_PREVIEW=1, SHOWING_EDITOR=2, WILL_SHOW_EDITOR=3;
130     
131     /* 
132      * The database shown in this panel.
133      */
134     BibtexDatabase database;
135     
136     private int mode=0;
137     private EntryEditor currentEditor = null;
138     private PreviewPanel currentPreview = null;
139
140     boolean tmp = true;
141
142     private MainTableSelectionListener selectionListener = null;
143     private ListEventListener<BibtexEntry> groupsHighlightListener;
144     UIFSplitPane contentPane = new UIFSplitPane();
145
146     JSplitPane splitPane;
147
148     JabRefFrame frame;
149     
150     String fileMonitorHandle = null;
151     boolean saving = false, updatedExternally = false;
152     private String encoding;
153
154     GridBagLayout gbl = new GridBagLayout();
155     GridBagConstraints con = new GridBagConstraints();
156
157     // Hashtable indexing the only search auto completer
158     // required for the SearchAutoCompleterUpdater
159     HashMap<String, AbstractAutoCompleter> searchAutoCompleterHM = new HashMap<String, AbstractAutoCompleter>();
160
161     HashMap<String, AbstractAutoCompleter> autoCompleters = new HashMap<String, AbstractAutoCompleter>();
162     // Hashtable that holds as keys the names of the fields where
163     // autocomplete is active, and references to the autocompleter objects.
164
165     NameFieldAutoCompleter searchCompleter = null;
166     AutoCompleteListener searchCompleteListener = null;
167
168     // The undo manager.
169     public CountingUndoManager undoManager = new CountingUndoManager(this);
170     UndoAction undoAction = new UndoAction();
171     RedoAction redoAction = new RedoAction();
172     
173     private List<BibtexEntry> previousEntries = new ArrayList<BibtexEntry>(),
174         nextEntries = new ArrayList<BibtexEntry>();
175
176     //ExampleFileFilter fileFilter;
177     // File filter for .bib files.
178
179     private boolean baseChanged = false;
180     private boolean nonUndoableChange = false;
181     // Used to track whether the base has changed since last save.
182
183     //EntryTableModel tableModel = null;
184     //public EntryTable entryTable = null;
185     public MainTable mainTable = null;
186     public MainTableFormat tableFormat = null;
187     public FilterList<BibtexEntry> searchFilterList = null, groupFilterList = null;
188
189     public RightClickMenu rcm;
190
191     BibtexEntry showing = null;
192
193     // Variable to prevent erroneous update of back/forward histories at the time
194     // when a Back or Forward operation is being processed:
195     private boolean backOrForwardInProgress = false;
196
197     // To indicate which entry is currently shown.
198     public HashMap<String, EntryEditor> entryEditors = new HashMap<String, EntryEditor>();
199     // To contain instantiated entry editors. This is to save time
200     // in switching between entries.
201
202     //HashMap entryTypeForms = new HashMap();
203     // Hashmap to keep track of which entries currently have open
204     // EntryTypeForm dialogs.
205
206     PreambleEditor preambleEditor = null;
207     // Keeps track of the preamble dialog if it is open.
208
209     StringDialog stringDialog = null;
210     // Keeps track of the string dialog if it is open.
211
212     SaveDatabaseAction saveAction;
213     CleanUpAction cleanUpAction;
214     
215     /**
216      * The group selector component for this database. Instantiated by the
217      * SidePaneManager if necessary, or from this class if merging groups from a
218      * different database.
219      */
220     //GroupSelector groupSelector;
221
222     public boolean
223             showingSearch = false,
224             showingGroup = false,
225             sortingBySearchResults = false,
226             coloringBySearchResults = false,
227             hidingNonHits = false,
228             sortingByGroup = false,
229             sortingByCiteSeerResults = false,
230             coloringByGroup = false;
231
232     int lastSearchHits = -1; // The number of hits in the latest search.
233     // Potential use in hiding non-hits completely.
234
235     // MetaData parses, keeps and writes meta data.
236     MetaData metaData;
237
238     private HashMap<String, Object> actions = new HashMap<String, Object>();
239     private SidePaneManager sidePaneManager;
240
241     /**
242      * Create a new BasePanel with an empty database.
243      * @param frame The application window.
244      */
245     public BasePanel(JabRefFrame frame) {
246       this.sidePaneManager = Globals.sidePaneManager;
247       database = new BibtexDatabase();
248       metaData = new MetaData();
249         metaData.initializeNewDatabase();
250       this.frame = frame;
251       setupActions();
252       setupMainPanel();
253         encoding = Globals.prefs.get("defaultEncoding");
254         //System.out.println("Default: "+encoding);
255     }
256
257     public BasePanel(JabRefFrame frame, BibtexDatabase db, File file,
258                      MetaData metaData, String encoding) {
259         init(frame, db, file, metaData, encoding);
260     }
261
262     private void init(JabRefFrame frame, BibtexDatabase db, File file,
263                       MetaData metaData, String encoding) {
264         assert(frame != null);
265         assert(db != null);
266         //file may be null
267         assert(encoding != null);
268         assert(metaData != null);
269
270         this.encoding = encoding;
271         this.metaData = metaData;
272         // System.out.println(encoding);
273         //super(JSplitPane.HORIZONTAL_SPLIT, true);
274         this.sidePaneManager = Globals.sidePaneManager;
275         this.frame = frame;
276         database = db;
277
278         setupActions();
279         setupMainPanel();
280
281         metaData.setFile(file);
282
283         // ensure that at each addition of a new entry, the entry is added to the groups interface
284         db.addDatabaseChangeListener(new GroupTreeUpdater());
285
286         if (file == null) {
287             if (!database.getEntries().isEmpty()) {
288                 // if the database is not empty and no file is assigned,
289                 // the database came from an import and has to be treated somehow
290                 // -> mark as changed
291                 this.baseChanged = true;
292             }
293         } else {
294             // Register so we get notifications about outside changes to the file.
295             try {
296                 fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(this,
297                         file);
298             } catch (IOException ex) {
299                 logger.warning(ex.toString());
300             }
301         }
302         
303     }
304
305     public boolean isBaseChanged(){
306         return baseChanged;
307     }
308     
309     public int getMode() {
310         return mode;
311     }
312
313     //Done by MrDlib
314     public void setMode(int mode) {
315         this.mode = mode;
316     }
317     //Done by MrDlib
318
319     public BibtexDatabase database() {
320                 return database;
321         }
322
323         public MetaData metaData() {
324                 return metaData;
325         }
326
327         public JabRefFrame frame() {
328                 return frame;
329         }
330
331         public JabRefPreferences prefs() {
332                 return Globals.prefs;
333         }
334
335         public String getEncoding() {
336                 return encoding;
337         }
338
339         public void setEncoding(String encoding) {
340                 this.encoding = encoding;
341         }
342
343     public void output(String s) {
344         boolean suppressOutput = false;
345         if (!suppressOutput)
346             frame.output(s);
347     }
348
349     private void setupActions() {
350         saveAction = new SaveDatabaseAction(this);
351         cleanUpAction = new CleanUpAction(this);
352         
353         actions.put("undo", undoAction);
354         actions.put("redo", redoAction);
355
356         actions.put("focusTable", new BaseAction() {
357             public void action() throws Throwable {
358                 new FocusRequester(mainTable);
359             }
360         });
361         
362         // The action for opening an entry editor.
363         actions.put("edit", new BaseAction() {
364             public void action() {
365                 /*System.out.println(Globals.focusListener.getFocused().getClass().getName());
366                 if (Globals.focusListener.getFocused() instanceof FieldEditor)
367                     new FocusRequester(mainTable);
368                 else*/
369                     selectionListener.editSignalled();
370             }
371                 /*
372                   if (isShowingEditor()) {
373                       new FocusRequester(splitPane.getBottomComponent());
374                       return;
375                   }
376
377                   frame.block();
378                 //(new Thread() {
379                 //public void run() {
380                 int clickedOn = -1;
381                 // We demand that one and only one row is selected.
382                 if (entryTable.getSelectedRowCount() == 1) {
383                   clickedOn = entryTable.getSelectedRow();
384                 }
385                 if (clickedOn >= 0) {
386                   String id = tableModel.getIdForRow(clickedOn);
387                   BibtexEntry be = database.getEntryById(id);
388                   showEntry(be);
389
390                   if (splitPane.getBottomComponent() != null) {
391                       new FocusRequester(splitPane.getBottomComponent());
392                   }
393
394                 }
395         frame.unblock();
396               }
397                 */
398             });
399
400
401         actions.put("test",// new AccessLinksForEntries.SaveWithLinkedFiles(this));
402                 new FindFullTextAction(this));
403
404
405         // The action for saving a database.
406         actions.put("save", saveAction);
407
408         actions.put("saveAs", new BaseAction() {
409             public void action() throws Throwable {
410                 saveAction.saveAs();
411             }
412         });
413
414         actions.put("saveSelectedAs", new BaseAction () {
415                 public void action() throws Throwable {
416
417                   String chosenFile = FileDialogs.getNewFile(frame, new File(Globals.prefs.get("workingDirectory")), ".bib",
418                                                          JFileChooser.SAVE_DIALOG, false);
419                   if (chosenFile != null) {
420                     File expFile = new File(chosenFile);
421                     if (!expFile.exists() ||
422                         (JOptionPane.showConfirmDialog
423                          (frame, "'"+expFile.getName()+"' "+
424                           Globals.lang("exists. Overwrite file?"),
425                           Globals.lang("Save database"), JOptionPane.OK_CANCEL_OPTION)
426                          == JOptionPane.OK_OPTION)) {
427
428                       saveDatabase(expFile, true, Globals.prefs.get("defaultEncoding"));
429                       //runCommand("save");
430                       frame.getFileHistory().newFile(expFile.getPath());
431                       frame.output(Globals.lang("Saved selected to")+" '"
432                                    +expFile.getPath()+"'.");
433                         }
434                     }
435                 }
436             });
437     
438         // The action for copying selected entries.
439         actions.put("copy", new BaseAction() {
440                 public void action() {
441                     BibtexEntry[] bes = mainTable.getSelectedEntries();
442
443                     if ((bes != null) && (bes.length > 0)) {
444                         TransferableBibtexEntry trbe
445                             = new TransferableBibtexEntry(bes);
446                         // ! look at ClipBoardManager
447                         Toolkit.getDefaultToolkit().getSystemClipboard()
448                             .setContents(trbe, BasePanel.this);
449                         output(Globals.lang("Copied")+" "+(bes.length>1 ? bes.length+" "
450                                                            +Globals.lang("entries")
451                                                            : "1 "+Globals.lang("entry")+"."));
452                     } else {
453                         // The user maybe selected a single cell.
454                         int[] rows = mainTable.getSelectedRows(),
455                             cols = mainTable.getSelectedColumns();
456                         if ((cols.length == 1) && (rows.length == 1)) {
457                             // Copy single value.
458                             Object o = mainTable.getValueAt(rows[0], cols[0]);
459                             if (o != null) {
460                                 StringSelection ss = new StringSelection(o.toString());
461                                 Toolkit.getDefaultToolkit().getSystemClipboard()
462                                     .setContents(ss, BasePanel.this);
463
464                                 output(Globals.lang("Copied cell contents")+".");
465                             }
466                         }
467                     }
468                 }
469             });
470
471         actions.put("cut", new BaseAction() {
472                 public void action() throws Throwable {
473                     runCommand("copy");
474                     BibtexEntry[] bes = mainTable.getSelectedEntries();
475                     //int row0 = mainTable.getSelectedRow();
476                     if ((bes != null) && (bes.length > 0)) {
477                         // Create a CompoundEdit to make the action undoable.
478                         NamedCompound ce = new NamedCompound
479                         (Globals.lang(bes.length > 1 ? "cut entries" : "cut entry"));
480                         // Loop through the array of entries, and delete them.
481                         for (BibtexEntry be : bes) {
482                             database.removeEntry(be.getId());
483                             ensureNotShowing(be);
484                             ce.addEdit(new UndoableRemoveEntry
485                                     (database, be, BasePanel.this));
486                         }
487                         //entryTable.clearSelection();
488                         frame.output(Globals.lang("Cut_pr")+" "+
489                                      (bes.length>1 ? bes.length
490                                       +" "+ Globals.lang("entries")
491                                       : Globals.lang("entry"))+".");
492                         ce.end();
493                         undoManager.addEdit(ce);
494                         markBaseChanged();
495
496                         // Reselect the entry in the first prev. selected position:
497                         /*if (row0 >= entryTable.getRowCount())
498                             row0 = entryTable.getRowCount()-1;
499                         if (row0 >= 0)
500                             entryTable.addRowSelectionInterval(row0, row0);*/
501                     }
502                 }
503             });
504
505         actions.put("delete", new BaseAction() {
506                 public void action() {
507                   BibtexEntry[] bes = mainTable.getSelectedEntries();
508                   if ((bes != null) && (bes.length > 0)) {
509
510                       boolean goOn = showDeleteConfirmationDialog(bes.length);
511                       if (goOn) {
512                           // Create a CompoundEdit to make the action undoable.
513                           NamedCompound ce = new NamedCompound
514                               (Globals.lang(bes.length > 1 ? "delete entries" : "delete entry"));
515                           // Loop through the array of entries, and delete them.
516                           for (BibtexEntry be : bes) {
517                               database.removeEntry(be.getId());
518                               ensureNotShowing(be);
519                               ce.addEdit(new UndoableRemoveEntry(database, be, BasePanel.this));
520                           }
521                           markBaseChanged();
522                           frame.output(Globals.lang("Deleted") + " " +
523                                        (bes.length > 1 ? bes.length
524                                         + " " + Globals.lang("entries")
525                                         : Globals.lang("entry")) + ".");
526                           ce.end();
527                           undoManager.addEdit(ce);
528                           //entryTable.clearSelection();
529                       }
530
531
532                       // Reselect the entry in the first prev. selected position:
533                           /*if (row0 >= entryTable.getRowCount())
534                               row0 = entryTable.getRowCount()-1;
535                           if (row0 >= 0) {
536                              final int toSel = row0;
537                             //
538                               SwingUtilities.invokeLater(new Runnable() {
539                                 public void run() {
540                                     entryTable.addRowSelectionInterval(toSel, toSel);
541                                     //entryTable.ensureVisible(toSel);
542                                 }
543                               });
544                             */
545                           }
546
547                       }
548
549             });
550
551         // The action for pasting entries or cell contents.
552         // Edited by Seb Wills <saw27@mrao.cam.ac.uk> on 14-Apr-04:
553         //  - more robust detection of available content flavors (doesn't only look at first one offered)
554         //  - support for parsing string-flavor clipboard contents which are bibtex entries.
555         //    This allows you to (a) paste entire bibtex entries from a text editor, web browser, etc
556         //                       (b) copy and paste entries between multiple instances of JabRef (since
557         //         only the text representation seems to get as far as the X clipboard, at least on my system)
558         actions.put("paste", new BaseAction() {
559                 public void action() {
560                     // Get clipboard contents, and see if TransferableBibtexEntry is among the content flavors offered
561                     Transferable content = Toolkit.getDefaultToolkit()
562                         .getSystemClipboard().getContents(null);
563                     if (content != null) {
564                         BibtexEntry[] bes = null;
565                         if (content.isDataFlavorSupported(TransferableBibtexEntry.entryFlavor)) {
566                             // We have determined that the clipboard data is a set of entries.
567                             try {
568                                 bes = (BibtexEntry[])(content.getTransferData(TransferableBibtexEntry.entryFlavor));
569
570                             } catch (UnsupportedFlavorException ex) {
571                                 ex.printStackTrace();
572                             } catch (IOException ex) {
573                                 ex.printStackTrace();
574                             }
575                         } else if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
576                            try {
577                                   BibtexParser bp = new BibtexParser
578                                       (new java.io.StringReader( (String) (content.getTransferData(
579                                       DataFlavor.stringFlavor))));
580                                   BibtexDatabase db = bp.parse().getDatabase();
581                                   Util.pr("Parsed " + db.getEntryCount() + " entries from clipboard text");
582                                   if(db.getEntryCount()>0) {
583                                       bes = db.getEntries().toArray(new BibtexEntry[db.getEntryCount()]);
584                                   }
585                               } catch (UnsupportedFlavorException ex) {
586                                   ex.printStackTrace();
587                               } catch (Throwable ex) {
588                                   ex.printStackTrace();
589                               }
590
591                         }
592
593                         // finally we paste in the entries (if any), which either came from TransferableBibtexEntries
594                         // or were parsed from a string
595                         if ((bes != null) && (bes.length > 0)) {
596
597                           NamedCompound ce = new NamedCompound
598                               (Globals.lang(bes.length > 1 ? "paste entries" : "paste entry"));
599                           
600                           // Store the first inserted bibtexentry.
601                           // bes[0] does not work as bes[0] is first clonded,
602                           // then inserted.
603                           // This entry is used to open up an entry editor
604                           // for the first inserted entry.
605                           BibtexEntry firstBE = null;
606
607                             for (BibtexEntry be1 : bes) {
608                                 try {
609                                     BibtexEntry be = (BibtexEntry) (be1.clone());
610                                     if (firstBE == null) firstBE = be;
611                                     Util.setAutomaticFields(be,
612                                             Globals.prefs.getBoolean("overwriteOwner"),
613                                             Globals.prefs.getBoolean("overwriteTimeStamp"));
614
615                                     // We have to clone the
616                                     // entries, since the pasted
617                                     // entries must exist
618                                     // independently of the copied
619                                     // ones.
620                                     be.setId(Util.createNeutralId());
621                                     database.insertEntry(be);
622
623                                     ce.addEdit(new UndoableInsertEntry
624                                             (database, be, BasePanel.this));
625                                 } catch (KeyCollisionException ex) {
626                                     Util.pr("KeyCollisionException... this shouldn't happen.");
627                                 }
628                             }
629                           ce.end();
630                           undoManager.addEdit(ce);
631                           //entryTable.clearSelection();
632                           //entryTable.revalidate();
633                           output(Globals.lang("Pasted") + " " +
634                                   (bes.length > 1 ? bes.length + " " +
635                                           Globals.lang("entries") : "1 " + Globals.lang("entry"))
636                                   + ".");
637                           markBaseChanged();
638                                   
639                           if (Globals.prefs.getBoolean("autoOpenForm")) {
640                                   selectionListener.editSignalled(firstBE);
641                           }
642                           highlightEntry(firstBE);
643                         }
644                       }
645
646                     }
647
648 });
649
650         actions.put("selectAll", new BaseAction() {
651                 public void action() {
652                     mainTable.selectAll();
653                 }
654             });
655
656         // The action for opening the preamble editor
657         actions.put("editPreamble", new BaseAction() {
658                 public void action() {
659                     if (preambleEditor == null) {
660                         PreambleEditor form = new PreambleEditor
661                             (frame, BasePanel.this, database, Globals.prefs);
662                         Util.placeDialog(form, frame);
663                         form.setVisible(true);
664                         preambleEditor = form;
665                     } else {
666                         preambleEditor.setVisible(true);
667                     }
668
669                 }
670             });
671
672         // The action for opening the string editor
673         actions.put("editStrings", new BaseAction() {
674                 public void action() {
675                     if (stringDialog == null) {
676                         StringDialog form = new StringDialog
677                             (frame, BasePanel.this, database, Globals.prefs);
678                         Util.placeDialog(form, frame);
679                         form.setVisible(true);
680                         stringDialog = form;
681                     } else {
682                         stringDialog.setVisible(true);
683                     }
684
685                 }
686             });
687
688         // The action for toggling the groups interface
689         actions.put("toggleGroups", new BaseAction() {
690             public void action() {
691               sidePaneManager.toggle("groups");
692               frame.groupToggle.setSelected(sidePaneManager.isComponentVisible("groups"));
693             }
694         });
695
696         // The action for toggling the visibility of the toolbar
697         actions.put("toggleToolbar", new BaseAction() {
698             public void action() {
699                 frame.tlb.setVisible(! frame.tlb.isVisible());
700             }
701         });
702
703         // action for collecting database strings from user
704         actions.put("dbConnect", new DbConnectAction(this));
705
706
707         // action for exporting database to external SQL database
708         actions.put("dbExport", new AbstractWorker () {
709
710             String errorMessage = null;
711             boolean connectToDB = false;
712
713             // run first, in EDT:
714             public void init() {
715
716                 DBStrings dbs = metaData.getDBStrings();
717
718                 // get DBStrings from user if necessary
719                 if (!dbs.isConfigValid()) {
720
721                     // init DB strings if necessary
722                     if (! dbs.isInitialized()) {
723                         dbs.initialize();
724                     }
725
726                     // show connection dialog
727                     DBConnectDialog dbd = new DBConnectDialog(frame(), dbs);
728                     Util.placeDialog(dbd, BasePanel.this );
729                     dbd.setVisible(true);
730
731                     connectToDB = dbd.getConnectToDB();
732
733                     // store database strings
734                     if (connectToDB) {
735                         dbs = dbd.getDBStrings();
736                         metaData.setDBStrings(dbs);
737                         dbd.dispose();
738                     }
739
740                 } else {
741
742                     connectToDB  = true;
743
744                 }
745
746             }
747
748             // run second, on a different thread:
749             public void run() {
750
751                 if (connectToDB) {
752
753                     DBStrings dbs = metaData.getDBStrings();
754
755                     try {
756                         /*boolean okToExport = null!=metaData.getFile();
757                         if (!okToExport)
758                         {
759                                 okToExport = false;
760                                 int response = JOptionPane.showConfirmDialog(null, "You need to save your database in the disk \n" +
761                                                 "before saving. Save it now?", "Database is not saved",
762                                         JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
763                                         if(response == JOptionPane.YES_OPTION)
764                                         {
765                                                 try {
766                                                         saveAction.saveAs();
767                                                         okToExport = (null!=metaData.getFile());
768                                                 } catch (Throwable e) {
769                                                 e.printStackTrace();
770                                         }
771                                 }
772                         }
773                         if (okToExport)
774                         {*/
775                                 frame.output(Globals.lang("Attempting SQL export..."));
776                                 DBExporterAndImporterFactory factory = new DBExporterAndImporterFactory();
777                                 DBExporter exporter = factory.getExporter(dbs.getServerType());
778                                 exporter.exportDatabaseToDBMS(database, metaData, null, dbs, frame);
779                                 dbs.isConfigValid(true);
780                         //}
781                         //else
782                         //      errorMessage = "Database was not exported. Your database must be saved \nbefore exporting to a SQL database";
783                     } catch (Exception ex) {
784                         String preamble = "Could not export to SQL database for the following reason:";
785                         errorMessage = SQLUtil.getExceptionMessage(ex);
786                         ex.printStackTrace();
787                         dbs.isConfigValid(false);
788                         JOptionPane.showMessageDialog(frame, Globals.lang(preamble)
789                                 + "\n" +errorMessage, Globals.lang("Export to SQL database"),
790                                 JOptionPane.ERROR_MESSAGE);
791                     }
792
793                     metaData.setDBStrings(dbs);
794
795                 }
796
797             }
798
799             // run third, on EDT:
800             public void update() {
801
802                 // if no error, report success
803                 if (errorMessage == null) {
804                     if (connectToDB) {
805                         frame.output(Globals.lang("%0 export successful"));
806                     }
807                 }
808
809                 // show an error dialog if an error occurred
810                 else {
811
812                     String preamble = "Could not export to SQL database for the following reason:";
813                     frame.output(Globals.lang(preamble)
814                             + "  " + errorMessage);
815
816                     JOptionPane.showMessageDialog(frame, Globals.lang(preamble)
817                         + "\n" + errorMessage, Globals.lang("Export to SQL database"),
818                         JOptionPane.ERROR_MESSAGE);
819
820                     errorMessage = null;
821
822                 }
823             }
824
825         });
826                 
827         actions.put(FindUnlinkedFilesDialog.ACTION_COMMAND, new BaseAction() {
828                 @Override
829                 public void action() throws Throwable {
830                                         FindUnlinkedFilesDialog dialog = new FindUnlinkedFilesDialog(frame, frame, BasePanel.this);
831                                         Util.placeDialog(dialog, frame);
832                                         dialog.setVisible(true);
833                                 }
834           });
835
836         // The action for auto-generating keys.
837         actions.put("makeKey", new AbstractWorker() {
838         //int[] rows;
839         List<BibtexEntry> entries;
840         int numSelected;
841         boolean cancelled = false;
842
843         // Run first, in EDT:
844         public void init() {
845                     entries = new ArrayList<BibtexEntry>(Arrays.asList(getSelectedEntries()));
846                    //rows = entryTable.getSelectedRows() ;
847                     numSelected = entries.size();
848
849                     if (entries.size() == 0) { // None selected. Inform the user to select entries first.
850                         JOptionPane.showMessageDialog(frame, Globals.lang("First select the entries you want keys to be generated for."),
851                                                       Globals.lang("Autogenerate BibTeX key"), JOptionPane.INFORMATION_MESSAGE);
852                         return ;
853                     }
854             frame.block();
855             output(Globals.lang("Generating BibTeX key for")+" "+
856                            numSelected+" "+(numSelected>1 ? Globals.lang("entries")
857                                             : Globals.lang("entry"))+"...");
858         }
859
860
861         // Run second, on a different thread:
862                 public void run() {
863                     BibtexEntry bes = null ;
864                     NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
865
866                     // First check if any entries have keys set already. If so, possibly remove
867                     // them from consideration, or warn about overwriting keys.
868                     for (Iterator<BibtexEntry> i = entries.iterator(); i.hasNext(); ) {
869                         bes = i.next();
870                         if (bes.getField(BibtexFields.KEY_FIELD) != null) {
871                             if (Globals.prefs.getBoolean("avoidOverwritingKey"))
872                                 // Remove the entry, because its key is already set:
873                                 i.remove();
874                             else if (Globals.prefs.getBoolean("warnBeforeOverwritingKey")) {
875                                 // Ask if the user wants to cancel the operation:
876                                 CheckBoxMessage cbm = new CheckBoxMessage(Globals.lang("One or more keys will be overwritten. Continue?"),
877                                         Globals.lang("Disable this confirmation dialog"), false);
878                                 int answer = JOptionPane.showConfirmDialog(frame, cbm, Globals.lang("Overwrite keys"),
879                                         JOptionPane.YES_NO_OPTION);
880                                 if (cbm.isSelected())
881                                     Globals.prefs.putBoolean("warnBeforeOverwritingKey", false);
882                                 if (answer == JOptionPane.NO_OPTION) {
883                                     // Ok, break off the operation.
884                                     cancelled = true;
885                                     return;
886                                 }
887                                 // No need to check more entries, because the user has already confirmed
888                                 // that it's ok to overwrite keys:
889                                 break;
890                             }
891                         }
892                     }
893
894                     HashMap<BibtexEntry, Object> oldvals = new HashMap<BibtexEntry, Object>();
895                     // Iterate again, removing already set keys. This is skipped if overwriting
896                     // is disabled, since all entries with keys set will have been removed.
897                     if (!Globals.prefs.getBoolean("avoidOverwritingKey")) for (BibtexEntry entry : entries) {
898                         bes = entry;
899                         // Store the old value:
900                         oldvals.put(bes, bes.getField(BibtexFields.KEY_FIELD));
901                         database.setCiteKeyForEntry(bes.getId(), null);
902                     }
903
904                     // Finally, set the new keys:
905                     for (BibtexEntry entry : entries) {
906                         bes = entry;
907                         bes = LabelPatternUtil.makeLabel(metaData, database, bes);
908                         ce.addEdit(new UndoableKeyChange
909                                 (database, bes.getId(), (String) oldvals.get(bes),
910                                         bes.getField(BibtexFields.KEY_FIELD)));
911                     }
912                     ce.end();
913                     undoManager.addEdit(ce);
914         }
915
916         // Run third, on EDT:
917         public void update() {
918             database.setFollowCrossrefs(true);
919             if (cancelled) {
920                 frame.unblock();
921                 return;
922             }
923             markBaseChanged() ;
924             numSelected = entries.size();
925
926 ////////////////////////////////////////////////////////////////////////////////
927 //          Prevent selection loss for autogenerated BibTeX-Keys
928 ////////////////////////////////////////////////////////////////////////////////
929             for (final BibtexEntry bibEntry : entries) {
930                 SwingUtilities.invokeLater(new Runnable() {
931                     public void run() {
932                         final int row = mainTable.findEntry(bibEntry);
933                         if (row >= 0 && mainTable.getSelectedRowCount() < entries.size())
934                             mainTable.addRowSelectionInterval(row, row);
935                     }
936                 });
937             }
938 ////////////////////////////////////////////////////////////////////////////////
939             output(Globals.lang("Generated BibTeX key for")+" "+
940                numSelected+" "+(numSelected!=1 ? Globals.lang("entries")
941                                     : Globals.lang("entry")));
942             frame.unblock();
943         }
944     });
945          
946         
947      // The action for cleaning up entry.
948         actions.put("Cleanup", cleanUpAction);
949         
950         actions.put("mergeEntries", new BaseAction() {
951             public void action() {
952                 new MergeEntriesDialog(BasePanel.this);
953             }
954         });
955
956         actions.put("search", new BaseAction() {
957                 public void action() {
958                     //sidePaneManager.togglePanel("search");
959                     sidePaneManager.show("search");
960                     //boolean on = sidePaneManager.isPanelVisible("search");
961                     frame.searchToggle.setSelected(true);
962                     frame.getSearchManager().startSearch();
963                 }
964             });
965         
966         actions.put("toggleSearch", new BaseAction() {
967                 public void action() {
968                     //sidePaneManager.togglePanel("search");
969                     sidePaneManager.toggle("search");
970                     boolean on = sidePaneManager.isComponentVisible("search");
971                     frame.searchToggle.setSelected(on);
972                     if (on)
973                       frame.getSearchManager().startSearch();
974                 }
975             });
976
977         actions.put("incSearch", new BaseAction() {
978                 public void action() {
979                     sidePaneManager.show("search");
980                     frame.searchToggle.setSelected(true);
981                     frame.getSearchManager().startIncrementalSearch();
982                 }
983             });
984
985         // The action for copying the selected entry's key.
986         actions.put("copyKey", new BaseAction() {
987                 public void action() {
988                     BibtexEntry[] bes = mainTable.getSelectedEntries();
989                     if ((bes != null) && (bes.length > 0)) {
990                         storeCurrentEdit();
991                         //String[] keys = new String[bes.length];
992                         Vector<Object> keys = new Vector<Object>();
993                         // Collect all non-null keys.
994                         for (BibtexEntry be : bes)
995                             if (be.getField(BibtexFields.KEY_FIELD) != null)
996                                 keys.add(be.getField(BibtexFields.KEY_FIELD));
997                         if (keys.size() == 0) {
998                             output("None of the selected entries have BibTeX keys.");
999                             return;
1000                         }
1001                         StringBuilder sb = new StringBuilder((String)keys.elementAt(0));
1002                         for (int i=1; i<keys.size(); i++) {
1003                             sb.append(',');
1004                             sb.append((String)keys.elementAt(i));
1005                         }
1006
1007                         StringSelection ss = new StringSelection(sb.toString());
1008                         Toolkit.getDefaultToolkit().getSystemClipboard()
1009                             .setContents(ss, BasePanel.this);
1010
1011                         if (keys.size() == bes.length)
1012                             // All entries had keys.
1013                             output(Globals.lang((bes.length > 1) ? "Copied keys"
1014                                                 : "Copied key")+".");
1015                         else
1016                             output(Globals.lang("Warning")+": "+(bes.length-keys.size())
1017                                    +" "+Globals.lang("out of")+" "+bes.length+" "+
1018                                    Globals.lang("entries have undefined BibTeX key")+".");
1019                     }
1020                 }
1021             });
1022
1023         // The action for copying a cite for the selected entry.
1024         actions.put("copyCiteKey", new BaseAction() {
1025                 public void action() {
1026                     BibtexEntry[] bes = mainTable.getSelectedEntries();
1027                     if ((bes != null) && (bes.length > 0)) {
1028                         storeCurrentEdit();
1029                         //String[] keys = new String[bes.length];
1030                         Vector<Object> keys = new Vector<Object>();
1031                         // Collect all non-null keys.
1032                         for (BibtexEntry be : bes)
1033                             if (be.getField(BibtexFields.KEY_FIELD) != null)
1034                                 keys.add(be.getField(BibtexFields.KEY_FIELD));
1035                         if (keys.size() == 0) {
1036                             output("None of the selected entries have BibTeX keys.");
1037                             return;
1038                         }
1039                         StringBuilder sb = new StringBuilder((String)keys.elementAt(0));
1040                         for (int i=1; i<keys.size(); i++) {
1041                             sb.append(',');
1042                             sb.append((String)keys.elementAt(i));
1043                         }
1044
1045                         StringSelection ss = new StringSelection
1046                             ("\\cite{"+sb.toString()+"}");
1047                         Toolkit.getDefaultToolkit().getSystemClipboard()
1048                             .setContents(ss, BasePanel.this);
1049
1050                         if (keys.size() == bes.length)
1051                             // All entries had keys.
1052                             output(bes.length > 1 ? Globals.lang("Copied keys")
1053                                                   : Globals.lang("Copied key")+".");
1054                         else
1055                             output(Globals.lang("Warning")+": "+(bes.length-keys.size())
1056                                    +" "+Globals.lang("out of")+" "+bes.length+" "+
1057                                    Globals.lang("entries have undefined BibTeX key")+".");
1058                     }
1059                 }
1060             });
1061         
1062           // The action for copying the BibTeX key and the title for the first selected entry
1063           actions.put("copyKeyAndTitle", new BaseAction() {
1064                   public void action() {
1065                   BibtexEntry[] bes = mainTable.getSelectedEntries();
1066                   if ((bes != null) && (bes.length > 0)) {
1067                       storeCurrentEdit();
1068
1069                       // OK: in a future version, this string should be configurable to allow arbitrary exports
1070                           StringReader sr = new StringReader("\\bibtexkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n");
1071                           Layout layout;
1072                                           try {
1073                                                   layout = new LayoutHelper(sr).getLayoutFromText(Globals.FORMATTER_PACKAGE);
1074                                           } catch (Exception e) {
1075                                                   e.printStackTrace();
1076                                                   return;
1077                                           }
1078                                           
1079                           StringBuilder sb = new StringBuilder();
1080
1081                           int copied = 0;
1082                       // Collect all non-null keys.
1083                       for (BibtexEntry be : bes)
1084                           if (be.getField(BibtexFields.KEY_FIELD) != null) {
1085                               copied++;
1086                               sb.append(layout.doLayout(be, database));
1087                           }
1088                       
1089                       if (copied==0) {
1090                           output("None of the selected entries have BibTeX keys.");
1091                           return;
1092                       }
1093
1094                       StringSelection ss = new StringSelection(sb.toString());
1095                       Toolkit.getDefaultToolkit().getSystemClipboard()
1096                           .setContents(ss, BasePanel.this);
1097
1098                       if (copied == bes.length)
1099                           // All entries had keys.
1100                           output(Globals.lang((bes.length > 1) ? "Copied keys"
1101                                               : "Copied key")+".");
1102                       else
1103                           output(Globals.lang("Warning")+": "+(copied)
1104                                  +" "+Globals.lang("out of")+" "+bes.length+" "+
1105                                  Globals.lang("entries have undefined BibTeX key")+".");
1106                   }
1107                   }
1108           });
1109
1110           actions.put("mergeDatabase", new AppendDatabaseAction(frame, this));
1111
1112
1113         actions.put("openFile", new BaseAction() {
1114             public void action() {
1115                 (new Thread() {
1116                     public void run() {
1117                         BibtexEntry[] bes = mainTable.getSelectedEntries();
1118                         String field = "ps";
1119
1120                         if ((bes != null) && (bes.length == 1)) {
1121                             FileListEntry entry = null;
1122                             FileListTableModel tm = new FileListTableModel();
1123                             tm.setContent(bes[0].getField("file"));
1124                             for (int i=0; i< tm.getRowCount(); i++) {
1125                                 FileListEntry flEntry = tm.getEntry(i);
1126                                 if (flEntry.getType().getName().toLowerCase().equals("pdf")
1127                                     || flEntry.getType().getName().toLowerCase().equals("ps")) {
1128                                     entry = flEntry;
1129                                     break;
1130                                 }
1131                             }
1132                             if (entry != null) {
1133                                 try {
1134                                     Util.openExternalFileAnyFormat(metaData, entry.getLink(), entry.getType());
1135                                     output(Globals.lang("External viewer called") + ".");
1136                                 } catch (IOException e) {
1137                                     output(Globals.lang("Could not open link"));
1138                                     e.printStackTrace();
1139                                 }
1140                                 return;
1141                             }
1142                             // If we didn't find anything in the "file" field, check "ps" and "pdf" fields:
1143                             Object link = bes[0].getField("ps");
1144                             if (bes[0].getField("pdf") != null) {
1145                                 link = bes[0].getField("pdf");
1146                                 field = "pdf";
1147                             }
1148                             String filepath = null;
1149                             if (link != null) {
1150                                 filepath = link.toString();
1151                             } else {
1152                                 if (Globals.prefs.getBoolean("runAutomaticFileSearch")) {
1153
1154                                      /*  The search can lead to an unexpected 100% CPU usage which is perceived
1155                                          as a bug, if the search incidentally starts at a directory with lots
1156                                          of stuff below. It is now disabled by default. */
1157
1158                                     // see if we can fall back to a filename based on the bibtex key
1159                                     final Collection<BibtexEntry> entries = new ArrayList<BibtexEntry>();
1160                                     entries.add(bes[0]);
1161                                     ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
1162                                     ArrayList<File> dirs = new ArrayList<File>();
1163                                     if (metaData.getFileDirectory(GUIGlobals.FILE_FIELD).length > 0) {
1164                                         String[] mdDirs = metaData.getFileDirectory(GUIGlobals.FILE_FIELD);
1165                                         for (String mdDir : mdDirs) {
1166                                             dirs.add(new File(mdDir));
1167
1168                                         }
1169                                     }
1170                                     Collection<String> extensions = new ArrayList<String>();
1171                                     for (final ExternalFileType type : types) {
1172                                         extensions.add(type.getExtension());
1173                                     }
1174                                     // Run the search operation:
1175                                     Map<BibtexEntry, List<File>> result;
1176                                     if (Globals.prefs.getBoolean(JabRefPreferences.USE_REG_EXP_SEARCH_KEY)) {
1177                                         String regExp = Globals.prefs.get(JabRefPreferences.REG_EXP_SEARCH_EXPRESSION_KEY);
1178                                         result = RegExpFileSearch.findFilesForSet(entries, extensions, dirs, regExp);
1179                                     }
1180                                     else
1181                                         result = Util.findAssociatedFiles(entries, extensions, dirs);
1182                                     if (result.get(bes[0]) != null) {
1183                                         List<File> res = result.get(bes[0]);
1184                                         if (res.size() > 0) {
1185                                             filepath = res.get(0).getPath();
1186                                             int index = filepath.lastIndexOf('.');
1187                                             if ((index >= 0) && (index < filepath.length()-1)) {
1188                                                 String extension = filepath.substring(index+1);
1189                                                 ExternalFileType type = Globals.prefs.getExternalFileTypeByExt(extension);
1190                                                 if (type != null) {
1191                                                     try {
1192                                                         Util.openExternalFileAnyFormat(metaData, filepath, type);
1193                                                         output(Globals.lang("External viewer called") + ".");
1194                                                         return;
1195                                                     } catch (IOException ex) {
1196                                                         output(Globals.lang("Error") + ": " + ex.getMessage());
1197                                                     }
1198                                                 }
1199                                             }
1200
1201                                             // TODO: add code for opening the file
1202                                         }
1203                                     }
1204                                     /*String basefile;
1205                                     Object key = bes[0].getField(BibtexFields.KEY_FIELD);
1206                                     if (key != null) {
1207                                         basefile = key.toString();
1208                                         final ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
1209                                         final String sep = System.getProperty("file.separator");
1210                                         String dir = metaData.getFileDirectory(GUIGlobals.FILE_FIELD);
1211                                         if ((dir != null) && (dir.length() > 0)) {
1212                                             if (dir.endsWith(sep)) {
1213                                                 dir = dir.substring(0, dir.length() - sep.length());
1214                                             }
1215                                             for (int i = 0; i < types.length; i++) {
1216                                                 String found = Util.findPdf(basefile, types[i].getExtension(),
1217                                                         dir, new OpenFileFilter("." + types[i].getExtension()));
1218                                                 if (found != null) {
1219                                                     filepath = dir + sep + found;
1220                                                     break;
1221                                                 }
1222                                             }
1223                                         }
1224                                     }*/
1225                                 }
1226                             }
1227
1228
1229                             if (filepath != null) {
1230                                 //output(Globals.lang("Calling external viewer..."));
1231                                 try {
1232                                     Util.openExternalViewer(metaData(), filepath, field);
1233                                     output(Globals.lang("External viewer called") + ".");
1234                                 }
1235                                 catch (IOException ex) {
1236                                     output(Globals.lang("Error") + ": " + ex.getMessage());
1237                                 }
1238                             } else
1239                                 output(Globals.lang(
1240                                         "No pdf or ps defined, and no file matching Bibtex key found") +
1241                                         ".");
1242                         } else
1243                             output(Globals.lang("No entries or multiple entries selected."));
1244                     }
1245                 }).start();
1246             }
1247         });
1248
1249         actions.put("addFileLink", new AttachFileAction(this));
1250
1251         actions.put("openExternalFile", new BaseAction() {
1252             public void action() {
1253                 (new Thread() {
1254                     public void run() {
1255                         BibtexEntry[] bes = mainTable.getSelectedEntries();
1256                         String field = GUIGlobals.FILE_FIELD;
1257                         if ((bes != null) && (bes.length == 1)) {
1258                             Object link = bes[0].getField(field);
1259                             if (link == null) {
1260                                 runCommand("openFile"); // Fall back on PDF/PS fields???
1261                                 return;
1262                             }
1263                             FileListTableModel tableModel = new FileListTableModel();
1264                             tableModel.setContent((String)link);
1265                             if (tableModel.getRowCount() == 0) {
1266                                 runCommand("openFile"); // Fall back on PDF/PS fields???
1267                                 return;
1268                             }
1269                             FileListEntry flEntry = tableModel.getEntry(0);
1270                             ExternalFileMenuItem item = new ExternalFileMenuItem
1271                                 (frame(), bes[0], "",
1272                                 flEntry.getLink(), flEntry.getType().getIcon(),
1273                                 metaData(), flEntry.getType());
1274                             item.openLink();
1275                         } else
1276                             output(Globals.lang("No entries or multiple entries selected."));
1277                     }
1278                 }).start();
1279             }
1280         });
1281
1282         actions.put("openFolder", new BaseAction() {
1283             public void action() {
1284                 (new Thread() {
1285                     public void run() {
1286                         BibtexEntry[] bes = mainTable.getSelectedEntries();
1287                         List<File> files = Util.getListOfLinkedFiles(bes, metaData().getFileDirectory(GUIGlobals.FILE_FIELD));
1288                         for (File f: files) {
1289                             try {
1290                                 Util.openFolderAndSelectFile(f.getAbsolutePath());
1291                             } catch (IOException e) {
1292                                 logger.fine(e.getMessage());
1293                             }
1294                         }
1295                     }
1296                 }).start();
1297             }
1298         });
1299
1300         actions.put("openUrl", new BaseAction() {
1301             public void action() {
1302                 BibtexEntry[] bes = mainTable.getSelectedEntries();
1303                 String field = "doi";
1304                 if ((bes != null) && (bes.length == 1)) {
1305                     Object link = bes[0].getField("doi");
1306                     if (bes[0].getField("url") != null) {
1307                         link = bes[0].getField("url");
1308                         field = "url";
1309                     }
1310                     if (link != null) {
1311                         //output(Globals.lang("Calling external viewer..."));
1312                         try {
1313                             Util.openExternalViewer(metaData(), link.toString(), field);
1314                             output(Globals.lang("External viewer called")+".");
1315                         } catch (IOException ex) {
1316                             output(Globals.lang("Error") + ": " + ex.getMessage());
1317                         }
1318                     }
1319                     else {
1320                         // No URL or DOI found in the "url" and "doi" fields.
1321                         // Look for web links in the "file" field as a fallback:
1322                         FileListEntry entry = null;
1323                         FileListTableModel tm = new FileListTableModel();
1324                         tm.setContent(bes[0].getField("file"));
1325                         for (int i=0; i< tm.getRowCount(); i++) {
1326                             FileListEntry flEntry = tm.getEntry(i);
1327                             if (flEntry.getType().getName().toLowerCase().equals("url")
1328                                     || flEntry.getType().getName().toLowerCase().equals("ps")) {
1329                                 entry = flEntry;
1330                                 break;
1331                             }
1332                         }
1333                         if (entry != null) {
1334                             try {
1335                                 Util.openExternalFileAnyFormat(metaData, entry.getLink(), entry.getType());
1336                                 output(Globals.lang("External viewer called") + ".");
1337                             } catch (IOException e) {
1338                                 output(Globals.lang("Could not open link"));
1339                                 e.printStackTrace();
1340                             }
1341                         } else
1342                             output(Globals.lang("No url defined")+".");
1343                     }
1344                 } else
1345                     output(Globals.lang("No entries or multiple entries selected."));
1346             }
1347         });
1348
1349         actions.put("openSpires", new BaseAction() {
1350                 public void action() {
1351                         BibtexEntry[] bes = mainTable.getSelectedEntries();
1352                 if ((bes != null) && (bes.length == 1)) {
1353                         Object link = null;
1354                     if (bes[0].getField("eprint") != null)
1355                       link = SPIRESFetcher.constructUrlFromEprint(bes[0].getField("eprint"));
1356                     else if (bes[0].getField("slaccitation") != null)
1357                         link = SPIRESFetcher.constructUrlFromSlaccitation(bes[0].getField("slaccitation"));
1358                     if (link != null) {
1359                       //output(Globals.lang("Calling external viewer..."));
1360                       try {
1361                         Util.openExternalViewer(metaData(), link.toString(), "url");
1362                         output(Globals.lang("External viewer called")+".");
1363                       } catch (IOException ex) {
1364                           output(Globals.lang("Error") + ": " + ex.getMessage());
1365                       }
1366                     }
1367                     else
1368                         output(Globals.lang("No url defined")+".");
1369                 } else
1370                   output(Globals.lang("No entries or multiple entries selected."));
1371             }
1372                 });
1373
1374                 /*
1375                  *  It looks like this action was not being supported for SPIRES anyway
1376                  *  so we don't bother to implement it.
1377         actions.put("openInspire", new BaseAction() {
1378                 public void action() {
1379                         BibtexEntry[] bes = mainTable.getSelectedEntries();
1380                 if ((bes != null) && (bes.length == 1)) {
1381                         Object link = null;
1382                     if (bes[0].getField("eprint") != null)
1383                       link = INSPIREFetcher.constructUrlFromEprint(bes[0].getField("eprint").toString());
1384                     else if (bes[0].getField("slaccitation") != null)
1385                         link = INSPIREFetcher.constructUrlFromSlaccitation(bes[0].getField("slaccitation").toString());
1386                     if (link != null) {
1387                       //output(Globals.lang("Calling external viewer..."));
1388                       try {
1389                         Util.openExternalViewer(metaData(), link.toString(), "url");
1390                         output(Globals.lang("External viewer called")+".");
1391                       } catch (IOException ex) {
1392                           output(Globals.lang("Error") + ": " + ex.getMessage());
1393                       }
1394                     }
1395                     else
1396                         output(Globals.lang("No url defined")+".");
1397                 } else
1398                   output(Globals.lang("No entries or multiple entries selected."));
1399             }
1400                 });
1401                         */
1402
1403         
1404           actions.put("replaceAll", new BaseAction() {
1405                     public void action() {
1406                       ReplaceStringDialog rsd = new ReplaceStringDialog(frame);
1407                       rsd.setVisible(true);
1408                       if (!rsd.okPressed())
1409                           return;
1410                       int counter = 0;
1411                       NamedCompound ce = new NamedCompound(Globals.lang("Replace string"));
1412                       if (!rsd.selOnly()) {
1413                           for (BibtexEntry entry : database.getEntries()){
1414                               counter += rsd.replace(entry, ce);
1415                           }
1416                       } else {
1417                           BibtexEntry[] bes = mainTable.getSelectedEntries();
1418                           for (BibtexEntry be : bes) counter += rsd.replace(be, ce);
1419                       }
1420
1421                       output(Globals.lang("Replaced")+" "+counter+" "+
1422                              Globals.lang(counter==1?"occurence":"occurences")+".");
1423                       if (counter > 0) {
1424                           ce.end();
1425                           undoManager.addEdit(ce);
1426                           markBaseChanged();
1427                       }
1428                   }
1429               });
1430
1431               actions.put("dupliCheck", new BaseAction() {
1432                 public void action() {
1433                   DuplicateSearch ds = new DuplicateSearch(BasePanel.this);
1434                   ds.start();
1435                 }
1436               });
1437
1438               /*actions.put("strictDupliCheck", new BaseAction() {
1439                 public void action() {
1440                   StrictDuplicateSearch ds = new StrictDuplicateSearch(BasePanel.this);
1441                   ds.start();
1442                 }
1443               });*/
1444
1445               actions.put("plainTextImport", new BaseAction() {
1446                 public void action()
1447                 {
1448                   // get Type of new entry
1449                   EntryTypeDialog etd = new EntryTypeDialog(frame);
1450                   Util.placeDialog(etd, BasePanel.this);
1451                   etd.setVisible(true);
1452                   BibtexEntryType tp = etd.getChoice();
1453                   if (tp == null)
1454                     return;
1455
1456                   String id = Util.createNeutralId();
1457                   BibtexEntry bibEntry = new BibtexEntry(id, tp) ;
1458                   TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1459                                                                  "import", true,
1460                                                                  bibEntry) ;
1461                   Util.placeDialog(tidialog, BasePanel.this);
1462                   tidialog.setVisible(true);
1463
1464                   if (tidialog.okPressed())
1465                   {
1466                       Util.setAutomaticFields(Arrays.asList(bibEntry),
1467                               false, false, false);
1468                     insertEntry(bibEntry) ;
1469                   }
1470                 }
1471               });
1472
1473               // The action starts the "import from plain text" dialog
1474               /*actions.put("importPlainText", new BaseAction() {
1475                       public void action()
1476                       {
1477                         BibtexEntry bibEntry = null ;
1478                         // try to get the first marked entry
1479                         BibtexEntry[] bes = entryTable.getSelectedEntries();
1480                         if ((bes != null) && (bes.length > 0))
1481                           bibEntry = bes[0] ;
1482
1483                         if (bibEntry != null)
1484                         {
1485                           // Create an UndoableInsertEntry object.
1486                           undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1487
1488                           TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1489                                                                          "import", true,
1490                                                                          bibEntry) ;
1491                           Util.placeDialog(tidialog, BasePanel.this);
1492                           tidialog.setVisible(true);
1493
1494                           if (tidialog.okPressed())
1495                           {
1496                             output(Globals.lang("changed ")+" '"
1497                                    +bibEntry.getType().getName().toLowerCase()+"' "
1498                                    +Globals.lang("entry")+".");
1499                             refreshTable();
1500                             int row = tableModel.getNumberFromName(bibEntry.getId());
1501
1502                             entryTable.clearSelection();
1503                             entryTable.scrollTo(row);
1504                             markBaseChanged(); // The database just changed.
1505                             if (Globals.prefs.getBoolean("autoOpenForm"))
1506                             {
1507                                   showEntry(bibEntry);
1508                             }
1509                           }
1510                         }
1511                       }
1512                   });
1513                 */
1514               actions.put("markEntries", new AbstractWorker() {
1515                   private int besLength = -1;
1516                 public void run() {
1517
1518                   NamedCompound ce = new NamedCompound(Globals.lang("Mark entries"));
1519                   BibtexEntry[] bes = mainTable.getSelectedEntries();
1520                   besLength = bes.length;
1521
1522                     for (BibtexEntry be : bes) {
1523                         Util.markEntry(be, 1, true, ce);
1524                     }
1525                   ce.end();
1526                   undoManager.addEdit(ce);
1527                 }
1528
1529                 public void update() {
1530                   markBaseChanged();
1531                   output(Globals.lang("Marked selected")+" "+Globals.lang(besLength>0?"entry":"entries"));
1532
1533                 }
1534               });
1535
1536               actions.put("unmarkEntries", new BaseAction() {
1537                 public void action() {
1538                     try {
1539                   NamedCompound ce = new NamedCompound(Globals.lang("Unmark entries"));
1540                   BibtexEntry[] bes = mainTable.getSelectedEntries();
1541           if (bes == null)
1542               return;
1543                         for (BibtexEntry be : bes) {
1544                             Util.unmarkEntry(be, false, database, ce);
1545                         }
1546                   ce.end();
1547                   undoManager.addEdit(ce);
1548                   markBaseChanged();
1549                   output(Globals.lang("Unmarked selected")+" "+Globals.lang(bes.length>0?"entry":"entries"));
1550                     } catch (Throwable ex) { ex.printStackTrace(); }
1551                 }
1552               });
1553
1554               actions.put("unmarkAll", new BaseAction() {
1555                 public void action() {
1556                   NamedCompound ce = new NamedCompound(Globals.lang("Unmark all"));
1557                   
1558                   for (BibtexEntry be : database.getEntries()){
1559                     Util.unmarkEntry(be, false, database, ce);
1560                   }
1561                   ce.end();
1562                   undoManager.addEdit(ce);
1563                   markBaseChanged();
1564                 }
1565               });
1566               
1567               actions.put(Relevance.getInstance().getValues().get(0).getActionName(), 
1568                   new SpecialFieldAction(frame, Relevance.getInstance(), Relevance.getInstance().getValues().get(0).getFieldValue(), true, Globals.lang("Marked entries as relevant"), "Marked %0 entries as relevant"));
1569               actions.put(Quality.getInstance().getValues().get(0).getActionName(),
1570                   new SpecialFieldAction(frame, Quality.getInstance(), Quality.getInstance().getValues().get(0).getFieldValue(), true, Globals.lang("Marked entries' quality as good"), "Set quality of %0 entries to good"));
1571               actions.put(Printed.getInstance().getValues().get(0).getActionName(),
1572                       new SpecialFieldAction(frame, Printed.getInstance(), Printed.getInstance().getValues().get(0).getFieldValue(), true, Globals.lang("Marked entries as printed"), "Marked %0 entries as printed"));
1573               
1574               for (SpecialFieldValue prio: Priority.getInstance().getValues()) {
1575                       actions.put(prio.getActionName(), prio.getAction(this.frame));
1576               }
1577               for (SpecialFieldValue rank: Rank.getInstance().getValues()) {
1578                       actions.put(rank.getActionName(), rank.getAction(this.frame));
1579               }
1580               for (SpecialFieldValue status: ReadStatus.getInstance().getValues()) {
1581                       actions.put(status.getActionName(), status.getAction(this.frame));
1582               }
1583               
1584               actions.put("togglePreview", new BaseAction() {
1585                       public void action() {
1586                           boolean enabled = !Globals.prefs.getBoolean("previewEnabled");
1587                           Globals.prefs.putBoolean("previewEnabled", enabled);
1588                           frame.setPreviewActive(enabled);
1589                           frame.previewToggle.setSelected(enabled);
1590                       }
1591                   });
1592
1593               actions.put("toggleHighlightGroupsMatchingAny", new BaseAction() {
1594                 public void action() {
1595                     boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAny");
1596                     Globals.prefs.putBoolean("highlightGroupsMatchingAny", enabled);
1597                     frame.highlightAny.setSelected(enabled);
1598                     if (enabled) {
1599                         frame.highlightAll.setSelected(false);
1600                         Globals.prefs.putBoolean("highlightGroupsMatchingAll", false);
1601                     }
1602                     // ping the listener so it updates:
1603                     groupsHighlightListener.listChanged(null);
1604                 }
1605               });
1606
1607               actions.put("toggleHighlightGroupsMatchingAll", new BaseAction() {
1608                   public void action() {
1609                       boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAll");
1610                       Globals.prefs.putBoolean("highlightGroupsMatchingAll", enabled);
1611                       frame.highlightAll.setSelected(enabled);
1612                       if (enabled) {
1613                           frame.highlightAny.setSelected(false);
1614                           Globals.prefs.putBoolean("highlightGroupsMatchingAny", false);
1615                       }
1616                       // ping the listener so it updates:
1617                       groupsHighlightListener.listChanged(null);
1618                   }
1619                 });
1620
1621               actions.put("switchPreview", new BaseAction() {
1622                       public void action() {
1623                           selectionListener.switchPreview();
1624                       }
1625                   });
1626
1627               actions.put("manageSelectors", new BaseAction() {
1628                       public void action() {
1629                           ContentSelectorDialog2 csd = new ContentSelectorDialog2
1630                               (frame, frame, BasePanel.this, false, metaData, null);
1631                           Util.placeDialog(csd, frame);
1632                           csd.setVisible(true);
1633                       }
1634                   });
1635
1636         actions.put("exportToClipboard", new ExportToClipboardAction(frame, database()));
1637         actions.put("sendAsEmail", new SendAsEMailAction(frame));
1638         
1639           
1640         actions.put("writeXMP", new WriteXMPAction(this));
1641         
1642         actions.put("abbreviateIso", new AbbreviateAction(this, true));
1643         actions.put("abbreviateMedline", new AbbreviateAction(this, false));
1644         actions.put("unabbreviate", new UnabbreviateAction(this));
1645         actions.put("autoSetPdf", new AutoSetExternalFileForEntries(this, "pdf"));
1646         actions.put("autoSetPs", new AutoSetExternalFileForEntries(this, "ps"));
1647         actions.put("autoSetFile", new SynchronizeFileField(this));
1648
1649         actions.put("back", new BaseAction() {
1650             public void action() throws Throwable {
1651                 back();
1652             }
1653         });
1654         actions.put("forward", new BaseAction() {
1655             public void action() throws Throwable {
1656                 forward();
1657             }
1658         });
1659
1660         actions.put("resolveDuplicateKeys", new SearchFixDuplicateLabels(this));
1661
1662         actions.put("addToGroup", new GroupAddRemoveDialog(this, true, false));
1663         actions.put("removeFromGroup", new GroupAddRemoveDialog(this, false, false));
1664         actions.put("moveToGroup", new GroupAddRemoveDialog(this, true, true));
1665
1666         //actions.put("downloadFullText", new FindFullTextAction(this));
1667     }
1668
1669     /**
1670      * This method is called from JabRefFrame is a database specific
1671      * action is requested by the user. Runs the command if it is
1672      * defined, or prints an error message to the standard error
1673      * stream.
1674      *
1675      * @param _command The name of the command to run.
1676     */
1677     public void runCommand(String _command) {
1678       final String command = _command;
1679       //(new Thread() {
1680       //  public void run() {
1681           if (actions.get(command) == null)
1682             Util.pr("No action defined for '" + command + "'");
1683             else {
1684         Object o = actions.get(command);
1685         try {
1686             if (o instanceof BaseAction)
1687             ((BaseAction)o).action();
1688             else {
1689             // This part uses Spin's features:
1690             Worker wrk = ((AbstractWorker)o).getWorker();
1691             // The Worker returned by getWorker() has been wrapped
1692             // by Spin.off(), which makes its methods be run in
1693             // a different thread from the EDT.
1694             CallBack clb = ((AbstractWorker)o).getCallBack();
1695
1696             ((AbstractWorker)o).init(); // This method runs in this same thread, the EDT.
1697             // Useful for initial GUI actions, like printing a message.
1698
1699             // The CallBack returned by getCallBack() has been wrapped
1700             // by Spin.over(), which makes its methods be run on
1701             // the EDT.
1702             wrk.run(); // Runs the potentially time-consuming action
1703             // without freezing the GUI. The magic is that THIS line
1704             // of execution will not continue until run() is finished.
1705             clb.update(); // Runs the update() method on the EDT.
1706             }
1707         } catch (Throwable ex) {
1708             // If the action has blocked the JabRefFrame before crashing, we need to unblock it.
1709             // The call to unblock will simply hide the glasspane, so there is no harm in calling
1710             // it even if the frame hasn't been blocked.
1711             frame.unblock();
1712             ex.printStackTrace();
1713         }
1714         }
1715       //  }
1716       //}).start();
1717     }
1718
1719     private boolean saveDatabase(File file, boolean selectedOnly, String encoding) throws SaveException {
1720         SaveSession session;
1721         frame.block();
1722         try {
1723             if (!selectedOnly)
1724                 session = FileActions.saveDatabase(database, metaData, file,
1725                                            Globals.prefs, false, false, encoding, false);
1726             else
1727                 session = FileActions.savePartOfDatabase(database, metaData, file,
1728                                                Globals.prefs, mainTable.getSelectedEntries(), encoding);
1729
1730         } catch (UnsupportedCharsetException ex2) {
1731             JOptionPane.showMessageDialog(frame, Globals.lang("Could not save file. "
1732                 +"Character encoding '%0' is not supported.", encoding),
1733                     Globals.lang("Save database"), JOptionPane.ERROR_MESSAGE);
1734             throw new SaveException("rt");
1735         } catch (SaveException ex) {
1736             if (ex.specificEntry()) {
1737                 // Error occured during processing of
1738                 // be. Highlight it:
1739                 int row = mainTable.findEntry(ex.getEntry()),
1740                     topShow = Math.max(0, row-3);
1741                 mainTable.setRowSelectionInterval(row, row);
1742                 mainTable.scrollTo(topShow);
1743                 showEntry(ex.getEntry());
1744             }
1745             else ex.printStackTrace();
1746
1747             JOptionPane.showMessageDialog
1748                 (frame, Globals.lang("Could not save file")
1749                  +".\n"+ex.getMessage(),
1750                  Globals.lang("Save database"),
1751                  JOptionPane.ERROR_MESSAGE);
1752             throw new SaveException("rt");
1753
1754         } finally {
1755             frame.unblock();
1756         }
1757
1758         boolean commit = true;
1759         if (!session.getWriter().couldEncodeAll()) {
1760             DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("left:pref, 4dlu, fill:pref", ""));
1761             JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters());
1762             ta.setEditable(false);
1763             builder.append(Globals.lang("The chosen encoding '%0' could not encode the following characters: ",
1764                       session.getEncoding()));
1765             builder.append(ta);
1766             builder.append(Globals.lang("What do you want to do?"));
1767             String tryDiff = Globals.lang("Try different encoding");
1768             int answer = JOptionPane.showOptionDialog(frame, builder.getPanel(), Globals.lang("Save database"),
1769                     JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
1770                     new String[] {Globals.lang("Save"), tryDiff, Globals.lang("Cancel")}, tryDiff);
1771
1772             if (answer == JOptionPane.NO_OPTION) {
1773                 // The user wants to use another encoding.
1774                 Object choice = JOptionPane.showInputDialog(frame, Globals.lang("Select encoding"), Globals.lang("Save database"),
1775                         JOptionPane.QUESTION_MESSAGE, null, Globals.ENCODINGS, encoding);
1776                 if (choice != null) {
1777                     String newEncoding = (String)choice;
1778                     return saveDatabase(file, selectedOnly, newEncoding);
1779                 } else
1780                     commit = false;
1781             } else if (answer == JOptionPane.CANCEL_OPTION)
1782                     commit = false;
1783
1784
1785           }
1786
1787         if (commit) {
1788             session.commit();
1789             this.encoding = encoding; // Make sure to remember which encoding we used.
1790         }
1791         else
1792             session.cancel();
1793
1794         return commit;
1795     }
1796
1797
1798     /**
1799      * This method is called from JabRefFrame when the user wants to
1800      * create a new entry. If the argument is null, the user is
1801      * prompted for an entry type.
1802      *
1803      * @param type The type of the entry to create.
1804      * @return The newly created BibtexEntry or null the operation was canceled by the user.
1805      */
1806     public BibtexEntry newEntry(BibtexEntryType type) {
1807         if (type == null) {
1808             // Find out what type is wanted.
1809             EntryTypeDialog etd = new EntryTypeDialog(frame);
1810             // We want to center the dialog, to make it look nicer.
1811             Util.placeDialog(etd, frame);
1812             etd.setVisible(true);
1813             type = etd.getChoice();
1814         }
1815         if (type != null) { // Only if the dialog was not cancelled.
1816             String id = Util.createNeutralId();            
1817             final BibtexEntry be = new BibtexEntry(id, type);
1818             try {
1819                 database.insertEntry(be);
1820                 // Set owner/timestamp if options are enabled:
1821                 ArrayList<BibtexEntry> list = new ArrayList<BibtexEntry>();
1822                 list.add(be);
1823                 Util.setAutomaticFields(list, true, true, false);
1824
1825                 // Create an UndoableInsertEntry object.
1826                 undoManager.addEdit(new UndoableInsertEntry(database, be, BasePanel.this));
1827                 output(Globals.lang("Added new")+" '"+type.getName().toLowerCase()+"' "
1828                        +Globals.lang("entry")+".");
1829
1830                 // We are going to select the new entry. Before that, make sure that we are in
1831                 // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR
1832                 // mode which makes sure the selection will trigger display of the entry editor
1833                 // and adjustment of the splitter.
1834                 if (mode != SHOWING_EDITOR) {
1835                     mode = WILL_SHOW_EDITOR;
1836                 }
1837
1838                 int row = mainTable.findEntry(be);
1839                 if (row >= 0)
1840                     highlightEntry(be);  // Selects the entry. The selection listener will open the editor.
1841                 else {
1842                     // The entry is not visible in the table, perhaps due to a filtering search
1843                     // or group selection. Show the entry editor anyway:
1844                     showEntry(be);
1845                 }
1846
1847                 markBaseChanged(); // The database just changed.
1848                 new FocusRequester(getEntryEditor(be));
1849
1850                 return be;
1851             } catch (KeyCollisionException ex) {
1852                 Util.pr(ex.getMessage());
1853             }
1854         }
1855         return null;
1856     }
1857
1858     /**
1859      * This listener is used to add a new entry to a group (or a set of groups)
1860      * in case the Group View is selected and one or more groups are marked
1861      */
1862     private class GroupTreeUpdater implements DatabaseChangeListener {
1863         public void databaseChanged(DatabaseChangeEvent e) {
1864             if ( (e.getType() == ChangeType.ADDED_ENTRY)
1865                  && (Globals.prefs.getBoolean("autoAssignGroup"))
1866                  && (frame.groupToggle.isSelected())) {
1867                                 BibtexEntry[] entries = {e.getEntry()};
1868                                 TreePath[] selection = frame.groupSelector.getGroupsTree().getSelectionPaths();
1869                                 if (selection != null) {
1870                                         // it is possible that the user selected nothing. Therefore, checked for "!= null"
1871                                         for (TreePath tree : selection){
1872                                                 ((GroupTreeNode)(tree.getLastPathComponent())).addToGroup(entries);
1873                                         }
1874                                 }
1875                                 //BasePanel.this.updateEntryEditorIfShowing(); // doesn't seem to be necessary
1876                                 SwingUtilities.invokeLater(new Runnable() {
1877                     @Override
1878                     public void run() {
1879                         BasePanel.this.getGroupSelector().valueChanged(null);
1880                     }
1881                 });
1882             }
1883         }
1884     }
1885
1886     /**
1887      * Ensures that the search auto completer is up to date when entries are changed
1888      * AKA Let the auto completer, if any, harvest words from the entry
1889      */
1890     private class SearchAutoCompleterUpdater implements DatabaseChangeListener {
1891         public void databaseChanged(DatabaseChangeEvent e) {
1892             if ((e.getType() == ChangeType.CHANGED_ENTRY) || (e.getType() == ChangeType.ADDED_ENTRY)) {
1893                 Util.updateCompletersForEntry(BasePanel.this.searchAutoCompleterHM, e.getEntry());
1894             }
1895         }
1896     }
1897
1898     /**
1899      * Ensures that auto completers are up to date when entries are changed
1900      * AKA Let the auto completer, if any, harvest words from the entry
1901      */
1902     private class AutoCompletersUpdater implements DatabaseChangeListener {
1903         public void databaseChanged(DatabaseChangeEvent e) {
1904             if ((e.getType() == ChangeType.CHANGED_ENTRY) || (e.getType() == ChangeType.ADDED_ENTRY)) {
1905                 Util.updateCompletersForEntry(BasePanel.this.getAutoCompleters(), e.getEntry());
1906             }
1907         }
1908     }
1909
1910     /**
1911      * This method is called from JabRefFrame when the user wants to
1912      * create a new entry.
1913      * @param bibEntry The new entry.
1914      */
1915     public void insertEntry(BibtexEntry bibEntry)
1916     {
1917       if (bibEntry != null)
1918       {
1919         try
1920         {
1921           database.insertEntry(bibEntry) ;
1922           if (Globals.prefs.getBoolean("useOwner"))
1923             // Set owner field to default value
1924                 Util.setAutomaticFields(bibEntry, true, true);
1925             // Create an UndoableInsertEntry object.
1926             undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1927             output(Globals.lang("Added new")+" '"
1928                    +bibEntry.getType().getName().toLowerCase()+"' "
1929                    +Globals.lang("entry")+".");
1930
1931             markBaseChanged(); // The database just changed.
1932             if (Globals.prefs.getBoolean("autoOpenForm"))
1933             {
1934                   selectionListener.editSignalled(bibEntry);
1935             }
1936             highlightEntry(bibEntry);
1937         } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); }
1938       }
1939     }
1940
1941     public void updateTableFont() {
1942         mainTable.updateFont();
1943     }
1944
1945     public void createMainTable() {
1946         //Comparator comp = new FieldComparator("author");
1947
1948         GlazedEntrySorter eventList = new GlazedEntrySorter(database.getEntryMap());
1949         // Must initialize sort columns somehow:
1950
1951         database.addDatabaseChangeListener(eventList);
1952         database.addDatabaseChangeListener(SpecialFieldDatabaseChangeListener.getInstance());
1953         groupFilterList = new FilterList<BibtexEntry>(eventList.getTheList(), NoSearchMatcher.INSTANCE);
1954         searchFilterList = new FilterList<BibtexEntry>(groupFilterList, NoSearchMatcher.INSTANCE);
1955         //final SortedList sortedList = new SortedList(searchFilterList, null);
1956         tableFormat = new MainTableFormat(this);
1957         tableFormat.updateTableFormat();
1958         //EventTableModel tableModel = new EventTableModel(sortedList, tableFormat);
1959         mainTable = new MainTable(tableFormat, searchFilterList, frame, this);
1960         
1961         selectionListener = new MainTableSelectionListener(this, mainTable);
1962         mainTable.updateFont();
1963         mainTable.addSelectionListener(selectionListener);
1964         mainTable.addMouseListener(selectionListener);
1965         mainTable.addKeyListener(selectionListener);
1966         mainTable.addFocusListener(selectionListener);
1967         
1968         // Add the listener that will take care of highlighting groups as the selection changes:
1969         groupsHighlightListener = new ListEventListener<BibtexEntry>() {
1970             public void listChanged(ListEvent<BibtexEntry> listEvent) {
1971                 if (Globals.prefs.getBoolean("highlightGroupsMatchingAny"))
1972                     getGroupSelector().showMatchingGroups(
1973                             mainTable.getSelectedEntries(), false);
1974                 else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll"))
1975                     getGroupSelector().showMatchingGroups(
1976                             mainTable.getSelectedEntries(), true);
1977                 else // no highlight
1978                     getGroupSelector().showMatchingGroups(null, true);
1979             }
1980         };
1981         mainTable.addSelectionListener(groupsHighlightListener);
1982
1983         mainTable.getActionMap().put("cut", new AbstractAction() {
1984                 public void actionPerformed(ActionEvent e) {
1985                     try { runCommand("cut");
1986                     } catch (Throwable ex) {
1987                         ex.printStackTrace();
1988                     }
1989                 }
1990             });
1991         mainTable.getActionMap().put("copy", new AbstractAction() {
1992                 public void actionPerformed(ActionEvent e) {
1993                     try { runCommand("copy");
1994                     } catch (Throwable ex) {
1995                         ex.printStackTrace();
1996                     }
1997                 }
1998             });
1999         mainTable.getActionMap().put("paste", new AbstractAction() {
2000                 public void actionPerformed(ActionEvent e) {
2001                     try { runCommand("paste");
2002                     } catch (Throwable ex) {
2003                         ex.printStackTrace();
2004                     }
2005                 }
2006             });
2007
2008         mainTable.addKeyListener(new KeyAdapter() {
2009
2010                 public void keyPressed(KeyEvent e) {
2011                     final int keyCode = e.getKeyCode();
2012                     final TreePath path = frame.groupSelector.getSelectionPath();
2013                     final GroupTreeNode node = path == null ? null : (GroupTreeNode) path.getLastPathComponent();
2014
2015                     if (e.isControlDown()) {
2016                         switch (keyCode) {
2017                         // The up/down/left/rightkeystrokes are displayed in the
2018                         // GroupSelector's popup menu, so if they are to be changed,
2019                         // edit GroupSelector.java accordingly!
2020                         case KeyEvent.VK_UP:
2021                             e.consume();
2022                             if (node != null)
2023                                 frame.groupSelector.moveNodeUp(node, true);
2024                             break;
2025                         case KeyEvent.VK_DOWN:
2026                             e.consume();
2027                             if (node != null)
2028                                 frame.groupSelector.moveNodeDown(node, true);
2029                             break;
2030                         case KeyEvent.VK_LEFT:
2031                             e.consume();
2032                             if (node != null)
2033                                 frame.groupSelector.moveNodeLeft(node, true);
2034                             break;
2035                         case KeyEvent.VK_RIGHT:
2036                             e.consume();
2037                             if (node != null)
2038                                 frame.groupSelector.moveNodeRight(node, true);
2039                             break;
2040                         case KeyEvent.VK_PAGE_DOWN:
2041                             frame.nextTab.actionPerformed(null);
2042                             e.consume();
2043                             break;
2044                         case KeyEvent.VK_PAGE_UP:
2045                             frame.prevTab.actionPerformed(null);
2046                             e.consume();
2047                             break;
2048                         }
2049                     } else if (keyCode == KeyEvent.VK_ENTER){
2050                         e.consume();
2051                         try { runCommand("edit");
2052                         } catch (Throwable ex) {
2053                             ex.printStackTrace();
2054                         }
2055                     }
2056                 }
2057         });
2058     }
2059
2060     public void setupMainPanel() {
2061         //System.out.println("setupMainPanel");
2062         //splitPane = new com.jgoodies.uif_lite.component.UIFSplitPane(JSplitPane.VERTICAL_SPLIT);
2063         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
2064         splitPane.setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
2065         // We replace the default FocusTraversalPolicy with a subclass
2066         // that only allows FieldEditor components to gain keyboard focus,
2067         // if there is an entry editor open.
2068         /*splitPane.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
2069                 protected boolean accept(Component c) {
2070                     Util.pr("jaa");
2071                     if (showing == null)
2072                         return super.accept(c);
2073                     else
2074                         return (super.accept(c) &&
2075                                 (c instanceof FieldEditor));
2076                 }
2077                 });*/
2078
2079         createMainTable();
2080
2081         for (EntryEditor ee : entryEditors.values()) {
2082             ee.validateAllFields();
2083         }
2084
2085
2086
2087         splitPane.setTopComponent(mainTable.getPane());
2088
2089         // Remove borders
2090         splitPane.setBorder(BorderFactory.createEmptyBorder());
2091         setBorder(BorderFactory.createEmptyBorder());
2092                 
2093         //setupTable();
2094         // If an entry is currently being shown, make sure it stays shown,
2095         // otherwise set the bottom component to null.
2096         if (mode == SHOWING_PREVIEW) {
2097             mode = SHOWING_NOTHING;
2098             int row = mainTable.findEntry(currentPreview.entry);
2099             if (row >= 0)
2100                 mainTable.setRowSelectionInterval(row, row);
2101
2102         }
2103         else if (mode == SHOWING_EDITOR) {
2104             mode = SHOWING_NOTHING;
2105             /*int row = mainTable.findEntry(currentEditor.entry);
2106             if (row >= 0)
2107                 mainTable.setRowSelectionInterval(row, row);
2108             */
2109             //showEntryEditor(currentEditor);
2110         } else
2111             splitPane.setBottomComponent(null);
2112
2113
2114         setLayout(new BorderLayout());
2115         removeAll();
2116         add(splitPane, BorderLayout.CENTER);
2117
2118         // Set up name autocompleter for search:
2119         //if (!Globals.prefs.getBoolean("searchAutoComplete")) {
2120         instantiateSearchAutoCompleter();
2121         this.getDatabase().addDatabaseChangeListener(new SearchAutoCompleterUpdater());
2122
2123         // Set up AutoCompleters for this panel:
2124         if (Globals.prefs.getBoolean("autoComplete")) {
2125             instantiateAutoCompleters();
2126             // ensure that the autocompleters are in sync with entries
2127             this.getDatabase().addDatabaseChangeListener(new AutoCompletersUpdater());
2128         }
2129
2130         splitPane.revalidate();
2131         revalidate();
2132         repaint();
2133     }
2134
2135     public void updateSearchManager() {
2136         frame.getSearchManager().setAutoCompleteListener(searchCompleteListener);
2137     }
2138
2139     public HashMap<String, AbstractAutoCompleter> getAutoCompleters() {
2140         return autoCompleters;
2141     }
2142     
2143     public AbstractAutoCompleter getAutoCompleter(String fieldName) {
2144         return autoCompleters.get(fieldName);
2145     }
2146
2147     private void instantiateSearchAutoCompleter() {
2148         searchCompleter = new NameFieldAutoCompleter(new String[] {"author", "editor"}, true);
2149         searchAutoCompleterHM.put("x", searchCompleter);
2150         for (BibtexEntry entry : database.getEntries()){
2151             Util.updateCompletersForEntry(searchAutoCompleterHM, entry);
2152         }
2153         searchCompleteListener = new AutoCompleteListener(searchCompleter);
2154         searchCompleteListener.setConsumeEnterKey(false); // So you don't have to press Enter twice
2155     }
2156
2157     private void instantiateAutoCompleters() {
2158         autoCompleters.clear();
2159         String[] completeFields = Globals.prefs.getStringArray("autoCompleteFields");
2160         for (String field : completeFields) {
2161             AbstractAutoCompleter autoCompleter = AutoCompleterFactory.getFor(field);
2162             autoCompleters.put(field, autoCompleter);
2163         }
2164         for (BibtexEntry entry : database.getEntries()){
2165             Util.updateCompletersForEntry(autoCompleters, entry);
2166         }
2167
2168         addJournalListToAutoCompleter();
2169         addContentSelectorValuesToAutoCompleters();
2170     }
2171
2172     /**
2173      * For all fields with both autocompletion and content selector, add content selector
2174      * values to the autocompleter list:
2175      */
2176     public void addContentSelectorValuesToAutoCompleters() {
2177         for (String field : autoCompleters.keySet()) {
2178             AbstractAutoCompleter ac = autoCompleters.get(field);
2179             if (metaData.getData(Globals.SELECTOR_META_PREFIX + field) != null) {
2180                 Vector<String> items = metaData.getData(Globals.SELECTOR_META_PREFIX + field);
2181                 if (items != null) {
2182                     for (String item : items) ac.addWordToIndex(item);
2183                 }
2184             }
2185         }
2186     }
2187
2188     /**
2189      * If an autocompleter exists for the "journal" field, add all
2190      * journal names in the journal abbreviation list to this autocompleter.
2191      */
2192     public void addJournalListToAutoCompleter() {
2193         if (autoCompleters.containsKey("journal")) {
2194             AbstractAutoCompleter ac = autoCompleters.get("journal");
2195             Set<String> journals = Globals.journalAbbrev.getJournals().keySet();
2196             for (String journal : journals)
2197                 ac.addWordToIndex(journal);
2198         }
2199
2200
2201     }
2202
2203
2204     /*
2205     public void refreshTable() {
2206         //System.out.println("hiding="+hidingNonHits+"\tlastHits="+lastSearchHits);
2207         // This method is called by EntryTypeForm when a field value is
2208         // stored. The table is scheduled for repaint.
2209         entryTable.assureNotEditing();
2210         //entryTable.invalidate();
2211         BibtexEntry[] bes = entryTable.getSelectedEntries();
2212     if (hidingNonHits)
2213         tableModel.update(lastSearchHits);
2214     else
2215         tableModel.update();
2216     //tableModel.remap();
2217         if ((bes != null) && (bes.length > 0))
2218             selectEntries(bes, 0);
2219
2220     //long toc = System.currentTimeMillis();
2221     //  Util.pr("Refresh took: "+(toc-tic)+" ms");
2222     } */
2223
2224     public void updatePreamble() {
2225         if (preambleEditor != null)
2226             preambleEditor.updatePreamble();
2227     }
2228
2229     public void assureStringDialogNotEditing() {
2230         if (stringDialog != null)
2231             stringDialog.assureNotEditing();
2232     }
2233
2234     public void updateStringDialog() {
2235         if (stringDialog != null)
2236             stringDialog.refreshTable();
2237     }
2238
2239     public void updateEntryPreviewToRow(BibtexEntry e) {
2240
2241     }
2242
2243     public void adjustSplitter() {
2244         int mode = getMode();
2245         if (mode == SHOWING_PREVIEW) {
2246             splitPane.setDividerLocation(splitPane.getHeight()-Globals.prefs.getInt("previewPanelHeight"));
2247         } else {
2248             splitPane.setDividerLocation(splitPane.getHeight()-Globals.prefs.getInt("entryEditorHeight"));
2249
2250         }
2251     }
2252
2253
2254
2255     /**
2256      * Stores the source view in the entry editor, if one is open, has the source view
2257      * selected and the source has been edited.
2258      * @return boolean false if there is a validation error in the source panel, true otherwise.
2259      */
2260     public boolean entryEditorAllowsChange() {
2261       Component c = splitPane.getBottomComponent();
2262       if ((c != null) && (c instanceof EntryEditor)) {
2263         return ((EntryEditor)c).lastSourceAccepted();
2264       }
2265       else
2266         return true;
2267     }
2268
2269     public void moveFocusToEntryEditor() {
2270       Component c = splitPane.getBottomComponent();
2271       if ((c != null) && (c instanceof EntryEditor)) {
2272         new FocusRequester(c);
2273       }
2274     }
2275
2276     public boolean isShowingEditor() {
2277       return ((splitPane.getBottomComponent() != null)
2278               && (splitPane.getBottomComponent() instanceof EntryEditor));
2279     }
2280
2281     public void showEntry(final BibtexEntry be) {
2282
2283         if (getShowing() == be) {
2284             if (splitPane.getBottomComponent() == null) {
2285                 // This is the special occasion when showing is set to an
2286                 // entry, but no entry editor is in fact shown. This happens
2287                 // after Preferences dialog is closed, and it means that we
2288                 // must make sure the same entry is shown again. We do this by
2289                 // setting showing to null, and recursively calling this method.
2290                 newEntryShowing(null);
2291                 showEntry(be);
2292             } else {
2293               // The correct entry is already being shown. Make sure the editor
2294               // is updated.
2295               ((EntryEditor)splitPane.getBottomComponent()).updateAllFields();
2296
2297             }
2298             return;
2299
2300         }
2301
2302         EntryEditor form;
2303         int divLoc = -1;
2304         String visName = null;
2305         if (getShowing() != null) {
2306                 if (isShowingEditor()) {
2307                         visName = ((EntryEditor) splitPane.getBottomComponent()).getVisiblePanelName();
2308                 }
2309         }
2310         if (getShowing() != null)
2311             divLoc = splitPane.getDividerLocation();
2312
2313         if (entryEditors.containsKey(be.getType().getName())) {
2314             // We already have an editor for this entry type.
2315             form = entryEditors.get
2316                 ((be.getType().getName()));
2317             form.switchTo(be);
2318             if (visName != null)
2319                 form.setVisiblePanel(visName);
2320             splitPane.setBottomComponent(form);
2321             //highlightEntry(be);
2322         } else {
2323             // We must instantiate a new editor for this type.
2324             form = new EntryEditor(frame, BasePanel.this, be);
2325             if (visName != null)
2326                 form.setVisiblePanel(visName);
2327             splitPane.setBottomComponent(form);
2328
2329             //highlightEntry(be);
2330             entryEditors.put(be.getType().getName(), form);
2331
2332         }
2333         if (divLoc > 0) {
2334           splitPane.setDividerLocation(divLoc);
2335         }
2336         else
2337             splitPane.setDividerLocation
2338                 (splitPane.getHeight()-Globals.prefs.getInt("entryEditorHeight"));
2339         //new FocusRequester(form);
2340         //form.requestFocus();
2341
2342         newEntryShowing(be);
2343         setEntryEditorEnabled(true); // Make sure it is enabled.
2344     }
2345
2346     /**
2347      * Get an entry editor ready to edit the given entry. If an appropriate editor is already
2348      * cached, it will be updated and returned.
2349      * @param entry The entry to be edited.
2350      * @return A suitable entry editor.
2351      */
2352     public EntryEditor getEntryEditor(BibtexEntry entry) {
2353         EntryEditor form;
2354         if (entryEditors.containsKey(entry.getType().getName())) {
2355             EntryEditor visibleNow = currentEditor;
2356             
2357             // We already have an editor for this entry type.
2358             form = entryEditors.get
2359                 ((entry.getType().getName()));
2360
2361             // If the cached editor is not the same as the currently shown one,
2362             // make sure the current one stores its current edit:
2363             if ((visibleNow != null) && (form != visibleNow)) {
2364                 visibleNow.storeCurrentEdit();
2365             }
2366             
2367             form.switchTo(entry);
2368             //if (visName != null)
2369             //    form.setVisiblePanel(visName);
2370         } else {
2371             // We must instantiate a new editor for this type. First make sure the old one
2372             // stores its last edit:
2373             storeCurrentEdit();
2374             // Then start the new one:
2375             form = new EntryEditor(frame, BasePanel.this, entry);
2376             //if (visName != null)
2377             //    form.setVisiblePanel(visName);
2378
2379             entryEditors.put(entry.getType().getName(), form);
2380         }
2381         return form;
2382     }
2383
2384     public EntryEditor getCurrentEditor() {
2385         return currentEditor;
2386     }
2387
2388     /**
2389      * Sets the given entry editor as the bottom component in the split pane. If an entry editor already
2390      * was shown, makes sure that the divider doesn't move.
2391      * Updates the mode to SHOWING_EDITOR.
2392      * @param editor The entry editor to add.
2393      */
2394     public void showEntryEditor(EntryEditor editor) {
2395         int oldSplitterLocation = -1;
2396         if (mode == SHOWING_EDITOR)
2397             Globals.prefs.putInt("entryEditorHeight", splitPane.getHeight() - splitPane.getDividerLocation());
2398         else if (mode == SHOWING_PREVIEW)
2399             Globals.prefs.putInt("previewPanelHeight", splitPane.getHeight()-splitPane.getDividerLocation());
2400         mode = SHOWING_EDITOR;
2401         currentEditor = editor;
2402         splitPane.setBottomComponent(editor);
2403         if (editor.getEntry() != getShowing())
2404             newEntryShowing(editor.getEntry());
2405         adjustSplitter();
2406
2407     }
2408
2409     /**
2410      * Sets the given preview panel as the bottom component in the split panel.
2411      * Updates the mode to SHOWING_PREVIEW.
2412      * @param preview The preview to show.
2413      */
2414     public void showPreview(PreviewPanel preview) {
2415         mode = SHOWING_PREVIEW;
2416         currentPreview = preview;
2417         splitPane.setBottomComponent(preview);
2418     }
2419
2420     /**
2421      * Removes the bottom component.
2422      */
2423     public void hideBottomComponent() {
2424         mode = SHOWING_NOTHING;
2425         splitPane.setBottomComponent(null);
2426     }
2427
2428     /**
2429      * This method selects the given entry, and scrolls it into view in the table.
2430      * If an entryEditor is shown, it is given focus afterwards.
2431      */
2432     public void highlightEntry(final BibtexEntry be) {
2433         //SwingUtilities.invokeLater(new Thread() {
2434         //     public void run() {
2435                  final int row = mainTable.findEntry(be);
2436                  if (row >= 0) {
2437                     mainTable.setRowSelectionInterval(row, row);
2438                     //entryTable.setActiveRow(row);
2439                     mainTable.ensureVisible(row);
2440                  }
2441         //     }
2442         //});
2443     }
2444
2445
2446     /**
2447      * This method is called from an EntryEditor when it should be closed. We relay
2448      * to the selection listener, which takes care of the rest.
2449      * @param editor The entry editor to close.
2450      */
2451     public void entryEditorClosing(EntryEditor editor) {
2452         // Store divider location for next time:
2453         Globals.prefs.putInt("entryEditorHeight", splitPane.getHeight()-splitPane.getDividerLocation());
2454         selectionListener.entryEditorClosing(editor);
2455     }
2456
2457     /**
2458      * This method selects the given enties.
2459      * If an entryEditor is shown, it is given focus afterwards.
2460      */
2461     /*public void selectEntries(final BibtexEntry[] bes, final int toScrollTo) {
2462
2463         SwingUtilities.invokeLater(new Thread() {
2464              public void run() {
2465                  int rowToScrollTo = 0;
2466                  entryTable.revalidate();
2467                  entryTable.clearSelection();
2468                  loop: for (int i=0; i<bes.length; i++) {
2469                     if (bes[i] == null)
2470                         continue loop;
2471                     int row = tableModel.getNumberFromName(bes[i].getId());
2472                     if (i==toScrollTo)
2473                     rowToScrollTo = row;
2474                     if (row >= 0)
2475                         entryTable.addRowSelectionIntervalQuietly(row, row);
2476                  }
2477                  entryTable.ensureVisible(rowToScrollTo);
2478                  Component comp = splitPane.getBottomComponent();
2479                  //if (comp instanceof EntryEditor)
2480                  //    comp.requestFocus();
2481              }
2482         });
2483     } */
2484
2485     /**
2486      * Closes the entry editor if it is showing the given entry.
2487      *
2488      * @param be a <code>BibtexEntry</code> value
2489      */
2490     public void ensureNotShowing(BibtexEntry be) {
2491         if ((mode == SHOWING_EDITOR) && (currentEditor.getEntry() == be)) {
2492             selectionListener.entryEditorClosing(currentEditor);
2493         }
2494     }
2495
2496     public void updateEntryEditorIfShowing() {
2497         if (mode == SHOWING_EDITOR) {
2498             if (currentEditor.getType() != currentEditor.getEntry().getType()) {
2499                 // The entry has changed type, so we must get a new editor.
2500                 newEntryShowing(null);
2501                 EntryEditor newEditor = getEntryEditor(currentEditor.getEntry());
2502                 showEntryEditor(newEditor);
2503             } else {
2504                 currentEditor.updateAllFields();
2505                 currentEditor.updateSource();
2506             }
2507         }
2508     }
2509
2510     /**
2511      * If an entry editor is showing, make sure its currently focused field
2512      * stores its changes, if any.
2513      */
2514     public void storeCurrentEdit() {
2515         if (isShowingEditor()) {
2516             EntryEditor editor = (EntryEditor)splitPane.getBottomComponent();
2517             editor.storeCurrentEdit();
2518         }
2519
2520     }
2521
2522     /**
2523      * This method iterates through all existing entry editors in this
2524      * BasePanel, telling each to update all its instances of
2525      * FieldContentSelector. This is done to ensure that the list of words
2526      * in each selector is up-to-date after the user has made changes in
2527      * the Manage dialog.
2528      */
2529     public void updateAllContentSelectors() {
2530         for (String s : entryEditors.keySet()) {
2531             EntryEditor ed = entryEditors.get(s);
2532             ed.updateAllContentSelectors();
2533         }
2534     }
2535
2536     public void rebuildAllEntryEditors() {
2537         for (String s : entryEditors.keySet()) {
2538             EntryEditor ed = entryEditors.get(s);
2539             ed.rebuildPanels();
2540         }
2541
2542     }
2543
2544     public void markBaseChanged() {
2545         baseChanged = true;
2546
2547         // Put an asterix behind the file name to indicate the
2548         // database has changed.
2549         String oldTitle = frame.getTabTitle(this);
2550         if (!oldTitle.endsWith("*")) {
2551             frame.setTabTitle(this, oldTitle+"*", frame.getTabTooltip(this));
2552             frame.setWindowTitle();
2553         }
2554         // If the status line states that the base has been saved, we
2555         // remove this message, since it is no longer relevant. If a
2556         // different message is shown, we leave it.
2557         if (frame.statusLine.getText().startsWith(Globals.lang("Saved database")));
2558             frame.output(" ");
2559     }
2560
2561     public void markNonUndoableBaseChanged() {
2562         nonUndoableChange = true;
2563         markBaseChanged();
2564     }
2565
2566     public synchronized void markChangedOrUnChanged() {
2567         if (undoManager.hasChanged()) {
2568             if (!baseChanged) {
2569                 markBaseChanged();
2570             }
2571         }
2572         else if (baseChanged && !nonUndoableChange) {
2573             baseChanged = false;
2574             if (getFile() != null)
2575                 frame.setTabTitle(BasePanel.this, getFile().getName(),
2576                         getFile().getAbsolutePath());
2577             else
2578                 frame.setTabTitle(BasePanel.this, Globals.lang("untitled"), null);
2579         }
2580         frame.setWindowTitle();
2581     }
2582
2583     /**
2584      * Selects a single entry, and scrolls the table to center it.
2585      *
2586      * @param pos Current position of entry to select.
2587      *
2588      */
2589     public void selectSingleEntry(int pos) {
2590         mainTable.clearSelection();
2591         mainTable.addRowSelectionInterval(pos, pos);
2592         mainTable.scrollToCenter(pos, 0);
2593     }
2594
2595     /* *
2596      * Selects all entries with a non-zero value in the field
2597      * @param field <code>String</code> field name.
2598      */
2599 /*    public void selectResults(String field) {
2600       LinkedList intervals = new LinkedList();
2601       int prevStart = -1, prevToSel = 0;
2602       // First we build a list of intervals to select, without touching the table.
2603       for (int i = 0; i < entryTable.getRowCount(); i++) {
2604         String value = (String) (database.getEntryById
2605                                  (tableModel.getIdForRow(i)))
2606             .getField(field);
2607         if ( (value != null) && !value.equals("0")) {
2608           if (prevStart < 0)
2609             prevStart = i;
2610           prevToSel = i;
2611         }
2612         else if (prevStart >= 0) {
2613           intervals.add(new int[] {prevStart, prevToSel});
2614           prevStart = -1;
2615         }
2616       }
2617       // Then select those intervals, if any.
2618       if (intervals.size() > 0) {
2619         entryTable.setSelectionListenerEnabled(false);
2620         entryTable.clearSelection();
2621         for (Iterator i=intervals.iterator(); i.hasNext();) {
2622           int[] interval = (int[])i.next();
2623           entryTable.addRowSelectionInterval(interval[0], interval[1]);
2624         }
2625         entryTable.setSelectionListenerEnabled(true);
2626       }
2627   */
2628
2629     public void setSearchMatcher(SearchMatcher matcher) {
2630         searchFilterList.setMatcher(matcher);
2631         showingSearch = true;
2632     }
2633
2634     public void setGroupMatcher(Matcher<BibtexEntry> matcher) {
2635         groupFilterList.setMatcher(matcher);
2636         showingGroup = true;
2637     }
2638
2639     public void stopShowingSearchResults() {
2640         searchFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2641         showingSearch = false;
2642     }
2643
2644     public void stopShowingGroup() {
2645         groupFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2646         showingGroup = false;
2647      }
2648
2649     /**
2650      * Query whether this BasePanel is in the mode where a float search result is shown.
2651      * @return true if showing float search, false otherwise.
2652      */
2653     public boolean isShowingFloatSearch() {
2654         return mainTable.isShowingFloatSearch();
2655     }
2656
2657     /**
2658      * Query whether this BasePanel is in the mode where a filter search result is shown.
2659      * @return true if showing filter search, false otherwise.
2660      */
2661     public boolean isShowingFilterSearch() {
2662         return showingSearch;
2663     }
2664
2665      public BibtexDatabase getDatabase(){
2666         return database ;
2667     }
2668
2669     public void preambleEditorClosing() {
2670         preambleEditor = null;
2671     }
2672
2673     public void stringsClosing() {
2674         stringDialog = null;
2675     }
2676
2677     public void changeType(BibtexEntry entry, BibtexEntryType type) {
2678       changeType(new BibtexEntry[] {entry}, type);
2679     }
2680
2681     public void changeType(BibtexEntryType type) {
2682       BibtexEntry[] bes = mainTable.getSelectedEntries();
2683       changeType(bes, type);
2684     }
2685
2686     public void changeType(BibtexEntry[] bes, BibtexEntryType type) {
2687
2688         if ((bes == null) || (bes.length == 0)) {
2689             output("First select the entries you wish to change type "+
2690                    "for.");
2691             return;
2692         }
2693         if (bes.length > 1) {
2694             int choice = JOptionPane.showConfirmDialog
2695                 (this, "Multiple entries selected. Do you want to change"
2696                  +"\nthe type of all these to '"+type.getName()+"'?",
2697                  "Change type", JOptionPane.YES_NO_OPTION,
2698                  JOptionPane.WARNING_MESSAGE);
2699             if (choice == JOptionPane.NO_OPTION)
2700                 return;
2701         }
2702
2703         NamedCompound ce = new NamedCompound(Globals.lang("change type"));
2704         for (BibtexEntry be : bes) {
2705             ce.addEdit(new UndoableChangeType(be,
2706                     be.getType(),
2707                     type));
2708             be.setType(type);
2709         }
2710
2711         output(Globals.lang("Changed type to")+" '"+type.getName()+"' "
2712                +Globals.lang("for")+" "+bes.length
2713                +" "+Globals.lang("entries")+".");
2714         ce.end();
2715         undoManager.addEdit(ce);
2716         markBaseChanged();
2717         updateEntryEditorIfShowing();
2718     }
2719
2720     public boolean showDeleteConfirmationDialog(int numberOfEntries) {
2721         if (Globals.prefs.getBoolean("confirmDelete")) {
2722             String msg = Globals.lang("Really delete the selected")
2723                 + " " + Globals.lang("entry") + "?",
2724                 title = Globals.lang("Delete entry");
2725             if (numberOfEntries > 1) {
2726                 msg = Globals.lang("Really delete the selected")
2727                     + " " + numberOfEntries + " " + Globals.lang("entries") + "?";
2728                 title = Globals.lang("Delete multiple entries");
2729             }
2730
2731             CheckBoxMessage cb = new CheckBoxMessage
2732                 (msg, Globals.lang("Disable this confirmation dialog"), false);
2733
2734             int answer = JOptionPane.showConfirmDialog(frame, cb, title,
2735                                                        JOptionPane.YES_NO_OPTION,
2736                                                        JOptionPane.QUESTION_MESSAGE);
2737             if (cb.isSelected())
2738                 Globals.prefs.putBoolean("confirmDelete", false);
2739             return (answer == JOptionPane.YES_OPTION);
2740         } else return true;
2741
2742     }
2743     
2744     /**
2745      * If the relevant option is set, autogenerate keys for all entries that are
2746      * lacking keys.
2747      */
2748     public void autoGenerateKeysBeforeSaving() {
2749         if (Globals.prefs.getBoolean("generateKeysBeforeSaving")) {
2750             NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
2751             boolean any = false;
2752             
2753             for (BibtexEntry bes : database.getEntries()){
2754                 String oldKey = bes.getCiteKey();
2755                 if ((oldKey == null) || (oldKey.equals(""))) {
2756                     LabelPatternUtil.makeLabel(metaData, database, bes);
2757                     ce.addEdit(new UndoableKeyChange(database, bes.getId(), null,
2758                         bes.getField(BibtexFields.KEY_FIELD)));
2759                     any = true;
2760                 }
2761             }
2762             // Store undo information, if any:
2763             if (any) {
2764                 ce.end();
2765                 undoManager.addEdit(ce);
2766             }
2767         }
2768     }
2769     
2770     /**
2771      * Activates or deactivates the entry preview, depending on the argument.
2772      * When deactivating, makes sure that any visible preview is hidden.
2773      * @param enabled
2774      */
2775     public void setPreviewActive(boolean enabled) {
2776         selectionListener.setPreviewActive(enabled);
2777     }
2778
2779     public void setSelectionListenerEnabled(boolean enabled) {
2780         selectionListener.setEnabled(enabled);
2781     }
2782
2783     /**
2784      * Depending on whether a preview or an entry editor is showing, save the current
2785      * divider location in the correct preference setting.
2786      */
2787     public void saveDividerLocation() {
2788         if (mode == SHOWING_PREVIEW)
2789             Globals.prefs.putInt("previewPanelHeight", splitPane.getHeight()-splitPane.getDividerLocation());
2790         else if (mode == SHOWING_EDITOR)
2791             Globals.prefs.putInt("entryEditorHeight", splitPane.getHeight()-splitPane.getDividerLocation());
2792     }
2793
2794         class UndoAction extends BaseAction {
2795         public void action() {
2796             try {
2797                 JComponent focused = Globals.focusListener.getFocused();
2798                 if ((focused != null) && (focused instanceof FieldEditor) && (focused.hasFocus())) {
2799                     // User is currently editing a field:
2800                     // Check if it is the preamble:
2801                     if ((preambleEditor != null) && (focused == preambleEditor.getFieldEditor())) {
2802                         preambleEditor.storeCurrentEdit();
2803                     }
2804                     else
2805                         storeCurrentEdit();
2806                 }
2807                 String name = undoManager.getUndoPresentationName();
2808                 undoManager.undo();
2809                 markBaseChanged();
2810                 frame.output(name);
2811             } catch (CannotUndoException ex) {
2812                 ex.printStackTrace();
2813                 frame.output(Globals.lang("Nothing to undo")+".");
2814             }
2815             // After everything, enable/disable the undo/redo actions
2816             // appropriately.
2817             //updateUndoState();
2818             //redoAction.updateRedoState();
2819             markChangedOrUnChanged();
2820         }
2821     }
2822
2823     class RedoAction extends BaseAction {
2824
2825         public void action() {
2826             try {
2827
2828                 JComponent focused = Globals.focusListener.getFocused();
2829                 if ((focused != null) && (focused instanceof FieldEditor) && (focused.hasFocus())) {
2830                     // User is currently editing a field:
2831                     storeCurrentEdit();
2832                 }
2833
2834                 String name = undoManager.getRedoPresentationName();
2835                 undoManager.redo();
2836                 markBaseChanged();
2837                 frame.output(name);
2838             } catch (CannotRedoException ex) {
2839                 frame.output(Globals.lang("Nothing to redo")+".");
2840             }
2841             // After everything, enable/disable the undo/redo actions
2842             // appropriately.
2843             //updateRedoState();
2844             //undoAction.updateUndoState();
2845             markChangedOrUnChanged();
2846         }
2847     }
2848
2849     // Method pertaining to the ClipboardOwner interface.
2850     public void lostOwnership(Clipboard clipboard, Transferable contents) {}
2851
2852
2853   public void setEntryEditorEnabled(boolean enabled) {
2854     if ((getShowing() != null) && (splitPane.getBottomComponent() instanceof EntryEditor)) {
2855           EntryEditor ed = (EntryEditor)splitPane.getBottomComponent();
2856           if (ed.isEnabled() != enabled)
2857             ed.setEnabled(enabled);
2858     }
2859   }
2860
2861   public String fileMonitorHandle() { return fileMonitorHandle; }
2862
2863     public void fileUpdated() {
2864       if (saving)
2865         return; // We are just saving the file, so this message is most likely due
2866       //if (updatedExternally) {
2867       //  return;
2868       //}
2869       // to bad timing. If not, we'll handle it on the next polling.
2870       //Util.pr("File '"+file.getPath()+"' has been modified.");
2871       updatedExternally = true;
2872
2873       final ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this);
2874
2875       // Adding the sidepane component is Swing work, so we must do this in the Swing
2876       // thread:
2877       Thread t = new Thread() {
2878               public void run() {
2879                   
2880                   // Check if there is already a notification about external
2881                   // changes:
2882                   boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.NAME);
2883                   if (hasAlready) {
2884                       sidePaneManager.hideComponent(FileUpdatePanel.NAME);
2885                       sidePaneManager.unregisterComponent(FileUpdatePanel.NAME);
2886                   }
2887                   FileUpdatePanel pan = new FileUpdatePanel(frame, BasePanel.this,
2888                                                             sidePaneManager, getFile(), scanner);
2889                   sidePaneManager.register(FileUpdatePanel.NAME, pan);
2890                   sidePaneManager.show(FileUpdatePanel.NAME);
2891                   //setUpdatedExternally(false);
2892                   //scanner.displayResult();
2893               }
2894           };
2895
2896       // Test: running scan automatically in background
2897       if ((BasePanel.this.getFile() != null) &&
2898               !Util.waitForFileLock(BasePanel.this.getFile(), 10)) {
2899           // The file is locked even after the maximum wait. Do nothing.
2900           System.err.println("File updated externally, but change scan failed because the file is locked.");
2901           // Perturb the stored timestamp so successive checks are made:
2902           Globals.fileUpdateMonitor.perturbTimestamp(getFileMonitorHandle());
2903           return;
2904       }
2905
2906       scanner.changeScan(BasePanel.this.getFile());
2907       try {
2908           scanner.join();
2909       } catch (InterruptedException e) {
2910           e.printStackTrace();
2911       }
2912
2913       if (scanner.changesFound()) {
2914           SwingUtilities.invokeLater(t);
2915       } else {
2916           setUpdatedExternally(false);
2917           //System.out.println("No changes found.");
2918       }
2919     }
2920
2921       public void fileRemoved() {
2922         Util.pr("File '"+getFile().getPath()+"' has been deleted.");
2923       }
2924
2925
2926     /**
2927      * Perform necessary cleanup when this BasePanel is closed.
2928      */
2929     public void cleanUp() {
2930         if (fileMonitorHandle != null)
2931             Globals.fileUpdateMonitor.removeUpdateListener(fileMonitorHandle);
2932         // Check if there is a FileUpdatePanel for this BasePanel being shown. If so,
2933         // remove it:
2934         if (sidePaneManager.hasComponent("fileUpdate")) {
2935             FileUpdatePanel fup = (FileUpdatePanel)sidePaneManager.getComponent("fileUpdate");
2936             if (fup.getPanel() == this) {
2937                 sidePaneManager.hideComponent("fileUpdate");
2938             }
2939         }
2940     }
2941
2942   public void setUpdatedExternally(boolean b) {
2943     updatedExternally = b;
2944   }
2945
2946     /**
2947      * Get an array containing the currently selected entries.
2948      *
2949      * @return An array containing the selected entries.
2950      */
2951     public BibtexEntry[] getSelectedEntries() {
2952         return mainTable.getSelectedEntries();
2953     }
2954
2955     /**
2956      * Get the file where this database was last saved to or loaded from, if any.
2957      *
2958      * @return The relevant File, or null if none is defined.
2959      */
2960     public File getFile() {
2961         return metaData.getFile();
2962     }
2963     
2964     /**
2965      * Get a String containing a comma-separated list of the bibtex keys
2966      * of the selected entries.
2967      *
2968      * @return A comma-separated list of the keys of the selected entries.
2969      */
2970     public String getKeysForSelection() {
2971         StringBuilder result = new StringBuilder();
2972         String citeKey = "";//, message = "";
2973         boolean first = true;
2974         for (BibtexEntry bes : mainTable.getSelected()){
2975             citeKey = bes.getField(BibtexFields.KEY_FIELD);
2976             // if the key is empty we give a warning and ignore this entry
2977             if (citeKey == null || citeKey.equals(""))
2978                 continue;
2979             if (first) {
2980                 result.append(citeKey);
2981                 first = false;
2982             } else {
2983                 result.append(",").append(citeKey);
2984             }
2985         }
2986         return result.toString();
2987     }
2988
2989     public GroupSelector getGroupSelector() {
2990         return frame.groupSelector;
2991     }
2992
2993
2994     public boolean isUpdatedExternally() {
2995         return updatedExternally;
2996     }
2997
2998
2999     public String getFileMonitorHandle() {
3000         return fileMonitorHandle;
3001     }
3002
3003
3004     public void setFileMonitorHandle(String fileMonitorHandle) {
3005         this.fileMonitorHandle = fileMonitorHandle;
3006     }
3007
3008     public SidePaneManager getSidePaneManager() {
3009         return sidePaneManager;
3010     }
3011
3012
3013     public void setNonUndoableChange(boolean nonUndoableChange) {
3014         this.nonUndoableChange = nonUndoableChange;
3015     }
3016
3017     public void setBaseChanged(boolean baseChanged) {
3018         this.baseChanged = baseChanged;
3019     }
3020
3021
3022     public void setSaving(boolean saving) {
3023         this.saving = saving;
3024     }
3025
3026     public boolean isSaving() {
3027         return saving;
3028     }
3029
3030     public BibtexEntry getShowing() {
3031         return showing;
3032     }
3033
3034     /**
3035      * Update the pointer to the currently shown entry in all cases where the user has
3036      * moved to a new entry, except when using Back and Forward commands. Also updates
3037      * history for Back command, and clears history for Forward command.
3038      * @param entry The entry that is now to be shown.
3039      */
3040     public void newEntryShowing(BibtexEntry entry) {
3041         // If this call is the result of a Back or Forward operation, we must take
3042         // care not to make any history changes, since the necessary changes will
3043         // already have been done in the back() or forward() method:
3044         if (backOrForwardInProgress) {
3045             showing = entry;
3046             backOrForwardInProgress = false;
3047             setBackAndForwardEnabledState();
3048             return;
3049         }
3050         nextEntries.clear();
3051         if (entry != showing) {
3052             // Add the entry we are leaving to the history:
3053             if (showing != null) {
3054                 previousEntries.add(showing);
3055                 if (previousEntries.size() > GUIGlobals.MAX_BACK_HISTORY_SIZE)
3056                     previousEntries.remove(0);
3057             }
3058             showing = entry;
3059             setBackAndForwardEnabledState();
3060         }
3061         
3062     }
3063
3064     /**
3065      * Go back (if there is any recorded history) and update the histories for
3066      * the Back and Forward commands.
3067      */
3068     private void back() {
3069         if (previousEntries.size() > 0) {
3070             BibtexEntry toShow = previousEntries.get(previousEntries.size()-1);
3071             previousEntries.remove(previousEntries.size()-1);
3072             // Add the entry we are going back from to the Forward history:
3073             if (showing != null)
3074                 nextEntries.add(showing);
3075             backOrForwardInProgress = true; // to avoid the history getting updated erroneously
3076             //showEntry(toShow);
3077             highlightEntry(toShow);
3078         }
3079     }
3080
3081     private void forward() {
3082         if (nextEntries.size() > 0) {
3083             BibtexEntry toShow = nextEntries.get(nextEntries.size()-1);
3084             nextEntries.remove(nextEntries.size()-1);
3085             // Add the entry we are going forward from to the Back history:
3086             if (showing != null)
3087                 previousEntries.add(showing);
3088             backOrForwardInProgress = true; // to avoid the history getting updated erroneously
3089             //showEntry(toShow);
3090             highlightEntry(toShow);
3091         }
3092     }
3093
3094     public void setBackAndForwardEnabledState() {
3095         frame.back.setEnabled(previousEntries.size() > 0);
3096         frame.forward.setEnabled(nextEntries.size() > 0);
3097     }
3098
3099
3100 }