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