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