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