04888b2f4e10498b3da712084f945c34754cb943
[debian/jabref.git] / src / java / net / sf / jabref / BasePanel.java
1 /*
2 Copyright (C) 2003 Morten O. Alver and Nizar N. Batada
3
4 All programs in this directory and
5 subdirectories are published under the GNU General Public License as
6 described below.
7
8 This program is free software; you can redistribute it and/or modify
9 it under the terms of the GNU General Public License as published by
10 the Free Software Foundation; either version 2 of the License, or (at
11 your option) any later version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, write to the Free Software
20 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
21 USA
22
23 Further information about the GNU GPL is available at:
24 http://www.gnu.org/copyleft/gpl.ja.html
25
26
27 */
28
29 package net.sf.jabref;
30
31 import java.awt.BorderLayout;
32 import java.awt.Component;
33 import java.awt.GridBagConstraints;
34 import java.awt.GridBagLayout;
35 import java.awt.Toolkit;
36 import java.awt.datatransfer.Clipboard;
37 import java.awt.datatransfer.ClipboardOwner;
38 import java.awt.datatransfer.DataFlavor;
39 import java.awt.datatransfer.StringSelection;
40 import java.awt.datatransfer.Transferable;
41 import java.awt.datatransfer.UnsupportedFlavorException;
42 import java.awt.event.ActionEvent;
43 import java.awt.event.KeyAdapter;
44 import java.awt.event.KeyEvent;
45 import java.awt.event.ActionListener;
46 import java.io.File;
47 import java.io.IOException;
48 import java.nio.charset.UnsupportedCharsetException;
49 import java.util.ArrayList;
50 import java.util.Arrays;
51 import java.util.HashMap;
52 import java.util.Iterator;
53 import java.util.List;
54 import java.util.Set;
55 import java.util.Vector;
56
57 import javax.swing.filechooser.FileFilter;
58 import javax.swing.tree.TreePath;
59 import javax.swing.undo.CannotRedoException;
60 import javax.swing.undo.CannotUndoException;
61
62 import net.sf.jabref.collab.ChangeScanner;
63 import net.sf.jabref.collab.FileUpdateListener;
64 import net.sf.jabref.collab.FileUpdatePanel;
65 import net.sf.jabref.export.*;
66 import net.sf.jabref.external.*;
67 import net.sf.jabref.groups.GroupSelector;
68 import net.sf.jabref.groups.GroupTreeNode;
69 import net.sf.jabref.gui.*;
70 import net.sf.jabref.imports.AppendDatabaseAction;
71 import net.sf.jabref.imports.BibtexParser;
72 import net.sf.jabref.journals.AbbreviateAction;
73 import net.sf.jabref.journals.UnabbreviateAction;
74 import net.sf.jabref.labelPattern.LabelPatternUtil;
75 import net.sf.jabref.search.NoSearchMatcher;
76 import net.sf.jabref.search.SearchMatcher;
77 import net.sf.jabref.undo.CountingUndoManager;
78 import net.sf.jabref.undo.NamedCompound;
79 import net.sf.jabref.undo.UndoableChangeType;
80 import net.sf.jabref.undo.UndoableInsertEntry;
81 import net.sf.jabref.undo.UndoableKeyChange;
82 import net.sf.jabref.undo.UndoableRemoveEntry;
83 import net.sf.jabref.wizard.text.gui.TextInputDialog;
84 import ca.odell.glazedlists.FilterList;
85 import ca.odell.glazedlists.event.ListEvent;
86 import ca.odell.glazedlists.event.ListEventListener;
87 import ca.odell.glazedlists.matchers.Matcher;
88
89 import com.jgoodies.forms.builder.DefaultFormBuilder;
90 import com.jgoodies.forms.layout.FormLayout;
91 import com.jgoodies.uif_lite.component.UIFSplitPane;
92
93 import javax.swing.*;
94
95 public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener {
96
97     public final static int SHOWING_NOTHING=0, SHOWING_PREVIEW=1, SHOWING_EDITOR=2, WILL_SHOW_EDITOR=3;
98     private int mode=0;
99     private EntryEditor currentEditor = null;
100     private PreviewPanel currentPreview = null;
101
102     boolean tmp = true;
103
104     private MainTableSelectionListener selectionListener = null;
105     private ListEventListener groupsHighlightListener;
106     UIFSplitPane contentPane = new UIFSplitPane();
107
108     JSplitPane splitPane;
109     //BibtexEntry testE = new BibtexEntry("tt");
110     //boolean previewActive = true;
111
112     JabRefFrame frame;
113     BibtexDatabase database;
114     // The database shown in this panel.
115     
116     //          Moving file to MetaData (Morten, 2006.08.29)
117     // private File fileToOpen = null; 
118     
119     String fileMonitorHandle = null;
120     boolean saving = false, updatedExternally = false;
121     private String encoding;
122
123     GridBagLayout gbl = new GridBagLayout();
124     GridBagConstraints con = new GridBagConstraints();
125
126     HashMap autoCompleters = new HashMap();
127     // Hashtable that holds as keys the names of the fields where
128     // autocomplete is active, and references to the autocompleter objects.
129
130     // The undo manager.
131     public CountingUndoManager undoManager = new CountingUndoManager(this);
132     UndoAction undoAction = new UndoAction();
133     RedoAction redoAction = new RedoAction();
134
135     //ExampleFileFilter fileFilter;
136     // File filter for .bib files.
137
138     boolean baseChanged = false, nonUndoableChange = false;
139     // Used to track whether the base has changed since last save.
140
141     //EntryTableModel tableModel = null;
142     //public EntryTable entryTable = null;
143     public MainTable mainTable = null;
144     public FilterList searchFilterList = null, groupFilterList = null;
145
146     public RightClickMenu rcm;
147
148     BibtexEntry showing = null;
149     // To indicate which entry is currently shown.
150     public HashMap entryEditors = new HashMap();
151     // To contain instantiated entry editors. This is to save time
152     // in switching between entries.
153
154     //HashMap entryTypeForms = new HashMap();
155     // Hashmap to keep track of which entries currently have open
156     // EntryTypeForm dialogs.
157
158     PreambleEditor preambleEditor = null;
159     // Keeps track of the preamble dialog if it is open.
160
161     StringDialog stringDialog = null;
162     // Keeps track of the string dialog if it is open.
163
164     SaveDatabaseAction saveAction;
165
166     /**
167      * The group selector component for this database. Instantiated by the
168      * SidePaneManager if necessary, or from this class if merging groups from a
169      * different database.
170      */
171     //GroupSelector groupSelector;
172
173     public boolean sortingBySearchResults = false,
174         coloringBySearchResults = false,
175     hidingNonHits = false,
176         sortingByGroup = false,
177         sortingByCiteSeerResults = false,
178         coloringByGroup = false;
179         //previewEnabled = Globals.prefs.getBoolean("previewEnabled");
180     int lastSearchHits = -1; // The number of hits in the latest search.
181     // Potential use in hiding non-hits completely.
182
183     // MetaData parses, keeps and writes meta data.
184     MetaData metaData;
185     HashMap fieldExtras = new HashMap();
186     //## keep track of all keys for duplicate key warning and unique key generation
187     //private HashMap allKeys  = new HashMap(); // use a map instead of a set since i need to know how many of each key is inthere
188
189     private boolean suppressOutput = false;
190
191     private HashMap<String, Object> actions = new HashMap<String, Object>();
192     private SidePaneManager sidePaneManager;
193
194     /**
195      * Create a new BasePanel with an empty database.
196      * @param frame The application window.
197      */
198     public BasePanel(JabRefFrame frame) {
199       this.sidePaneManager = Globals.sidePaneManager;
200       database = new BibtexDatabase();
201       metaData = new MetaData();
202         metaData.initializeNewDatabase();
203       this.frame = frame;
204       setupActions();
205       setupMainPanel();
206         encoding = Globals.prefs.get("defaultEncoding");
207         //System.out.println("Default: "+encoding);
208     }
209
210     public BasePanel(JabRefFrame frame, BibtexDatabase db, File file,
211                      HashMap meta, String encoding) {
212
213         this.encoding = encoding;
214        // System.out.println(encoding);
215      //super(JSplitPane.HORIZONTAL_SPLIT, true);
216       this.sidePaneManager = Globals.sidePaneManager;
217       this.frame = frame;
218       database = db;
219       if (meta != null)
220         parseMetaData(meta);
221       else {
222         metaData = new MetaData();
223         metaData.initializeNewDatabase();   
224       }
225       setupActions();
226       setupMainPanel();
227
228       metaData.setFile(file);
229
230       // Register so we get notifications about outside changes to the file.
231       if (file != null)
232         try {
233           fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(this,
234               file);
235         } catch (IOException ex) {
236         }
237     }
238
239     public boolean isBaseChanged(){
240         return baseChanged;
241     }
242     
243     public int getMode() {
244         return mode;
245     }
246
247     public BibtexDatabase database() {
248                 return database;
249         }
250
251         public MetaData metaData() {
252                 return metaData;
253         }
254
255         public JabRefFrame frame() {
256                 return frame;
257         }
258
259         public JabRefPreferences prefs() {
260                 return Globals.prefs;
261         }
262
263         public String getEncoding() {
264                 return encoding;
265         }
266
267         public void setEncoding(String encoding) {
268                 this.encoding = encoding;
269         }
270
271     public void output(String s) {
272     //Util.pr("\""+s+"\""+(SwingUtilities.isEventDispatchThread()));
273         if (!suppressOutput)
274             frame.output(s);
275     }
276
277     private void setupActions() {
278         saveAction = new SaveDatabaseAction(this);
279         
280         actions.put("undo", undoAction);
281         actions.put("redo", redoAction);
282
283         // The action for opening an entry editor.
284         actions.put("edit", new BaseAction() {
285             public void action() {
286                 selectionListener.editSignalled();
287             }
288                 /*
289                   if (isShowingEditor()) {
290                       new FocusRequester(splitPane.getBottomComponent());
291                       return;
292                   }
293
294                   frame.block();
295                 //(new Thread() {
296                 //public void run() {
297                 int clickedOn = -1;
298                 // We demand that one and only one row is selected.
299                 if (entryTable.getSelectedRowCount() == 1) {
300                   clickedOn = entryTable.getSelectedRow();
301                 }
302                 if (clickedOn >= 0) {
303                   String id = tableModel.getIdForRow(clickedOn);
304                   BibtexEntry be = database.getEntryById(id);
305                   showEntry(be);
306
307                   if (splitPane.getBottomComponent() != null) {
308                       new FocusRequester(splitPane.getBottomComponent());
309                   }
310
311                 }
312         frame.unblock();
313               }
314                 */
315             });
316
317
318         actions.put("test", new BaseAction () {
319                 public void action() throws Throwable {
320
321                     ArrayList<BibtexEntry> entries = new ArrayList<BibtexEntry>();
322                     BibtexEntry[] sel = getSelectedEntries();
323                     for (int i = 0; i < sel.length; i++) {
324                         BibtexEntry bibtexEntry = sel[i];
325                         entries.add(bibtexEntry);
326                     }
327                     final List<FileListEntry> links =
328                             AccessLinksForEntries.getExternalLinksForEntries(entries);
329                     for (Iterator<FileListEntry> iterator = links.iterator(); iterator.hasNext();) {
330                         FileListEntry entry = iterator.next();
331                         System.out.println("Link: "+entry.getLink());
332                     };
333
334                     final JProgressBar prog = new JProgressBar();
335                     prog.setIndeterminate(true);
336                     final JDialog diag = new JDialog(frame, false);
337                     diag.getContentPane().add(prog, BorderLayout.CENTER);
338                     diag.pack();
339                     diag.setLocationRelativeTo(frame);
340                     diag.setVisible(true);
341                     Thread t = new Thread(new Runnable() {
342                         public void run() {
343                             AccessLinksForEntries.copyExternalLinksToDirectory(links,
344                                 new File("/home/alver/tmp"), metaData, prog, false,
345                                     new ActionListener() {
346                                         public void actionPerformed(ActionEvent actionEvent) {
347                                             diag.dispose();
348                                         }
349                                     });
350                         }
351                     });
352                     t.start();
353                     
354                     //CheckBoxFileChooser cb = new CheckBoxFileChooser(new File(""), "Selected only");
355                     //cb.showSaveDialog(frame);
356
357                     //ExternalFileTypeEditor efte = new ExternalFileTypeEditor(frame);
358                     //efte.setVisible(true);
359
360                     /*NamedCompound ce = Util.upgradePdfPsToFile(database,
361                             new String[] {"pdf", "ps"});
362                     undoManager.addEdit(ce);
363                     markBaseChanged();*/
364                 }
365             });
366
367
368         // The action for saving a database.
369         actions.put("save", saveAction);
370
371         actions.put("saveAs", new BaseAction() {
372             public void action() throws Throwable {
373                 saveAction.saveAs();
374             }
375         });
376
377         actions.put("saveSelectedAs", new BaseAction () {
378                 public void action() throws Throwable {
379
380                   String chosenFile = Globals.getNewFile(frame, new File(Globals.prefs.get("workingDirectory")), ".bib",
381                                                          JFileChooser.SAVE_DIALOG, false);
382                   if (chosenFile != null) {
383                     File expFile = new File(chosenFile);
384                     if (!expFile.exists() ||
385                         (JOptionPane.showConfirmDialog
386                          (frame, "'"+expFile.getName()+"' "+
387                           Globals.lang("exists. Overwrite file?"),
388                           Globals.lang("Save database"), JOptionPane.OK_CANCEL_OPTION)
389                          == JOptionPane.OK_OPTION)) {
390
391                       saveDatabase(expFile, true, Globals.prefs.get("defaultEncoding"));
392                       //runCommand("save");
393                       frame.getFileHistory().newFile(expFile.getPath());
394                       frame.output(Globals.lang("Saved selected to")+" '"
395                                    +expFile.getPath()+"'.");
396                         }
397                     }
398                 }
399             });
400     
401         // The action for copying selected entries.
402         actions.put("copy", new BaseAction() {
403                 public void action() {
404                     BibtexEntry[] bes = mainTable.getSelectedEntries();
405
406                     if ((bes != null) && (bes.length > 0)) {
407                         TransferableBibtexEntry trbe
408                             = new TransferableBibtexEntry(bes);
409                         // ! look at ClipBoardManager
410                         Toolkit.getDefaultToolkit().getSystemClipboard()
411                             .setContents(trbe, BasePanel.this);
412                         output(Globals.lang("Copied")+" "+(bes.length>1 ? bes.length+" "
413                                                            +Globals.lang("entries")
414                                                            : "1 "+Globals.lang("entry")+"."));
415                     } else {
416                         // The user maybe selected a single cell.
417                         int[] rows = mainTable.getSelectedRows(),
418                             cols = mainTable.getSelectedColumns();
419                         if ((cols.length == 1) && (rows.length == 1)) {
420                             // Copy single value.
421                             Object o = mainTable.getValueAt(rows[0], cols[0]);
422                             if (o != null) {
423                                 StringSelection ss = new StringSelection(o.toString());
424                                 Toolkit.getDefaultToolkit().getSystemClipboard()
425                                     .setContents(ss, BasePanel.this);
426
427                                 output(Globals.lang("Copied cell contents")+".");
428                             }
429                         }
430                     }
431                 }
432             });
433
434         actions.put("cut", new BaseAction() {
435                 public void action() throws Throwable {
436                     runCommand("copy");
437                     BibtexEntry[] bes = mainTable.getSelectedEntries();
438                     //int row0 = mainTable.getSelectedRow();
439                     if ((bes != null) && (bes.length > 0)) {
440                         // Create a CompoundEdit to make the action undoable.
441                         NamedCompound ce = new NamedCompound
442                         (Globals.lang(bes.length > 1 ? "cut entries" : "cut entry"));
443                         // Loop through the array of entries, and delete them.
444                         for (int i=0; i<bes.length; i++) {
445                             database.removeEntry(bes[i].getId());
446                             ensureNotShowing(bes[i]);
447                             ce.addEdit(new UndoableRemoveEntry
448                                        (database, bes[i], BasePanel.this));
449                         }
450                         //entryTable.clearSelection();
451                         frame.output(Globals.lang("Cut_pr")+" "+
452                                      (bes.length>1 ? bes.length
453                                       +" "+ Globals.lang("entries")
454                                       : Globals.lang("entry"))+".");
455                         ce.end();
456                         undoManager.addEdit(ce);
457                         markBaseChanged();
458
459                         // Reselect the entry in the first prev. selected position:
460                         /*if (row0 >= entryTable.getRowCount())
461                             row0 = entryTable.getRowCount()-1;
462                         if (row0 >= 0)
463                             entryTable.addRowSelectionInterval(row0, row0);*/
464                     }
465                 }
466             });
467
468         actions.put("delete", new BaseAction() {
469                 public void action() {
470                   boolean cancelled = false;
471                   BibtexEntry[] bes = mainTable.getSelectedEntries();
472                   int row0 = mainTable.getSelectedRow();
473                   if ((bes != null) && (bes.length > 0)) {
474
475                       boolean goOn = showDeleteConfirmationDialog(bes.length);
476                       if (!goOn) {
477                           return;
478                       }
479                       else {
480                           // Create a CompoundEdit to make the action undoable.
481                           NamedCompound ce = new NamedCompound
482                               (Globals.lang(bes.length > 1 ? "delete entries" : "delete entry"));
483                           // Loop through the array of entries, and delete them.
484                           for (int i = 0; i < bes.length; i++) {
485                               database.removeEntry(bes[i].getId());
486                               ensureNotShowing(bes[i]);
487                               ce.addEdit(new UndoableRemoveEntry(database, bes[i], BasePanel.this));
488                           }
489                           markBaseChanged();
490                           frame.output(Globals.lang("Deleted") + " " +
491                                        (bes.length > 1 ? bes.length
492                                         + " " + Globals.lang("entries")
493                                         : Globals.lang("entry")) + ".");
494                           ce.end();
495                           undoManager.addEdit(ce);
496                           //entryTable.clearSelection();
497                       }
498
499
500                           // Reselect the entry in the first prev. selected position:
501                           /*if (row0 >= entryTable.getRowCount())
502                               row0 = entryTable.getRowCount()-1;
503                           if (row0 >= 0) {
504                              final int toSel = row0;
505                             //
506                               SwingUtilities.invokeLater(new Runnable() {
507                                 public void run() {
508                                     entryTable.addRowSelectionInterval(toSel, toSel);
509                                     //entryTable.ensureVisible(toSel);
510                                 }
511                               });
512                             */
513                           }
514
515                       }
516
517             });
518
519         // The action for pasting entries or cell contents.
520         // Edited by Seb Wills <saw27@mrao.cam.ac.uk> on 14-Apr-04:
521         //  - more robust detection of available content flavors (doesn't only look at first one offered)
522         //  - support for parsing string-flavor clipboard contents which are bibtex entries.
523         //    This allows you to (a) paste entire bibtex entries from a text editor, web browser, etc
524         //                       (b) copy and paste entries between multiple instances of JabRef (since
525         //         only the text representation seems to get as far as the X clipboard, at least on my system)
526         actions.put("paste", new BaseAction() {
527                 public void action() {
528                     // Get clipboard contents, and see if TransferableBibtexEntry is among the content flavors offered
529                     Transferable content = Toolkit.getDefaultToolkit()
530                         .getSystemClipboard().getContents(null);
531                     if (content != null) {
532                         BibtexEntry[] bes = null;
533                         if (content.isDataFlavorSupported(TransferableBibtexEntry.entryFlavor)) {
534                             // We have determined that the clipboard data is a set of entries.
535                             try {
536                                 bes = (BibtexEntry[])(content.getTransferData(TransferableBibtexEntry.entryFlavor));
537
538                             } catch (UnsupportedFlavorException ex) {
539                                 ex.printStackTrace();
540                             } catch (IOException ex) {
541                                 ex.printStackTrace();
542                             }
543                         } else if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
544                             // We have determined that no TransferableBibtexEntry is available, but
545                             // there is a string, which we will handle according to context:
546                             int[] rows = mainTable.getSelectedRows();
547                                 //cols = entryTable.getSelectedColumns();
548                             //Util.pr(rows.length+" x "+cols.length);
549                             /*if ((cols != null) && (cols.length == 1) && (cols[0] != 0)
550                                 && (rows != null) && (rows.length == 1)) {
551                                 // A single cell is highlighted, so paste the string straight into it without parsing
552                                 try {
553                                     tableModel.setValueAt((String)(content.getTransferData(DataFlavor.stringFlavor)), rows[0], cols[0]);
554                                     refreshTable();
555                                     markBaseChanged();
556                                     output("Pasted cell contents");
557                                 } catch (UnsupportedFlavorException ex) {
558                                     ex.printStackTrace();
559                                 } catch (IOException ex) {
560                                     ex.printStackTrace();
561                                 } catch (IllegalArgumentException ex) {
562                                     output("Can't paste.");
563                                 }
564                             } else {*/
565                               // no single cell is selected, so try parsing the clipboard contents as bibtex entries instead
566                               try {
567                                   BibtexParser bp = new BibtexParser
568                                       (new java.io.StringReader( (String) (content.getTransferData(
569                                       DataFlavor.stringFlavor))));
570                                   BibtexDatabase db = bp.parse().getDatabase();
571                                   Util.pr("Parsed " + db.getEntryCount() + " entries from clipboard text");
572                                   if(db.getEntryCount()>0) {
573                                       Set keySet = db.getKeySet();
574                                       if (keySet != null) {
575                                           // Copy references to the entries into a BibtexEntry array.
576                                           // Could import directly from db, but going via bes allows re-use
577                                           // of the same pasting code as used for TransferableBibtexEntries
578                                           bes = new BibtexEntry[db.getEntryCount()];
579                                           Iterator it = keySet.iterator();
580                                           for (int i=0; it.hasNext();i++) {
581                                               bes[i]=db.getEntryById((String) (it.next()));
582                                           }
583                                       }
584                                   } /*else {
585                                     String cont = (String)(content.getTransferData(DataFlavor.stringFlavor));
586                                     Util.pr("----------------\n"+cont+"\n---------------------");
587                                     TextAnalyzer ta = new TextAnalyzer(cont);
588                                       output(Globals.lang("Unable to parse clipboard text as Bibtex entries."));
589                                       }*/
590                               } catch (UnsupportedFlavorException ex) {
591                                   ex.printStackTrace();
592                               } catch (Throwable ex) {
593                                   ex.printStackTrace();
594                               }
595
596                         }
597
598                         // finally we paste in the entries (if any), which either came from TransferableBibtexEntries
599                         // or were parsed from a string
600                         if ((bes != null) && (bes.length > 0)) {
601
602                           NamedCompound ce = new NamedCompound
603                               (Globals.lang(bes.length > 1 ? "paste entries" : "paste entry"));
604                           for (int i=0; i<bes.length; i++) {
605                             try {
606                               BibtexEntry be = (BibtexEntry)(bes[i].clone());
607                                 Util.setAutomaticFields(be,
608                                         Globals.prefs.getBoolean("overwriteOwner"),
609                                         Globals.prefs.getBoolean("overwriteTimeStamp"));
610
611                               // We have to clone the
612                               // entries, since the pasted
613                               // entries must exist
614                               // independently of the copied
615                               // ones.
616                               be.setId(Util.createNeutralId());
617                               database.insertEntry(be);
618                               ce.addEdit(new UndoableInsertEntry
619                                          (database, be, BasePanel.this));
620                             } catch (KeyCollisionException ex) {
621                               Util.pr("KeyCollisionException... this shouldn't happen.");
622                             }
623                           }
624                           ce.end();
625                           undoManager.addEdit(ce);
626                           //entryTable.clearSelection();
627                           //entryTable.revalidate();
628                           output(Globals.lang("Pasted")+" "+
629                                  (bes.length>1 ? bes.length+" "+
630                                   Globals.lang("entries") : "1 "+Globals.lang("entry"))
631                                  +".");
632                           markBaseChanged();
633                         }
634                       }
635
636                     }
637
638 });
639
640         actions.put("selectAll", new BaseAction() {
641                 public void action() {
642                     mainTable.selectAll();
643                 }
644             });
645
646         // The action for opening the preamble editor
647         actions.put("editPreamble", new BaseAction() {
648                 public void action() {
649                     if (preambleEditor == null) {
650                         PreambleEditor form = new PreambleEditor
651                             (frame, BasePanel.this, database, Globals.prefs);
652                         Util.placeDialog(form, frame);
653                         form.setVisible(true);
654                         preambleEditor = form;
655                     } else {
656                         preambleEditor.setVisible(true);
657                     }
658
659                 }
660             });
661
662         // The action for opening the string editor
663         actions.put("editStrings", new BaseAction() {
664                 public void action() {
665                     if (stringDialog == null) {
666                         StringDialog form = new StringDialog
667                             (frame, BasePanel.this, database, Globals.prefs);
668                         Util.placeDialog(form, frame);
669                         form.setVisible(true);
670                         stringDialog = form;
671                     } else {
672                         stringDialog.setVisible(true);
673                     }
674
675                 }
676             });
677
678         // The action for toggling the groups interface
679         actions.put("toggleGroups", new BaseAction() {
680             public void action() {
681               sidePaneManager.toggle("groups");
682               frame.groupToggle.setSelected(sidePaneManager.isComponentVisible("groups"));
683             }
684         });
685
686
687         // The action for auto-generating keys.
688         actions.put("makeKey", new AbstractWorker() {
689         //int[] rows;
690         List entries;
691         int numSelected;
692         boolean cancelled = false;
693
694         // Run first, in EDT:
695         public void init() {
696
697                     entries = new ArrayList(Arrays.asList(getSelectedEntries()));
698                     //rows = entryTable.getSelectedRows() ;
699                     numSelected = entries.size();
700
701                     if (entries.size() == 0) { // None selected. Inform the user to select entries first.
702                         JOptionPane.showMessageDialog(frame, Globals.lang("First select the entries you want keys to be generated for."),
703                                                       Globals.lang("Autogenerate BibTeX key"), JOptionPane.INFORMATION_MESSAGE);
704                         return ;
705                     }
706             frame.block();
707             output(Globals.lang("Generating BibTeX key for")+" "+
708                            numSelected+" "+(numSelected>1 ? Globals.lang("entries")
709                                             : Globals.lang("entry"))+"...");
710         }
711
712         // Run second, on a different thread:
713                 public void run() {
714                     BibtexEntry bes = null ;
715                     NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
716                     //BibtexEntry be;
717                     Object oldValue;
718                     boolean hasShownWarning = false;
719                     // First check if any entries have keys set already. If so, possibly remove
720                     // them from consideration, or warn about overwriting keys.
721                     loop: for (Iterator i=entries.iterator(); i.hasNext();) {
722                         bes = (BibtexEntry)i.next();
723                         if (bes.getField(BibtexFields.KEY_FIELD) != null) {
724                             if (Globals.prefs.getBoolean("avoidOverwritingKey"))
725                                 // Rmove the entry, because its key is already set:
726                                 i.remove();
727                             else if (Globals.prefs.getBoolean("warnBeforeOverwritingKey")) {
728                                 // Ask if the user wants to cancel the operation:
729                                 CheckBoxMessage cbm = new CheckBoxMessage(Globals.lang("One or more keys will be overwritten. Continue?"),
730                                         Globals.lang("Disable this confirmation dialog"), false);
731                                 int answer = JOptionPane.showConfirmDialog(frame, cbm, Globals.lang("Overwrite keys"),
732                                         JOptionPane.YES_NO_OPTION);
733                                 if (cbm.isSelected())
734                                     Globals.prefs.putBoolean("warnBeforeOverwritingKey", false);
735                                 if (answer == JOptionPane.NO_OPTION) {
736                                     // Ok, break off the operation.
737                                     cancelled = true;
738                                     return;
739                                 }
740                                 // No need to check more entries, because the user has already confirmed
741                                 // that it's ok to overwrite keys:
742                                 break loop;
743                             }
744                         }
745                     }
746
747                     HashMap oldvals = new HashMap();
748                     // Iterate again, removing already set keys. This is skipped if overwriting
749                     // is disabled, since all entries with keys set will have been removed.
750                     if (!Globals.prefs.getBoolean("avoidOverwritingKey")) for (Iterator i=entries.iterator(); i.hasNext();) {
751                         bes = (BibtexEntry)i.next();
752                         // Store the old value:
753                         oldvals.put(bes, bes.getField(BibtexFields.KEY_FIELD));
754                         database.setCiteKeyForEntry(bes.getId(), null);
755                     }
756
757                     // Finally, set the new keys:
758                     for (Iterator i=entries.iterator(); i.hasNext();) {
759                         bes = (BibtexEntry)i.next();
760                         bes = LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, bes);
761                         ce.addEdit(new UndoableKeyChange
762                                    (database, bes.getId(), (String)oldvals.get(bes),
763                                     (String)bes.getField(BibtexFields.KEY_FIELD)));
764                     }
765                     ce.end();
766                     undoManager.addEdit(ce);
767         }
768
769         // Run third, on EDT:
770         public void update() {
771             if (cancelled) {
772                 frame.unblock();
773                 return;
774             }
775             markBaseChanged() ;
776             numSelected = entries.size();
777             output(Globals.lang("Generated BibTeX key for")+" "+
778                numSelected+" "+(numSelected!=1 ? Globals.lang("entries")
779                                     : Globals.lang("entry")));
780             frame.unblock();
781         }
782     });
783
784         actions.put("search", new BaseAction() {
785                 public void action() {
786                     //sidePaneManager.togglePanel("search");
787                     sidePaneManager.show("search");
788                     //boolean on = sidePaneManager.isPanelVisible("search");
789                     frame.searchToggle.setSelected(true);
790                     if (true)
791                       frame.searchManager.startSearch();
792                 }
793             });
794
795         actions.put("toggleSearch", new BaseAction() {
796                 public void action() {
797                     //sidePaneManager.togglePanel("search");
798                     sidePaneManager.toggle("search");
799                     boolean on = sidePaneManager.isComponentVisible("search");
800                     frame.searchToggle.setSelected(on);
801                     if (on)
802                       frame.searchManager.startSearch();
803                 }
804             });
805
806         actions.put("incSearch", new BaseAction() {
807                 public void action() {
808                     sidePaneManager.show("search");
809                     frame.searchToggle.setSelected(true);
810                     frame.searchManager.startIncrementalSearch();
811                 }
812             });
813
814         // The action for copying the selected entry's key.
815         actions.put("copyKey", new BaseAction() {
816                 public void action() {
817                     BibtexEntry[] bes = mainTable.getSelectedEntries();
818                     if ((bes != null) && (bes.length > 0)) {
819                         storeCurrentEdit();
820                         //String[] keys = new String[bes.length];
821                         Vector keys = new Vector();
822                         // Collect all non-null keys.
823                         for (int i=0; i<bes.length; i++)
824                             if (bes[i].getField(BibtexFields.KEY_FIELD) != null)
825                                 keys.add(bes[i].getField(BibtexFields.KEY_FIELD));
826                         if (keys.size() == 0) {
827                             output("None of the selected entries have BibTeX keys.");
828                             return;
829                         }
830                         StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
831                         for (int i=1; i<keys.size(); i++) {
832                             sb.append(',');
833                             sb.append((String)keys.elementAt(i));
834                         }
835
836                         StringSelection ss = new StringSelection(sb.toString());
837                         Toolkit.getDefaultToolkit().getSystemClipboard()
838                             .setContents(ss, BasePanel.this);
839
840                         if (keys.size() == bes.length)
841                             // All entries had keys.
842                             output(Globals.lang((bes.length > 1) ? "Copied keys"
843                                                 : "Copied key")+".");
844                         else
845                             output(Globals.lang("Warning")+": "+(bes.length-keys.size())
846                                    +" "+Globals.lang("out of")+" "+bes.length+" "+
847                                    Globals.lang("entries have undefined BibTeX key")+".");
848                     }
849                 }
850             });
851
852         // The action for copying a cite for the selected entry.
853         actions.put("copyCiteKey", new BaseAction() {
854                 public void action() {
855                     BibtexEntry[] bes = mainTable.getSelectedEntries();
856                     if ((bes != null) && (bes.length > 0)) {
857                         storeCurrentEdit();
858                         //String[] keys = new String[bes.length];
859                         Vector keys = new Vector();
860                         // Collect all non-null keys.
861                         for (int i=0; i<bes.length; i++)
862                             if (bes[i].getField(BibtexFields.KEY_FIELD) != null)
863                                 keys.add(bes[i].getField(BibtexFields.KEY_FIELD));
864                         if (keys.size() == 0) {
865                             output("None of the selected entries have BibTeX keys.");
866                             return;
867                         }
868                         StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
869                         for (int i=1; i<keys.size(); i++) {
870                             sb.append(',');
871                             sb.append((String)keys.elementAt(i));
872                         }
873
874                         StringSelection ss = new StringSelection
875                             ("\\cite{"+sb.toString()+"}");
876                         Toolkit.getDefaultToolkit().getSystemClipboard()
877                             .setContents(ss, BasePanel.this);
878
879                         if (keys.size() == bes.length)
880                             // All entries had keys.
881                             output(Globals.lang((bes.length > 1) ? "Copied keys"
882                                                 : "Copied key")+".");
883                         else
884                             output(Globals.lang("Warning")+": "+(bes.length-keys.size())
885                                    +" "+Globals.lang("out of")+" "+bes.length+" "+
886                                    Globals.lang("entries have undefined BibTeX key")+".");
887                     }
888                 }
889             });
890
891           actions.put("mergeDatabase", new AppendDatabaseAction(frame, this));
892
893
894         actions.put("openFile", new BaseAction() {
895             public void action() {
896                 (new Thread() {
897                     public void run() {
898                         BibtexEntry[] bes = mainTable.getSelectedEntries();
899                         String field = "ps";
900                         if ((bes != null) && (bes.length == 1)) {
901                             Object link = bes[0].getField("ps");
902                             if (bes[0].getField("pdf") != null) {
903                                 link = bes[0].getField("pdf");
904                                 field = "pdf";
905                             }
906                             String filepath = null;
907                             if (link != null) {
908                                 filepath = link.toString();
909                             } else {
910
911                                 // see if we can fall back to a filename based on the bibtex key
912                                 String basefile;
913                                 Object key = bes[0].getField(BibtexFields.KEY_FIELD);
914                                 if (key != null) {
915                                     basefile = key.toString();
916                                     final String[] types = new String[]{"pdf", "ps"};
917                                     final String sep = System.getProperty("file.separator");
918                                     for (int i = 0; i < types.length; i++) {
919                                         String dir = Globals.prefs.get(types[i] + "Directory");
920                                         if (dir != null) {
921                                             if (dir.endsWith(sep)) {
922                                                 dir = dir.substring(0, dir.length() - sep.length());
923                                             }
924                                         } else
925                                             dir = "";
926                                         String found = Util.findPdf(basefile, types[i], dir, new OpenFileFilter("." + types[i]));
927                                         if (found != null) {
928                                             filepath = dir + sep + found;
929                                             break;
930                                         }
931                                     }
932                                 }
933                             }
934
935
936                             if (filepath != null) {
937                                 //output(Globals.lang("Calling external viewer..."));
938                                 try {
939                                     Util.openExternalViewer(metaData(), filepath, field);
940                                     output(Globals.lang("External viewer called") + ".");
941                                 }
942                                 catch (IOException ex) {
943                                     output(Globals.lang("Error") + ": " + ex.getMessage());
944                                 }
945                             } else
946                                 output(Globals.lang(
947                                         "No pdf or ps defined, and no file matching Bibtex key found") +
948                                         ".");
949                         } else
950                             output(Globals.lang("No entries or multiple entries selected."));
951                     }
952                 }).start();
953             }
954         });
955
956         actions.put("openExternalFile", new BaseAction() {
957             public void action() {
958                 (new Thread() {
959                     public void run() {
960                         BibtexEntry[] bes = mainTable.getSelectedEntries();
961                         String field = GUIGlobals.FILE_FIELD;
962                         if ((bes != null) && (bes.length == 1)) {
963                             Object link = bes[0].getField(field);
964                             if (link == null) {
965                                 runCommand("openFile"); // Fall back on PDF/PS fields???
966                                 return;
967                             }
968                             FileListTableModel tableModel = new FileListTableModel();
969                             tableModel.setContent((String)link);
970                             if (tableModel.getRowCount() == 0) {
971                                 runCommand("openFile"); // Fall back on PDF/PS fields???
972                                 return;
973                             }
974                             FileListEntry flEntry = tableModel.getEntry(0);
975                             ExternalFileMenuItem item = new ExternalFileMenuItem
976                                 (frame(), bes[0], "",
977                                 flEntry.getLink(), flEntry.getType().getIcon(),
978                                 metaData(), flEntry.getType());
979                             item.actionPerformed(null);
980                         } else
981                             output(Globals.lang("No entries or multiple entries selected."));
982                     }
983                 }).start();
984             }
985         });
986
987
988         actions.put("openUrl", new BaseAction() {
989                       public void action() {
990                           BibtexEntry[] bes = mainTable.getSelectedEntries();
991                           String field = "doi";
992                           if ((bes != null) && (bes.length == 1)) {
993                               Object link = bes[0].getField("doi");
994                               if (bes[0].getField("url") != null) {
995                                 link = bes[0].getField("url");
996                                 field = "url";
997                               }
998                               if (link != null) {
999                                 //output(Globals.lang("Calling external viewer..."));
1000                                 try {
1001                                   Util.openExternalViewer(metaData(), link.toString(), field);
1002                                   output(Globals.lang("External viewer called")+".");
1003                                 } catch (IOException ex) {
1004                                     output(Globals.lang("Error") + ": " + ex.getMessage());
1005                                 }
1006                               }
1007                               else
1008                                   output(Globals.lang("No url defined")+".");
1009                           } else
1010                             output(Globals.lang("No entries or multiple entries selected."));
1011                       }
1012               });
1013
1014           actions.put("replaceAll", new BaseAction() {
1015                     public void action() {
1016                       ReplaceStringDialog rsd = new ReplaceStringDialog(frame);
1017                       rsd.setVisible(true);
1018                       if (!rsd.okPressed())
1019                           return;
1020                       int counter = 0;
1021                       NamedCompound ce = new NamedCompound(Globals.lang("Replace string"));
1022                       if (!rsd.selOnly()) {
1023                           for (Iterator i=database.getKeySet().iterator();
1024                                i.hasNext();)
1025                               counter += rsd.replace(database.getEntryById((String)i.next()), ce);
1026                       } else {
1027                           BibtexEntry[] bes = mainTable.getSelectedEntries();
1028                           for (int i=0; i<bes.length; i++)
1029                               counter += rsd.replace(bes[i], ce);
1030                       }
1031
1032                       output(Globals.lang("Replaced")+" "+counter+" "+
1033                              Globals.lang(counter==1?"occurence":"occurences")+".");
1034                       if (counter > 0) {
1035                           ce.end();
1036                           undoManager.addEdit(ce);
1037                           markBaseChanged();
1038                       }
1039                   }
1040               });
1041
1042               actions.put("dupliCheck", new BaseAction() {
1043                 public void action() {
1044                   DuplicateSearch ds = new DuplicateSearch(BasePanel.this);
1045                   ds.start();
1046                 }
1047               });
1048
1049               /*actions.put("strictDupliCheck", new BaseAction() {
1050                 public void action() {
1051                   StrictDuplicateSearch ds = new StrictDuplicateSearch(BasePanel.this);
1052                   ds.start();
1053                 }
1054               });*/
1055
1056               actions.put("plainTextImport", new BaseAction() {
1057                 public void action()
1058                 {
1059                   // get Type of new entry
1060                   EntryTypeDialog etd = new EntryTypeDialog(frame);
1061                   Util.placeDialog(etd, BasePanel.this);
1062                   etd.setVisible(true);
1063                   BibtexEntryType tp = etd.getChoice();
1064                   if (tp == null)
1065                     return;
1066
1067                   String id = Util.createNeutralId();
1068                   BibtexEntry bibEntry = new BibtexEntry(id, tp) ;
1069                   TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1070                                                                  "import", true,
1071                                                                  bibEntry) ;
1072                   Util.placeDialog(tidialog, BasePanel.this);
1073                   tidialog.setVisible(true);
1074
1075                   if (tidialog.okPressed())
1076                   {
1077                       Util.setAutomaticFields(Arrays.asList(new BibtexEntry[] {bibEntry}),
1078                               false, false);
1079                     insertEntry(bibEntry) ;
1080                   }
1081                 }
1082               });
1083
1084               // The action starts the "import from plain text" dialog
1085               /*actions.put("importPlainText", new BaseAction() {
1086                       public void action()
1087                       {
1088                         BibtexEntry bibEntry = null ;
1089                         // try to get the first marked entry
1090                         BibtexEntry[] bes = entryTable.getSelectedEntries();
1091                         if ((bes != null) && (bes.length > 0))
1092                           bibEntry = bes[0] ;
1093
1094                         if (bibEntry != null)
1095                         {
1096                           // Create an UndoableInsertEntry object.
1097                           undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1098
1099                           TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1100                                                                          "import", true,
1101                                                                          bibEntry) ;
1102                           Util.placeDialog(tidialog, BasePanel.this);
1103                           tidialog.setVisible(true);
1104
1105                           if (tidialog.okPressed())
1106                           {
1107                             output(Globals.lang("changed ")+" '"
1108                                    +bibEntry.getType().getName().toLowerCase()+"' "
1109                                    +Globals.lang("entry")+".");
1110                             refreshTable();
1111                             int row = tableModel.getNumberFromName(bibEntry.getId());
1112
1113                             entryTable.clearSelection();
1114                             entryTable.scrollTo(row);
1115                             markBaseChanged(); // The database just changed.
1116                             if (Globals.prefs.getBoolean("autoOpenForm"))
1117                             {
1118                                   showEntry(bibEntry);
1119                             }
1120                           }
1121                         }
1122                       }
1123                   });
1124                 */
1125               actions.put("markEntries", new AbstractWorker() {
1126                   private int besLength = -1;
1127                 public void run() {
1128
1129                   NamedCompound ce = new NamedCompound(Globals.lang("Mark entries"));
1130                   BibtexEntry[] bes = mainTable.getSelectedEntries();
1131                   besLength = bes.length;
1132           if (bes == null)
1133               return;
1134                   for (int i=0; i<bes.length; i++) {
1135                       Util.markEntry(bes[i], ce);
1136                   }
1137                   ce.end();
1138                   undoManager.addEdit(ce);
1139                 }
1140
1141                 public void update() {
1142                   markBaseChanged();
1143                   output(Globals.lang("Marked selected")+" "+Globals.lang(besLength>0?"entry":"entries"));
1144
1145                 }
1146               });
1147
1148               actions.put("unmarkEntries", new BaseAction() {
1149                 public void action() {
1150                     try {
1151                   NamedCompound ce = new NamedCompound(Globals.lang("Unmark entries"));
1152                   BibtexEntry[] bes = mainTable.getSelectedEntries();
1153           if (bes == null)
1154               return;
1155                   for (int i=0; i<bes.length; i++) {
1156                       Util.unmarkEntry(bes[i], database, ce);
1157                   }
1158                   ce.end();
1159                   undoManager.addEdit(ce);
1160                   markBaseChanged();
1161                   output(Globals.lang("Unmarked selected")+" "+Globals.lang(bes.length>0?"entry":"entries"));
1162                     } catch (Throwable ex) { ex.printStackTrace(); }
1163                 }
1164               });
1165
1166               actions.put("unmarkAll", new BaseAction() {
1167                 public void action() {
1168                   NamedCompound ce = new NamedCompound(Globals.lang("Unmark all"));
1169                   Set keySet = database.getKeySet();
1170                   for (Iterator i = keySet.iterator(); i.hasNext(); ) {
1171                     BibtexEntry be = database.getEntryById( (String) i.next());
1172                     Util.unmarkEntry(be, database, ce);
1173
1174                   }
1175                   ce.end();
1176                   undoManager.addEdit(ce);
1177                   markBaseChanged();
1178                 }
1179               });
1180
1181               actions.put("togglePreview", new BaseAction() {
1182                       public void action() {
1183                           boolean enabled = !Globals.prefs.getBoolean("previewEnabled");
1184                           Globals.prefs.putBoolean("previewEnabled", enabled);
1185                           frame.setPreviewActive(enabled);
1186                           frame.previewToggle.setSelected(enabled);
1187                       }
1188                   });
1189
1190               actions.put("toggleHighlightGroupsMatchingAny", new BaseAction() {
1191                 public void action() {
1192                     boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAny");
1193                     Globals.prefs.putBoolean("highlightGroupsMatchingAny", enabled);
1194                     frame.highlightAny.setSelected(enabled);
1195                     if (enabled) {
1196                         frame.highlightAll.setSelected(false);
1197                         Globals.prefs.putBoolean("highlightGroupsMatchingAll", false);
1198                     }
1199                     // ping the listener so it updates:
1200                     groupsHighlightListener.listChanged(null);
1201                 }
1202               });
1203
1204               actions.put("toggleHighlightGroupsMatchingAll", new BaseAction() {
1205                   public void action() {
1206                       boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAll");
1207                       Globals.prefs.putBoolean("highlightGroupsMatchingAll", enabled);
1208                       frame.highlightAll.setSelected(enabled);
1209                       if (enabled) {
1210                           frame.highlightAny.setSelected(false);
1211                           Globals.prefs.putBoolean("highlightGroupsMatchingAny", false);
1212                       }
1213                       // ping the listener so it updates:
1214                       groupsHighlightListener.listChanged(null);
1215                   }
1216                 });
1217
1218               actions.put("switchPreview", new BaseAction() {
1219                       public void action() {
1220                           selectionListener.switchPreview();
1221                       }
1222                   });
1223
1224               actions.put("manageSelectors", new BaseAction() {
1225                       public void action() {
1226                           ContentSelectorDialog2 csd = new ContentSelectorDialog2
1227                               (frame, frame, BasePanel.this, false, metaData, null);
1228                           Util.placeDialog(csd, frame);
1229                           csd.setVisible(true);
1230                       }
1231                   });
1232
1233
1234           actions.put("exportToClipboard", new ExportToClipboardAction(frame, database()));
1235         
1236         actions.put("writeXMP", new WriteXMPAction(this));
1237         
1238         actions.put("abbreviateIso", new AbbreviateAction(this, true));
1239         actions.put("abbreviateMedline", new AbbreviateAction(this, false));
1240         actions.put("unabbreviate", new UnabbreviateAction(this));
1241         actions.put("autoSetPdf", new AutoSetExternalFileForEntries(this, "pdf"));
1242         actions.put("autoSetPs", new AutoSetExternalFileForEntries(this, "ps"));
1243         actions.put("autoSetFile", new SynchronizeFileField(this));
1244         actions.put("upgradeLinks", new UpgradeExternalLinks(this));
1245
1246     }
1247
1248     /**
1249      * This method is called from JabRefFrame is a database specific
1250      * action is requested by the user. Runs the command if it is
1251      * defined, or prints an error message to the standard error
1252      * stream.
1253      *
1254      * @param _command The name of the command to run.
1255     */
1256     public void runCommand(String _command) {
1257       final String command = _command;
1258       //(new Thread() {
1259       //  public void run() {
1260           if (actions.get(command) == null)
1261             Util.pr("No action defined for'" + command + "'");
1262             else {
1263         Object o = actions.get(command);
1264         try {
1265             if (o instanceof BaseAction)
1266             ((BaseAction)o).action();
1267             else {
1268             // This part uses Spin's features:
1269             Worker wrk = ((AbstractWorker)o).getWorker();
1270             // The Worker returned by getWorker() has been wrapped
1271             // by Spin.off(), which makes its methods be run in
1272             // a different thread from the EDT.
1273             CallBack clb = ((AbstractWorker)o).getCallBack();
1274
1275             ((AbstractWorker)o).init(); // This method runs in this same thread, the EDT.
1276             // Useful for initial GUI actions, like printing a message.
1277
1278             // The CallBack returned by getCallBack() has been wrapped
1279             // by Spin.over(), which makes its methods be run on
1280             // the EDT.
1281             wrk.run(); // Runs the potentially time-consuming action
1282             // without freezing the GUI. The magic is that THIS line
1283             // of execution will not continue until run() is finished.
1284             clb.update(); // Runs the update() method on the EDT.
1285             }
1286         } catch (Throwable ex) {
1287             // If the action has blocked the JabRefFrame before crashing, we need to unblock it.
1288             // The call to unblock will simply hide the glasspane, so there is no harm in calling
1289             // it even if the frame hasn't been blocked.
1290             frame.unblock();
1291             ex.printStackTrace();
1292         }
1293         }
1294       //  }
1295       //}).start();
1296     }
1297
1298     private boolean saveDatabase(File file, boolean selectedOnly, String encoding) throws SaveException {
1299         SaveSession session;
1300         frame.block();
1301         try {
1302             if (!selectedOnly)
1303                 session = FileActions.saveDatabase(database, metaData, file,
1304                                            Globals.prefs, false, false, encoding);
1305             else
1306                 session = FileActions.savePartOfDatabase(database, metaData, file,
1307                                                Globals.prefs, mainTable.getSelectedEntries(), encoding);
1308
1309         } catch (UnsupportedCharsetException ex2) {
1310             JOptionPane.showMessageDialog(frame, Globals.lang("Could not save file. "
1311                 +"Character encoding '%0' is not supported.", encoding),
1312                     Globals.lang("Save database"), JOptionPane.ERROR_MESSAGE);
1313             throw new SaveException("rt");
1314         } catch (SaveException ex) {
1315             if (ex.specificEntry()) {
1316                 // Error occured during processing of
1317                 // be. Highlight it:
1318                 int row = mainTable.findEntry(ex.getEntry()),
1319                     topShow = Math.max(0, row-3);
1320                 mainTable.setRowSelectionInterval(row, row);
1321                 mainTable.scrollTo(topShow);
1322                 showEntry(ex.getEntry());
1323             }
1324             else ex.printStackTrace();
1325
1326             JOptionPane.showMessageDialog
1327                 (frame, Globals.lang("Could not save file")
1328                  +".\n"+ex.getMessage(),
1329                  Globals.lang("Save database"),
1330                  JOptionPane.ERROR_MESSAGE);
1331             throw new SaveException("rt");
1332
1333         } finally {
1334             frame.unblock();
1335         }
1336
1337         boolean commit = true;
1338         if (!session.getWriter().couldEncodeAll()) {
1339             DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("left:pref, 4dlu, fill:pref", ""));
1340             JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters());
1341             ta.setEditable(false);
1342             builder.append(Globals.lang("The chosen encoding '%0' could not encode the following characters: ",
1343                       session.getEncoding()));
1344             builder.append(ta);
1345             builder.append(Globals.lang("What do you want to do?"));
1346             String tryDiff = Globals.lang("Try different encoding");
1347             int answer = JOptionPane.showOptionDialog(frame, builder.getPanel(), Globals.lang("Save database"),
1348                     JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
1349                     new String[] {Globals.lang("Save"), tryDiff, Globals.lang("Cancel")}, tryDiff);
1350
1351             if (answer == JOptionPane.NO_OPTION) {
1352                 // The user wants to use another encoding.
1353                 Object choice = JOptionPane.showInputDialog(frame, Globals.lang("Select encoding"), Globals.lang("Save database"),
1354                         JOptionPane.QUESTION_MESSAGE, null, Globals.ENCODINGS, encoding);
1355                 if (choice != null) {
1356                     String newEncoding = (String)choice;
1357                     return saveDatabase(file, selectedOnly, newEncoding);
1358                 } else
1359                     commit = false;
1360             } else if (answer == JOptionPane.CANCEL_OPTION)
1361                     commit = false;
1362
1363
1364           }
1365
1366         try {
1367             if (commit) {
1368                 session.commit();
1369                 this.encoding = encoding; // Make sure to remember which encoding we used.
1370             }
1371             else
1372                 session.cancel();
1373         } catch (IOException e) {
1374             e.printStackTrace();
1375         }
1376
1377         return commit;
1378     }
1379
1380
1381     /**
1382      * This method is called from JabRefFrame when the user wants to
1383      * create a new entry. If the argument is null, the user is
1384      * prompted for an entry type.
1385      *
1386      * @param type The type of the entry to create.
1387      * @return The newly created BibtexEntry or null the operation was canceled by the user.
1388      */
1389     public BibtexEntry newEntry(BibtexEntryType type) {
1390         if (type == null) {
1391             // Find out what type is wanted.
1392             EntryTypeDialog etd = new EntryTypeDialog(frame);
1393             // We want to center the dialog, to make it look nicer.
1394             Util.placeDialog(etd, frame);
1395             etd.setVisible(true);
1396             type = etd.getChoice();
1397         }
1398         if (type != null) { // Only if the dialog was not cancelled.
1399             String id = Util.createNeutralId();
1400             final BibtexEntry be = new BibtexEntry(id, type);
1401             try {
1402                 database.insertEntry(be);
1403
1404                 // Set owner/timestamp if options are enabled:
1405                 ArrayList list = new ArrayList();
1406                 list.add(be);
1407                 Util.setAutomaticFields(list, true, true);
1408
1409                 // Create an UndoableInsertEntry object.
1410                 undoManager.addEdit(new UndoableInsertEntry(database, be, BasePanel.this));
1411                 output(Globals.lang("Added new")+" '"+type.getName().toLowerCase()+"' "
1412                        +Globals.lang("entry")+".");
1413                 final int row = mainTable.findEntry(be);
1414
1415                 // We are going to select the new entry. Before that, make sure that we are in
1416                 // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR
1417                 // mode which makes sure the selection will trigger display of the entry editor
1418                 // and adjustment of the splitter.
1419                 if (mode != SHOWING_EDITOR) {
1420                     mode = WILL_SHOW_EDITOR;
1421                 }
1422
1423                 highlightEntry(be);  // Selects the entry. The selection listener will open the editor.
1424
1425                 markBaseChanged(); // The database just changed.
1426                 new FocusRequester(getEntryEditor(be));
1427                 return be;
1428             } catch (KeyCollisionException ex) {
1429                 Util.pr(ex.getMessage());
1430             }
1431         }
1432         return null;
1433     }
1434
1435
1436
1437     /**
1438      * This method is called from JabRefFrame when the user wants to
1439      * create a new entry.
1440      * @param bibEntry The new entry.
1441      */
1442     public void insertEntry(BibtexEntry bibEntry)
1443     {
1444       if (bibEntry != null)
1445       {
1446         try
1447         {
1448           database.insertEntry(bibEntry) ;
1449           if (Globals.prefs.getBoolean("useOwner"))
1450             // Set owner field to default value
1451             bibEntry.setField(BibtexFields.OWNER, Globals.prefs.get("defaultOwner") );
1452             // Create an UndoableInsertEntry object.
1453             undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1454             output(Globals.lang("Added new")+" '"
1455                    +bibEntry.getType().getName().toLowerCase()+"' "
1456                    +Globals.lang("entry")+".");
1457             int row = mainTable.findEntry(bibEntry);
1458
1459             mainTable.clearSelection();
1460             mainTable.scrollTo(row);
1461             markBaseChanged(); // The database just changed.
1462             if (Globals.prefs.getBoolean("autoOpenForm"))
1463             {
1464                   showEntry(bibEntry);
1465             }
1466         } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); }
1467       }
1468     }
1469
1470     public void createMainTable() {
1471         //Comparator comp = new FieldComparator("author");
1472
1473         GlazedEntrySorter eventList = new GlazedEntrySorter(database.getEntryMap(), null);
1474         // Must initialize sort columns somehow:
1475
1476         database.addDatabaseChangeListener(eventList);
1477         groupFilterList = new FilterList(eventList.getTheList(), NoSearchMatcher.INSTANCE);
1478         searchFilterList = new FilterList(groupFilterList, NoSearchMatcher.INSTANCE);
1479         //final SortedList sortedList = new SortedList(searchFilterList, null);
1480         MainTableFormat tableFormat = new MainTableFormat(this);
1481         tableFormat.updateTableFormat();
1482         //EventTableModel tableModel = new EventTableModel(sortedList, tableFormat);
1483         mainTable = new MainTable(/*tableModel, */tableFormat, searchFilterList, frame, this);
1484         
1485         selectionListener = new MainTableSelectionListener(this, mainTable);
1486         mainTable.updateFont();
1487         mainTable.addSelectionListener(selectionListener);
1488         mainTable.addMouseListener(selectionListener);
1489         mainTable.addKeyListener(selectionListener);
1490         mainTable.addFocusListener(selectionListener);
1491         
1492         // Add the listener that will take care of highlighting groups as the selection changes:
1493         groupsHighlightListener = new ListEventListener() {
1494             public void listChanged(ListEvent listEvent) {
1495                 if (Globals.prefs.getBoolean("highlightGroupsMatchingAny"))
1496                     getGroupSelector().showMatchingGroups(
1497                             mainTable.getSelectedEntries(), false);
1498                 else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll"))
1499                     getGroupSelector().showMatchingGroups(
1500                             mainTable.getSelectedEntries(), true);
1501                 else // no highlight
1502                     getGroupSelector().showMatchingGroups(null, true);
1503             }
1504         };
1505         mainTable.addSelectionListener(groupsHighlightListener);
1506
1507         mainTable.getActionMap().put("cut", new AbstractAction() {
1508                 public void actionPerformed(ActionEvent e) {
1509                     try { runCommand("cut");
1510                     } catch (Throwable ex) {
1511                         ex.printStackTrace();
1512                     }
1513                 }
1514             });
1515         mainTable.getActionMap().put("copy", new AbstractAction() {
1516                 public void actionPerformed(ActionEvent e) {
1517                     try { runCommand("copy");
1518                     } catch (Throwable ex) {
1519                         ex.printStackTrace();
1520                     }
1521                 }
1522             });
1523         mainTable.getActionMap().put("paste", new AbstractAction() {
1524                 public void actionPerformed(ActionEvent e) {
1525                     try { runCommand("paste");
1526                     } catch (Throwable ex) {
1527                         ex.printStackTrace();
1528                     }
1529                 }
1530             });
1531
1532         mainTable.addKeyListener(new KeyAdapter() {
1533
1534                 public void keyPressed(KeyEvent e) {
1535                     final int keyCode = e.getKeyCode();
1536                     final TreePath path = frame.groupSelector.getSelectionPath();
1537                     final GroupTreeNode node = path == null ? null : (GroupTreeNode) path.getLastPathComponent();
1538
1539                     if (e.isControlDown()) {
1540                         switch (keyCode) {
1541                         // The up/down/left/rightkeystrokes are displayed in the
1542                         // GroupSelector's popup menu, so if they are to be changed,
1543                         // edit GroupSelector.java accordingly!
1544                         case KeyEvent.VK_UP:
1545                             e.consume();
1546                             if (node != null)
1547                                 frame.groupSelector.moveNodeUp(node, true);
1548                             break;
1549                         case KeyEvent.VK_DOWN:
1550                             e.consume();
1551                             if (node != null)
1552                                 frame.groupSelector.moveNodeDown(node, true);
1553                             break;
1554                         case KeyEvent.VK_LEFT:
1555                             e.consume();
1556                             if (node != null)
1557                                 frame.groupSelector.moveNodeLeft(node, true);
1558                             break;
1559                         case KeyEvent.VK_RIGHT:
1560                             e.consume();
1561                             if (node != null)
1562                                 frame.groupSelector.moveNodeRight(node, true);
1563                             break;
1564                         case KeyEvent.VK_PAGE_DOWN:
1565                             frame.nextTab.actionPerformed(null);
1566                             e.consume();
1567                             break;
1568                         case KeyEvent.VK_PAGE_UP:
1569                             frame.prevTab.actionPerformed(null);
1570                             e.consume();
1571                             break;
1572                         }
1573                     } else if (keyCode == KeyEvent.VK_ENTER){
1574                         e.consume();
1575                         try { runCommand("edit");
1576                         } catch (Throwable ex) {
1577                             ex.printStackTrace();
1578                         }
1579                     }
1580                 }
1581         });
1582     }
1583
1584     public void setupMainPanel() {
1585         //System.out.println("setupMainPanel");
1586         //splitPane = new com.jgoodies.uif_lite.component.UIFSplitPane(JSplitPane.VERTICAL_SPLIT);
1587         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
1588         splitPane.setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
1589         // We replace the default FocusTraversalPolicy with a subclass
1590         // that only allows FieldEditor components to gain keyboard focus,
1591         // if there is an entry editor open.
1592         /*splitPane.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
1593                 protected boolean accept(Component c) {
1594                     Util.pr("jaa");
1595                     if (showing == null)
1596                         return super.accept(c);
1597                     else
1598                         return (super.accept(c) &&
1599                                 (c instanceof FieldEditor));
1600                 }
1601                 });*/
1602
1603         createMainTable();
1604
1605         splitPane.setTopComponent(mainTable.getPane());
1606
1607         //setupTable();
1608         // If an entry is currently being shown, make sure it stays shown,
1609         // otherwise set the bottom component to null.
1610         if (mode == SHOWING_PREVIEW) {
1611             mode = SHOWING_NOTHING;
1612             int row = mainTable.findEntry(currentPreview.entry);
1613             if (row >= 0)
1614                 mainTable.setRowSelectionInterval(row, row);
1615
1616         }
1617         else if (mode == SHOWING_EDITOR) {
1618             mode = SHOWING_NOTHING;
1619             /*int row = mainTable.findEntry(currentEditor.entry);
1620             if (row >= 0)
1621                 mainTable.setRowSelectionInterval(row, row);
1622             */
1623             //showEntryEditor(currentEditor);
1624         } else
1625             splitPane.setBottomComponent(null);
1626
1627
1628         setLayout(new BorderLayout());
1629         removeAll();
1630         add(splitPane, BorderLayout.CENTER);
1631
1632         // Set up AutoCompleters for this panel:
1633         if (Globals.prefs.getBoolean("autoComplete")) {
1634             instantiateAutoCompleters();
1635         }
1636
1637         splitPane.revalidate();
1638         revalidate();
1639         repaint();
1640     }
1641
1642     public HashMap getAutoCompleters() {
1643         return autoCompleters;
1644     }
1645     
1646     public AutoCompleter getAutoCompleter(String fieldName) {
1647         return (AutoCompleter)autoCompleters.get(fieldName);
1648     }
1649
1650     private void instantiateAutoCompleters() {
1651         autoCompleters.clear();
1652         String[] completeFields = Globals.prefs.getStringArray("autoCompleteFields");
1653         for (int i = 0; i < completeFields.length; i++) {
1654             String field = completeFields[i];
1655             autoCompleters.put(field, new AutoCompleter(field));
1656         }
1657         for (Iterator i=database.getKeySet().iterator(); i.hasNext();) {
1658             BibtexEntry entry = database.getEntryById((String)i.next());
1659             Util.updateCompletersForEntry(autoCompleters, entry);
1660         }
1661     }
1662
1663
1664     /**
1665      * This method is called after a database has been parsed. The
1666      * hashmap contains the contents of all comments in the .bib file
1667      * that started with the meta flag (GUIGlobals.META_FLAG).
1668      * In this method, the meta data are input to their respective
1669      * handlers.
1670      *
1671      * @param meta Metadata to input.
1672      */
1673     public void parseMetaData(HashMap meta) {
1674         metaData = new MetaData(meta,database());
1675
1676     }
1677
1678     /*
1679     public void refreshTable() {
1680         //System.out.println("hiding="+hidingNonHits+"\tlastHits="+lastSearchHits);
1681         // This method is called by EntryTypeForm when a field value is
1682         // stored. The table is scheduled for repaint.
1683         entryTable.assureNotEditing();
1684         //entryTable.invalidate();
1685         BibtexEntry[] bes = entryTable.getSelectedEntries();
1686     if (hidingNonHits)
1687         tableModel.update(lastSearchHits);
1688     else
1689         tableModel.update();
1690     //tableModel.remap();
1691         if ((bes != null) && (bes.length > 0))
1692             selectEntries(bes, 0);
1693
1694     //long toc = System.currentTimeMillis();
1695     //  Util.pr("Refresh took: "+(toc-tic)+" ms");
1696     } */
1697
1698     public void updatePreamble() {
1699         if (preambleEditor != null)
1700             preambleEditor.updatePreamble();
1701     }
1702
1703     public void assureStringDialogNotEditing() {
1704         if (stringDialog != null)
1705             stringDialog.assureNotEditing();
1706     }
1707
1708     public void updateStringDialog() {
1709         if (stringDialog != null)
1710             stringDialog.refreshTable();
1711     }
1712
1713     public void updateEntryPreviewToRow(BibtexEntry e) {
1714
1715     }
1716
1717     public void adjustSplitter() {
1718         int mode = getMode();
1719         if (mode == SHOWING_PREVIEW) {
1720             splitPane.setDividerLocation(splitPane.getHeight()-GUIGlobals.PREVIEW_PANEL_HEIGHT);
1721         } else {
1722             splitPane.setDividerLocation(GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1723
1724         }
1725     }
1726
1727
1728
1729     /**
1730      * Stores the source view in the entry editor, if one is open, has the source view
1731      * selected and the source has been edited.
1732      * @return boolean false if there is a validation error in the source panel, true otherwise.
1733      */
1734     public boolean entryEditorAllowsChange() {
1735       Component c = splitPane.getBottomComponent();
1736       if ((c != null) && (c instanceof EntryEditor)) {
1737         return ((EntryEditor)c).lastSourceAccepted();
1738       }
1739       else
1740         return true;
1741     }
1742
1743     public void moveFocusToEntryEditor() {
1744       Component c = splitPane.getBottomComponent();
1745       if ((c != null) && (c instanceof EntryEditor)) {
1746         new FocusRequester(c);
1747       }
1748     }
1749
1750     /**
1751      * Ensure that no preview is shown. Called when preview is turned off. Must chech if
1752      * a preview is in fact visible before doing anything rash.
1753      */
1754     public void hidePreview() {
1755         Globals.prefs.putBoolean("previewEnabled", false);
1756
1757       Component c = splitPane.getBottomComponent();
1758       if ((c != null) && !(c instanceof EntryEditor))
1759         splitPane.setBottomComponent(null);
1760     }
1761
1762     public boolean isShowingEditor() {
1763       return ((splitPane.getBottomComponent() != null)
1764               && (splitPane.getBottomComponent() instanceof EntryEditor));
1765     }
1766
1767     public void showEntry(final BibtexEntry be) {
1768         if (showing == be) {
1769             if (splitPane.getBottomComponent() == null) {
1770                 // This is the special occasion when showing is set to an
1771                 // entry, but no entry editor is in fact shown. This happens
1772                 // after Preferences dialog is closed, and it means that we
1773                 // must make sure the same entry is shown again. We do this by
1774                 // setting showing to null, and recursively calling this method.
1775                 showing = null;
1776                 showEntry(be);
1777             } else {
1778               // The correct entry is already being shown. Make sure the editor
1779               // is updated.
1780               ((EntryEditor)splitPane.getBottomComponent()).updateAllFields();
1781
1782             }
1783             return;
1784
1785         }
1786
1787         EntryEditor form;
1788         int divLoc = -1;
1789         String visName = null;
1790         if (showing != null) {
1791             visName = ((EntryEditor)splitPane.getBottomComponent()).
1792                 getVisiblePanelName();
1793         }
1794         if (showing != null)
1795             divLoc = splitPane.getDividerLocation();
1796
1797         if (entryEditors.containsKey(be.getType().getName())) {
1798             // We already have an editor for this entry type.
1799             form = (EntryEditor)entryEditors.get
1800                 ((be.getType().getName()));
1801             form.switchTo(be);
1802             if (visName != null)
1803                 form.setVisiblePanel(visName);
1804             splitPane.setBottomComponent(form);
1805             //highlightEntry(be);
1806         } else {
1807             // We must instantiate a new editor for this type.
1808             form = new EntryEditor(frame, BasePanel.this, be);
1809             if (visName != null)
1810                 form.setVisiblePanel(visName);
1811             splitPane.setBottomComponent(form);
1812
1813             //highlightEntry(be);
1814             entryEditors.put(be.getType().getName(), form);
1815
1816         }
1817         if (divLoc > 0) {
1818           splitPane.setDividerLocation(divLoc);
1819         }
1820         else
1821             splitPane.setDividerLocation
1822                 (GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1823         //new FocusRequester(form);
1824         //form.requestFocus();
1825
1826         showing = be;
1827         setEntryEditorEnabled(true); // Make sure it is enabled.
1828     }
1829
1830     /**
1831      * Get an entry editor ready to edit the given entry. If an appropriate editor is already
1832      * cached, it will be updated and returned.
1833      * @param entry The entry to be edited.
1834      * @return A suitable entry editor.
1835      */
1836     public EntryEditor getEntryEditor(BibtexEntry entry) {
1837         EntryEditor form;
1838         if (entryEditors.containsKey(entry.getType().getName())) {
1839             EntryEditor visibleNow = currentEditor;
1840             // We already have an editor for this entry type.
1841             form = (EntryEditor)entryEditors.get
1842                 ((entry.getType().getName()));
1843
1844             form.switchTo(entry);
1845             //if (visName != null)
1846             //    form.setVisiblePanel(visName);
1847         } else {
1848             // We must instantiate a new editor for this type.
1849             form = new EntryEditor(frame, BasePanel.this, entry);
1850             //if (visName != null)
1851             //    form.setVisiblePanel(visName);
1852
1853             entryEditors.put(entry.getType().getName(), form);
1854         }
1855         return form;
1856     }
1857
1858     public EntryEditor getCurrentEditor() {
1859         return currentEditor;
1860     }
1861
1862     /**
1863      * Sets the given entry editor as the bottom component in the split pane. If an entry editor already
1864      * was shown, makes sure that the divider doesn't move.
1865      * Updates the mode to SHOWING_EDITOR.
1866      * @param editor The entry editor to add.
1867      */
1868     public void showEntryEditor(EntryEditor editor) {
1869         int oldSplitterLocation = -1;
1870         if (mode == SHOWING_EDITOR)
1871             oldSplitterLocation = splitPane.getDividerLocation();
1872         boolean adjustSplitter = (mode == WILL_SHOW_EDITOR);
1873         mode = SHOWING_EDITOR;
1874         currentEditor = editor;
1875         splitPane.setBottomComponent(editor);
1876         if (oldSplitterLocation > 0)
1877             splitPane.setDividerLocation(oldSplitterLocation);
1878         if (adjustSplitter) {
1879             adjustSplitter();
1880             //new FocusRequester(editor);
1881         }
1882     }
1883
1884     /**
1885      * Sets the given preview panel as the bottom component in the split panel.
1886      * Updates the mode to SHOWING_PREVIEW.
1887      * @param preview The preview to show.
1888      */
1889     public void showPreview(PreviewPanel preview) {
1890         mode = SHOWING_PREVIEW;
1891         currentPreview = preview;
1892         splitPane.setBottomComponent(preview.getPane());
1893     }
1894
1895     /**
1896      * Removes the bottom component.
1897      */
1898     public void hideBottomComponent() {
1899         mode = SHOWING_NOTHING;
1900         splitPane.setBottomComponent(null);
1901     }
1902
1903     /**
1904      * This method selects the given entry, and scrolls it into view in the table.
1905      * If an entryEditor is shown, it is given focus afterwards.
1906      */
1907     public void highlightEntry(final BibtexEntry be) {
1908         //SwingUtilities.invokeLater(new Thread() {
1909         //     public void run() {
1910                  final int row = mainTable.findEntry(be);
1911                  if (row >= 0) {
1912                     mainTable.setRowSelectionInterval(row, row);
1913                     //entryTable.setActiveRow(row);
1914                     mainTable.ensureVisible(row);
1915                  }
1916         //     }
1917         //});
1918     }
1919
1920
1921     /**
1922      * This method is called from an EntryEditor when it should be closed. We relay
1923      * to the selection listener, which takes care of the rest.
1924      * @param editor The entry editor to close.
1925      */
1926     public void entryEditorClosing(EntryEditor editor) {
1927         selectionListener.entryEditorClosing(editor);
1928     }
1929
1930     /**
1931      * This method selects the given enties.
1932      * If an entryEditor is shown, it is given focus afterwards.
1933      */
1934     /*public void selectEntries(final BibtexEntry[] bes, final int toScrollTo) {
1935
1936         SwingUtilities.invokeLater(new Thread() {
1937              public void run() {
1938                  int rowToScrollTo = 0;
1939                  entryTable.revalidate();
1940                  entryTable.clearSelection();
1941                  loop: for (int i=0; i<bes.length; i++) {
1942                     if (bes[i] == null)
1943                         continue loop;
1944                     int row = tableModel.getNumberFromName(bes[i].getId());
1945                     if (i==toScrollTo)
1946                     rowToScrollTo = row;
1947                     if (row >= 0)
1948                         entryTable.addRowSelectionIntervalQuietly(row, row);
1949                  }
1950                  entryTable.ensureVisible(rowToScrollTo);
1951                  Component comp = splitPane.getBottomComponent();
1952                  //if (comp instanceof EntryEditor)
1953                  //    comp.requestFocus();
1954              }
1955         });
1956     } */
1957
1958     /**
1959      * Closes the entry editor if it is showing the given entry.
1960      *
1961      * @param be a <code>BibtexEntry</code> value
1962      */
1963     public void ensureNotShowing(BibtexEntry be) {
1964         if ((mode == SHOWING_EDITOR) && (currentEditor.getEntry() == be)) {
1965             selectionListener.entryEditorClosing(currentEditor);
1966         }
1967     }
1968
1969     public void updateEntryEditorIfShowing() {
1970         if (mode == SHOWING_EDITOR) {
1971             if (currentEditor.getType() != currentEditor.getEntry().getType()) {
1972                 // The entry has changed type, so we must get a new editor.
1973                 showing = null;
1974                 EntryEditor newEditor = getEntryEditor(currentEditor.getEntry());
1975                 showEntryEditor(newEditor);
1976             } else {
1977                 currentEditor.updateAllFields();
1978                 currentEditor.updateSource();
1979             }
1980         }
1981     }
1982
1983     /**
1984      * If an entry editor is showing, make sure its currently focused field
1985      * stores its changes, if any.
1986      */
1987     public void storeCurrentEdit() {
1988         if (isShowingEditor()) {
1989             EntryEditor editor = (EntryEditor)splitPane.getBottomComponent();
1990             editor.storeCurrentEdit();
1991         }
1992
1993     }
1994
1995     /**
1996      * This method iterates through all existing entry editors in this
1997      * BasePanel, telling each to update all its instances of
1998      * FieldContentSelector. This is done to ensure that the list of words
1999      * in each selector is up-to-date after the user has made changes in
2000      * the Manage dialog.
2001      */
2002     public void updateAllContentSelectors() {
2003         for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2004             EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2005             ed.updateAllContentSelectors();
2006         }
2007     }
2008
2009     public void rebuildAllEntryEditors() {
2010         for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2011             EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2012             ed.rebuildPanels();
2013         }
2014
2015     }
2016
2017     public void markBaseChanged() {
2018         baseChanged = true;
2019
2020         // Put an asterix behind the file name to indicate the
2021         // database has changed.
2022         String oldTitle = frame.getTabTitle(this);
2023         if (!oldTitle.endsWith("*"))
2024             frame.setTabTitle(this, oldTitle+"*", frame.getTabTooltip(this));
2025
2026         // If the status line states that the base has been saved, we
2027         // remove this message, since it is no longer relevant. If a
2028         // different message is shown, we leave it.
2029         if (frame.statusLine.getText().startsWith("Saved database"))
2030             frame.output(" ");
2031     }
2032
2033     public void markNonUndoableBaseChanged() {
2034         nonUndoableChange = true;
2035         markBaseChanged();
2036     }
2037
2038     public synchronized void markChangedOrUnChanged() {
2039         if (undoManager.hasChanged()) {
2040             if (!baseChanged)
2041                 markBaseChanged();
2042         }
2043         else if (baseChanged && !nonUndoableChange) {
2044             baseChanged = false;
2045             if (getFile() != null)
2046                 frame.setTabTitle(BasePanel.this, getFile().getName(),
2047                         getFile().getAbsolutePath());
2048             else
2049                 frame.setTabTitle(BasePanel.this, Globals.lang("untitled"), null);
2050         }
2051     }
2052
2053     /**
2054      * Selects a single entry, and scrolls the table to center it.
2055      *
2056      * @param pos Current position of entry to select.
2057      *
2058      */
2059     public void selectSingleEntry(int pos) {
2060         mainTable.clearSelection();
2061         mainTable.addRowSelectionInterval(pos, pos);
2062         mainTable.scrollToCenter(pos, 0);
2063     }
2064
2065     /* *
2066      * Selects all entries with a non-zero value in the field
2067      * @param field <code>String</code> field name.
2068      */
2069 /*    public void selectResults(String field) {
2070       LinkedList intervals = new LinkedList();
2071       int prevStart = -1, prevToSel = 0;
2072       // First we build a list of intervals to select, without touching the table.
2073       for (int i = 0; i < entryTable.getRowCount(); i++) {
2074         String value = (String) (database.getEntryById
2075                                  (tableModel.getIdForRow(i)))
2076             .getField(field);
2077         if ( (value != null) && !value.equals("0")) {
2078           if (prevStart < 0)
2079             prevStart = i;
2080           prevToSel = i;
2081         }
2082         else if (prevStart >= 0) {
2083           intervals.add(new int[] {prevStart, prevToSel});
2084           prevStart = -1;
2085         }
2086       }
2087       // Then select those intervals, if any.
2088       if (intervals.size() > 0) {
2089         entryTable.setSelectionListenerEnabled(false);
2090         entryTable.clearSelection();
2091         for (Iterator i=intervals.iterator(); i.hasNext();) {
2092           int[] interval = (int[])i.next();
2093           entryTable.addRowSelectionInterval(interval[0], interval[1]);
2094         }
2095         entryTable.setSelectionListenerEnabled(true);
2096       }
2097   */
2098
2099     public void setSearchMatcher(SearchMatcher matcher) {
2100         searchFilterList.setMatcher(matcher);
2101     }
2102
2103     public void setGroupMatcher(Matcher matcher) {
2104         groupFilterList.setMatcher(matcher);
2105     }
2106
2107     public void stopShowingSearchResults() {
2108         searchFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2109     }
2110
2111     public void stopShowingGroup() {
2112         groupFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2113
2114      }
2115
2116      public BibtexDatabase getDatabase(){
2117         return database ;
2118     }
2119
2120     public void preambleEditorClosing() {
2121         preambleEditor = null;
2122     }
2123
2124     public void stringsClosing() {
2125         stringDialog = null;
2126     }
2127
2128     public void changeType(BibtexEntry entry, BibtexEntryType type) {
2129       changeType(new BibtexEntry[] {entry}, type);
2130     }
2131
2132     public void changeType(BibtexEntryType type) {
2133       BibtexEntry[] bes = mainTable.getSelectedEntries();
2134       changeType(bes, type);
2135     }
2136
2137     public void changeType(BibtexEntry[] bes, BibtexEntryType type) {
2138
2139         if ((bes == null) || (bes.length == 0)) {
2140             output("First select the entries you wish to change type "+
2141                    "for.");
2142             return;
2143         }
2144         if (bes.length > 1) {
2145             int choice = JOptionPane.showConfirmDialog
2146                 (this, "Multiple entries selected. Do you want to change"
2147                  +"\nthe type of all these to '"+type.getName()+"'?",
2148                  "Change type", JOptionPane.YES_NO_OPTION,
2149                  JOptionPane.WARNING_MESSAGE);
2150             if (choice == JOptionPane.NO_OPTION)
2151                 return;
2152         }
2153
2154         NamedCompound ce = new NamedCompound(Globals.lang("change type"));
2155         for (int i=0; i<bes.length; i++) {
2156             ce.addEdit(new UndoableChangeType(bes[i],
2157                                               bes[i].getType(),
2158                                               type));
2159             bes[i].setType(type);
2160         }
2161
2162         output(Globals.lang("Changed type to")+" '"+type.getName()+"' "
2163                +Globals.lang("for")+" "+bes.length
2164                +" "+Globals.lang("entries")+".");
2165         ce.end();
2166         undoManager.addEdit(ce);
2167         markBaseChanged();
2168         updateEntryEditorIfShowing();
2169     }
2170
2171     public boolean showDeleteConfirmationDialog(int numberOfEntries) {
2172         if (Globals.prefs.getBoolean("confirmDelete")) {
2173             String msg = Globals.lang("Really delete the selected")
2174                 + " " + Globals.lang("entry") + "?",
2175                 title = Globals.lang("Delete entry");
2176             if (numberOfEntries > 1) {
2177                 msg = Globals.lang("Really delete the selected")
2178                     + " " + numberOfEntries + " " + Globals.lang("entries") + "?";
2179                 title = Globals.lang("Delete multiple entries");
2180             }
2181
2182             CheckBoxMessage cb = new CheckBoxMessage
2183                 (msg, Globals.lang("Disable this confirmation dialog"), false);
2184
2185             int answer = JOptionPane.showConfirmDialog(frame, cb, title,
2186                                                        JOptionPane.YES_NO_OPTION,
2187                                                        JOptionPane.QUESTION_MESSAGE);
2188             if (cb.isSelected())
2189                 Globals.prefs.putBoolean("confirmDelete", false);
2190             return (answer == JOptionPane.YES_OPTION);
2191         } else return true;
2192
2193     }
2194     
2195     /**
2196      * If the relevant option is set, autogenerate keys for all entries that are
2197      * lacking keys.
2198      */
2199     public void autoGenerateKeysBeforeSaving() {
2200         if (Globals.prefs.getBoolean("generateKeysBeforeSaving")) {
2201             BibtexEntry bes;
2202             NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
2203             boolean any = false;
2204             for (Iterator i=database.getKeySet().iterator(); i.hasNext();) {
2205                 bes = database.getEntryById((String)i.next());
2206                 String oldKey = bes.getCiteKey();
2207                 if ((oldKey == null) || (oldKey.equals(""))) {
2208                     LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, bes);
2209                     ce.addEdit(new UndoableKeyChange(database, bes.getId(), null,
2210                         (String)bes.getField(BibtexFields.KEY_FIELD)));
2211                     any = true;
2212                 }
2213             }
2214             // Store undo information, if any:
2215             if (any) {
2216                 ce.end();
2217                 undoManager.addEdit(ce);
2218             }
2219         }
2220     }
2221     
2222     /**
2223      * Activates or deactivates the entry preview, depending on the argument.
2224      * When deactivating, makes sure that any visible preview is hidden.
2225      * @param enabled
2226      */
2227     public void setPreviewActive(boolean enabled) {
2228         selectionListener.setPreviewActive(enabled);
2229     }
2230
2231
2232     class UndoAction extends BaseAction {
2233         public void action() {
2234             try {
2235                 String name = undoManager.getUndoPresentationName();
2236                 undoManager.undo();
2237                 markBaseChanged();
2238                 frame.output(name);
2239             } catch (CannotUndoException ex) {
2240                 frame.output(Globals.lang("Nothing to undo")+".");
2241             }
2242             // After everything, enable/disable the undo/redo actions
2243             // appropriately.
2244             //updateUndoState();
2245             //redoAction.updateRedoState();
2246             markChangedOrUnChanged();
2247         }
2248     }
2249
2250     class RedoAction extends BaseAction {
2251         public void action() {
2252             try {
2253                 String name = undoManager.getRedoPresentationName();
2254                 undoManager.redo();
2255                 markBaseChanged();
2256                 frame.output(name);
2257             } catch (CannotRedoException ex) {
2258                 frame.output(Globals.lang("Nothing to redo")+".");
2259             }
2260             // After everything, enable/disable the undo/redo actions
2261             // appropriately.
2262             //updateRedoState();
2263             //undoAction.updateUndoState();
2264             markChangedOrUnChanged();
2265         }
2266     }
2267
2268     // Method pertaining to the ClipboardOwner interface.
2269     public void lostOwnership(Clipboard clipboard, Transferable contents) {}
2270
2271
2272   public void setEntryEditorEnabled(boolean enabled) {
2273     if ((showing != null) && (splitPane.getBottomComponent() instanceof EntryEditor)) {
2274           EntryEditor ed = (EntryEditor)splitPane.getBottomComponent();
2275           if (ed.isEnabled() != enabled)
2276             ed.setEnabled(enabled);
2277     }
2278   }
2279
2280   public String fileMonitorHandle() { return fileMonitorHandle; }
2281
2282     public void fileUpdated() {
2283       if (saving)
2284         return; // We are just saving the file, so this message is most likely due
2285       // to bad timing. If not, we'll handle it on the next polling.
2286       //Util.pr("File '"+file.getPath()+"' has been modified.");
2287       updatedExternally = true;
2288
2289       final ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this);
2290
2291       // Adding the sidepane component is Swing work, so we must do this in the Swing
2292       // thread:
2293       Thread t = new Thread() {
2294               public void run() {
2295                   
2296                   // Check if there is already a notification about external
2297                   // changes:
2298                   boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.NAME);
2299                   if (hasAlready) {
2300                       sidePaneManager.hideComponent(FileUpdatePanel.NAME);
2301                       sidePaneManager.unregisterComponent(FileUpdatePanel.NAME);
2302                   }
2303                   FileUpdatePanel pan = new FileUpdatePanel(frame, BasePanel.this,
2304                                                             sidePaneManager, getFile(), scanner);
2305                   sidePaneManager.register(FileUpdatePanel.NAME, pan);
2306                   sidePaneManager.show(FileUpdatePanel.NAME);
2307                   setUpdatedExternally(false);
2308                   //scanner.displayResult();
2309               }
2310           };
2311
2312       // Test: running scan automatically in background
2313       scanner.changeScan(BasePanel.this.getFile());
2314       try {
2315           scanner.join();
2316       } catch (InterruptedException e) {
2317           e.printStackTrace();
2318       }
2319
2320       if (scanner.changesFound()) {
2321           SwingUtilities.invokeLater(t);
2322       } else {
2323           setUpdatedExternally(false);
2324           //System.out.println("No changes found.");
2325       }
2326     }
2327
2328       public void fileRemoved() {
2329         Util.pr("File '"+getFile().getPath()+"' has been deleted.");
2330       }
2331
2332
2333       public void cleanUp() {
2334         if (fileMonitorHandle != null)
2335           Globals.fileUpdateMonitor.removeUpdateListener(fileMonitorHandle);
2336       }
2337
2338   public void setUpdatedExternally(boolean b) {
2339     updatedExternally = b;
2340   }
2341
2342     /**
2343      * Get an array containing the currently selected entries.
2344      *
2345      * @return An array containing the selected entries.
2346      */
2347     public BibtexEntry[] getSelectedEntries() {
2348         return mainTable.getSelectedEntries();
2349     }
2350
2351     /**
2352      * Get the file where this database was last saved to or loaded from, if any.
2353      *
2354      * @return The relevant File, or null if none is defined.
2355      */
2356     public File getFile() {
2357         return metaData.getFile();
2358     }
2359     
2360     /**
2361      * Get a String containing a comma-separated list of the bibtex keys
2362      * of the selected entries.
2363      *
2364      * @return A comma-separated list of the keys of the selected entries.
2365      */
2366     public String getKeysForSelection() {
2367         List entries = mainTable.getSelected();
2368         StringBuffer result = new StringBuffer();
2369         String citeKey = "";//, message = "";
2370         boolean first = true;
2371         for (Iterator i = entries.iterator(); i.hasNext();) {
2372             BibtexEntry bes = (BibtexEntry) i.next();
2373             citeKey = (String) bes.getField(BibtexFields.KEY_FIELD);
2374             // if the key is empty we give a warning and ignore this entry
2375             if (citeKey == null || citeKey.equals(""))
2376                 continue;
2377             if (first) {
2378                 result.append(citeKey);
2379                 first = false;
2380             } else {
2381                 result.append(",").append(citeKey);
2382             }
2383         }
2384         return result.toString();
2385     }
2386
2387     public GroupSelector getGroupSelector() {
2388         return frame.groupSelector;
2389     }
2390
2391
2392     public boolean isUpdatedExternally() {
2393         return updatedExternally;
2394     }
2395
2396
2397     public String getFileMonitorHandle() {
2398         return fileMonitorHandle;
2399     }
2400
2401
2402     public void setFileMonitorHandle(String fileMonitorHandle) {
2403         this.fileMonitorHandle = fileMonitorHandle;
2404     }
2405
2406     public SidePaneManager getSidePaneManager() {
2407         return sidePaneManager;
2408     }
2409
2410
2411     public void setNonUndoableChange(boolean nonUndoableChange) {
2412         this.nonUndoableChange = nonUndoableChange;
2413     }
2414
2415     public void setBaseChanged(boolean baseChanged) {
2416         this.baseChanged = baseChanged;
2417     }
2418
2419
2420     public void setSaving(boolean saving) {
2421         this.saving = saving;
2422     }
2423
2424 }