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