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