3acf1c69009bae7731c9b85209aace06166a2492
[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 AbstractWorker() {
1332               String message = null;
1333               public void run() {
1334               if (mainTable.getSelected().size() == 0) {
1335                   message = Globals.lang("No entries selected")+".";
1336                   getCallBack().update();
1337                   return;
1338               }
1339
1340               // Make a list of possible formats:
1341               Map formats = new HashMap();
1342               formats.put("BibTeXML", "bibtexml");
1343               formats.put("DocBook", "docbook");
1344               formats.put("HTML", "html");
1345                           formats.put("RTF (Harvard)", "harvard/harvard");
1346               formats.put("Simple HTML", "simplehtml");
1347               for (int i = 0; i < Globals.prefs.customExports.size(); i++) {
1348                   Object o = (Globals.prefs.customExports.getElementAt(i))[0];
1349                   formats.put(o, o);
1350               }
1351                           Object[] array = formats.keySet().toArray();
1352                           Arrays.sort(array);
1353               JList list = new JList(array);
1354               list.setBorder(BorderFactory.createEtchedBorder());
1355               list.setSelectionInterval(0,0);
1356               list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1357               int answer =
1358                   JOptionPane.showOptionDialog(frame, list, Globals.lang("Select format"),
1359                                JOptionPane.YES_NO_OPTION,
1360                                JOptionPane.QUESTION_MESSAGE, null,
1361                                new String[] {Globals.lang("Ok"), Globals.lang("Cancel")},
1362                                Globals.lang("Ok"));
1363
1364               if (answer == JOptionPane.NO_OPTION)
1365                   return;
1366
1367               String lfName = (String)(formats.get(list.getSelectedValue()));
1368               final boolean custom = (list.getSelectedIndex() >= Globals.STANDARD_EXPORT_COUNT);
1369               String dir = null;
1370               if (custom) {
1371                   int index = list.getSelectedIndex()-Globals.STANDARD_EXPORT_COUNT;
1372                   dir = (String)(Globals.prefs.customExports.getElementAt(index)[1]);
1373                   File f = new File(dir);
1374                   lfName = f.getName();
1375                   lfName = lfName.substring(0, lfName.indexOf("."));
1376                   // Remove file name - we want the directory only.
1377                   dir = f.getParent()+System.getProperty("file.separator");
1378               }
1379               final String format = lfName,
1380                   directory = dir;
1381
1382               try {
1383                   BibtexEntry[] bes = mainTable.getSelectedEntries();
1384                   StringWriter sw = new StringWriter();
1385                   System.out.println("actual export to clipboard not implemented...");
1386                   //FileActions.exportEntries(database, bes, format, custom, directory, sw);
1387                   ClipboardOwner owner = new ClipboardOwner() {
1388                     public void lostOwnership(Clipboard clipboard, Transferable content) {}
1389                   };
1390                   //StringSelection ss = new StringSelection(sw.toString());
1391                   RtfSelection rs = new RtfSelection(sw.toString());
1392                      Toolkit.getDefaultToolkit().getSystemClipboard()
1393                         .setContents(rs, owner);
1394                   message = Globals.lang("Entries exported to clipboard")+": "+bes.length;
1395               } catch (Exception ex) {
1396                   ex.printStackTrace();
1397               }
1398               }
1399
1400               public void update() {
1401               output(message);
1402               }
1403
1404           });
1405
1406         actions.put("writeXMP", new WriteXMPAction(this));
1407         
1408         actions.put("abbreviateIso", new AbbreviateAction(this, true));
1409         actions.put("abbreviateMedline", new AbbreviateAction(this, false));
1410         actions.put("unabbreviate", new UnabbreviateAction(this));
1411         actions.put("autoSetPdf", new AutoSetExternalFileForEntries(this, "pdf"));
1412         actions.put("autoSetPs", new AutoSetExternalFileForEntries(this, "ps"));
1413
1414     }
1415
1416     /**
1417      * This method is called from JabRefFrame is a database specific
1418      * action is requested by the user. Runs the command if it is
1419      * defined, or prints an error message to the standard error
1420      * stream.
1421      *
1422      * @param _command The name of the command to run.
1423     */
1424     public void runCommand(String _command) {
1425       final String command = _command;
1426       //(new Thread() {
1427       //  public void run() {
1428           if (actions.get(command) == null)
1429             Util.pr("No action defined for'" + command + "'");
1430             else {
1431         Object o = actions.get(command);
1432         try {
1433             if (o instanceof BaseAction)
1434             ((BaseAction)o).action();
1435             else {
1436             // This part uses Spin's features:
1437             Worker wrk = ((AbstractWorker)o).getWorker();
1438             // The Worker returned by getWorker() has been wrapped
1439             // by Spin.off(), which makes its methods be run in
1440             // a different thread from the EDT.
1441             CallBack clb = ((AbstractWorker)o).getCallBack();
1442
1443             ((AbstractWorker)o).init(); // This method runs in this same thread, the EDT.
1444             // Useful for initial GUI actions, like printing a message.
1445
1446             // The CallBack returned by getCallBack() has been wrapped
1447             // by Spin.over(), which makes its methods be run on
1448             // the EDT.
1449             wrk.run(); // Runs the potentially time-consuming action
1450             // without freezing the GUI. The magic is that THIS line
1451             // of execution will not continue until run() is finished.
1452             clb.update(); // Runs the update() method on the EDT.
1453             }
1454         } catch (Throwable ex) {
1455             // If the action has blocked the JabRefFrame before crashing, we need to unblock it.
1456             // The call to unblock will simply hide the glasspane, so there is no harm in calling
1457             // it even if the frame hasn't been blocked.
1458             frame.unblock();
1459             ex.printStackTrace();
1460         }
1461         }
1462       //  }
1463       //}).start();
1464     }
1465
1466     private boolean saveDatabase(File file, boolean selectedOnly, String encoding) throws SaveException {
1467         SaveSession session;
1468         frame.block();
1469         try {
1470             if (!selectedOnly)
1471                 session = FileActions.saveDatabase(database, metaData, file,
1472                                            Globals.prefs, false, false, encoding);
1473             else
1474                 session = FileActions.savePartOfDatabase(database, metaData, file,
1475                                                Globals.prefs, mainTable.getSelectedEntries(), encoding);
1476
1477         } catch (UnsupportedCharsetException ex2) {
1478             JOptionPane.showMessageDialog(frame, Globals.lang("Could not save file. "
1479                 +"Character encoding '%0' is not supported.", encoding),
1480                     Globals.lang("Save database"), JOptionPane.ERROR_MESSAGE);
1481             throw new SaveException("rt");
1482         } catch (SaveException ex) {
1483             if (ex.specificEntry()) {
1484                 // Error occured during processing of
1485                 // be. Highlight it:
1486                 int row = mainTable.findEntry(ex.getEntry()),
1487                     topShow = Math.max(0, row-3);
1488                 mainTable.setRowSelectionInterval(row, row);
1489                 mainTable.scrollTo(topShow);
1490                 showEntry(ex.getEntry());
1491             }
1492             else ex.printStackTrace();
1493
1494             JOptionPane.showMessageDialog
1495                 (frame, Globals.lang("Could not save file")
1496                  +".\n"+ex.getMessage(),
1497                  Globals.lang("Save database"),
1498                  JOptionPane.ERROR_MESSAGE);
1499             throw new SaveException("rt");
1500
1501         } finally {
1502             frame.unblock();
1503         }
1504
1505         boolean commit = true;
1506         if (!session.getWriter().couldEncodeAll()) {
1507             DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("left:pref, 4dlu, fill:pref", ""));
1508             JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters());
1509             ta.setEditable(false);
1510             builder.append(Globals.lang("The chosen encoding '%0' could not encode the following characters: ",
1511                       session.getEncoding()));
1512             builder.append(ta);
1513             builder.append(Globals.lang("What do you want to do?"));
1514             String tryDiff = Globals.lang("Try different encoding");
1515             int answer = JOptionPane.showOptionDialog(frame, builder.getPanel(), Globals.lang("Save database"),
1516                     JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
1517                     new String[] {Globals.lang("Save"), tryDiff, Globals.lang("Cancel")}, tryDiff);
1518
1519             if (answer == JOptionPane.NO_OPTION) {
1520                 // The user wants to use another encoding.
1521                 Object choice = JOptionPane.showInputDialog(frame, Globals.lang("Select encoding"), Globals.lang("Save database"),
1522                         JOptionPane.QUESTION_MESSAGE, null, Globals.ENCODINGS, encoding);
1523                 if (choice != null) {
1524                     String newEncoding = (String)choice;
1525                     return saveDatabase(file, selectedOnly, newEncoding);
1526                 } else
1527                     commit = false;
1528             } else if (answer == JOptionPane.CANCEL_OPTION)
1529                     commit = false;
1530
1531
1532           }
1533
1534         try {
1535             if (commit) {
1536                 session.commit();
1537                 this.encoding = encoding; // Make sure to remember which encoding we used.
1538             }
1539             else
1540                 session.cancel();
1541         } catch (IOException e) {
1542             e.printStackTrace();
1543         }
1544
1545         return commit;
1546     }
1547
1548
1549     /**
1550      * This method is called from JabRefFrame when the user wants to
1551      * create a new entry. If the argument is null, the user is
1552      * prompted for an entry type.
1553      *
1554      * @param type The type of the entry to create.
1555      * @return The newly created BibtexEntry or null the operation was canceled by the user.
1556      */
1557     public BibtexEntry newEntry(BibtexEntryType type) {
1558         if (type == null) {
1559             // Find out what type is wanted.
1560             EntryTypeDialog etd = new EntryTypeDialog(frame);
1561             // We want to center the dialog, to make it look nicer.
1562             Util.placeDialog(etd, frame);
1563             etd.setVisible(true);
1564             type = etd.getChoice();
1565         }
1566         if (type != null) { // Only if the dialog was not cancelled.
1567             String id = Util.createNeutralId();
1568             final BibtexEntry be = new BibtexEntry(id, type);
1569             try {
1570                 database.insertEntry(be);
1571
1572                 // Set owner/timestamp if options are enabled:
1573                 ArrayList list = new ArrayList();
1574                 list.add(be);
1575                 Util.setAutomaticFields(list);
1576
1577                 // Create an UndoableInsertEntry object.
1578                 undoManager.addEdit(new UndoableInsertEntry(database, be, BasePanel.this));
1579                 output(Globals.lang("Added new")+" '"+type.getName().toLowerCase()+"' "
1580                        +Globals.lang("entry")+".");
1581                 final int row = mainTable.findEntry(be);
1582
1583                 // We are going to select the new entry. Before that, make sure that we are in
1584                 // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR
1585                 // mode which makes sure the selection will trigger display of the entry editor
1586                 // and adjustment of the splitter.
1587                 if (mode != SHOWING_EDITOR) {
1588                     mode = WILL_SHOW_EDITOR;
1589                 }
1590
1591                 highlightEntry(be);  // Selects the entry. The selection listener will open the editor.
1592
1593                 markBaseChanged(); // The database just changed.
1594                 new FocusRequester(getEntryEditor(be));
1595                 return be;
1596             } catch (KeyCollisionException ex) {
1597                 Util.pr(ex.getMessage());
1598             }
1599         }
1600         return null;
1601     }
1602
1603
1604
1605     /**
1606      * This method is called from JabRefFrame when the user wants to
1607      * create a new entry.
1608      * @param bibEntry The new entry.
1609      */
1610     public void insertEntry(BibtexEntry bibEntry)
1611     {
1612       if (bibEntry != null)
1613       {
1614         try
1615         {
1616           database.insertEntry(bibEntry) ;
1617           if (Globals.prefs.getBoolean("useOwner"))
1618             // Set owner field to default value
1619             bibEntry.setField(BibtexFields.OWNER, Globals.prefs.get("defaultOwner") );
1620             // Create an UndoableInsertEntry object.
1621             undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1622             output(Globals.lang("Added new")+" '"
1623                    +bibEntry.getType().getName().toLowerCase()+"' "
1624                    +Globals.lang("entry")+".");
1625             int row = mainTable.findEntry(bibEntry);
1626
1627             mainTable.clearSelection();
1628             mainTable.scrollTo(row);
1629             markBaseChanged(); // The database just changed.
1630             if (Globals.prefs.getBoolean("autoOpenForm"))
1631             {
1632                   showEntry(bibEntry);
1633             }
1634         } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); }
1635       }
1636     }
1637
1638     public void createMainTable() {
1639         //Comparator comp = new FieldComparator("author");
1640
1641         GlazedEntrySorter eventList = new GlazedEntrySorter(database.getEntryMap(), null);
1642         // Must initialize sort columns somehow:
1643
1644         database.addDatabaseChangeListener(eventList);
1645         groupFilterList = new FilterList(eventList.getTheList(), NoSearchMatcher.INSTANCE);
1646         searchFilterList = new FilterList(groupFilterList, NoSearchMatcher.INSTANCE);
1647         //final SortedList sortedList = new SortedList(searchFilterList, null);
1648         MainTableFormat tableFormat = new MainTableFormat(this);
1649         tableFormat.updateTableFormat();
1650         //EventTableModel tableModel = new EventTableModel(sortedList, tableFormat);
1651         mainTable = new MainTable(/*tableModel, */tableFormat, searchFilterList, frame, this);
1652         
1653         selectionListener = new MainTableSelectionListener(this, mainTable);
1654         mainTable.updateFont();
1655         mainTable.addSelectionListener(selectionListener);
1656         mainTable.addMouseListener(selectionListener);
1657         mainTable.addKeyListener(selectionListener);
1658         mainTable.addFocusListener(selectionListener);
1659         
1660         // Add the listener that will take care of highlighting groups as the selection changes:
1661         groupsHighlightListener = new ListEventListener() {
1662             public void listChanged(ListEvent listEvent) {
1663                 if (Globals.prefs.getBoolean("highlightGroupsMatchingAny"))
1664                     getGroupSelector().showMatchingGroups(
1665                             mainTable.getSelectedEntries(), false);
1666                 else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll"))
1667                     getGroupSelector().showMatchingGroups(
1668                             mainTable.getSelectedEntries(), true);
1669                 else // no highlight
1670                     getGroupSelector().showMatchingGroups(null, true);
1671             }
1672         };
1673         mainTable.addSelectionListener(groupsHighlightListener);
1674
1675         mainTable.getActionMap().put("cut", new AbstractAction() {
1676                 public void actionPerformed(ActionEvent e) {
1677                     try { runCommand("cut");
1678                     } catch (Throwable ex) {
1679                         ex.printStackTrace();
1680                     }
1681                 }
1682             });
1683         mainTable.getActionMap().put("copy", new AbstractAction() {
1684                 public void actionPerformed(ActionEvent e) {
1685                     try { runCommand("copy");
1686                     } catch (Throwable ex) {
1687                         ex.printStackTrace();
1688                     }
1689                 }
1690             });
1691         mainTable.getActionMap().put("paste", new AbstractAction() {
1692                 public void actionPerformed(ActionEvent e) {
1693                     try { runCommand("paste");
1694                     } catch (Throwable ex) {
1695                         ex.printStackTrace();
1696                     }
1697                 }
1698             });
1699
1700         mainTable.addKeyListener(new KeyAdapter() {
1701
1702                 public void keyPressed(KeyEvent e) {
1703                     final int keyCode = e.getKeyCode();
1704                     final TreePath path = frame.groupSelector.getSelectionPath();
1705                     final GroupTreeNode node = path == null ? null : (GroupTreeNode) path.getLastPathComponent();
1706
1707                     if (e.isControlDown()) {
1708                         switch (keyCode) {
1709                         // The up/down/left/rightkeystrokes are displayed in the
1710                         // GroupSelector's popup menu, so if they are to be changed,
1711                         // edit GroupSelector.java accordingly!
1712                         case KeyEvent.VK_UP:
1713                             e.consume();
1714                             if (node != null)
1715                                 frame.groupSelector.moveNodeUp(node, true);
1716                             break;
1717                         case KeyEvent.VK_DOWN:
1718                             e.consume();
1719                             if (node != null)
1720                                 frame.groupSelector.moveNodeDown(node, true);
1721                             break;
1722                         case KeyEvent.VK_LEFT:
1723                             e.consume();
1724                             if (node != null)
1725                                 frame.groupSelector.moveNodeLeft(node, true);
1726                             break;
1727                         case KeyEvent.VK_RIGHT:
1728                             e.consume();
1729                             if (node != null)
1730                                 frame.groupSelector.moveNodeRight(node, true);
1731                             break;
1732                         case KeyEvent.VK_PAGE_DOWN:
1733                             frame.nextTab.actionPerformed(null);
1734                             e.consume();
1735                             break;
1736                         case KeyEvent.VK_PAGE_UP:
1737                             frame.prevTab.actionPerformed(null);
1738                             e.consume();
1739                             break;
1740                         }
1741                     } else if (keyCode == KeyEvent.VK_ENTER){
1742                         e.consume();
1743                         try { runCommand("edit");
1744                         } catch (Throwable ex) {
1745                             ex.printStackTrace();
1746                         }
1747                     }
1748                 }
1749         });
1750     }
1751
1752     public void setupMainPanel() {
1753         //System.out.println("setupMainPanel");
1754         //splitPane = new com.jgoodies.uif_lite.component.UIFSplitPane(JSplitPane.VERTICAL_SPLIT);
1755         splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
1756         splitPane.setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
1757         // We replace the default FocusTraversalPolicy with a subclass
1758         // that only allows FieldEditor components to gain keyboard focus,
1759         // if there is an entry editor open.
1760         /*splitPane.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
1761                 protected boolean accept(Component c) {
1762                     Util.pr("jaa");
1763                     if (showing == null)
1764                         return super.accept(c);
1765                     else
1766                         return (super.accept(c) &&
1767                                 (c instanceof FieldEditor));
1768                 }
1769                 });*/
1770
1771         createMainTable();
1772
1773         splitPane.setTopComponent(mainTable.getPane());
1774
1775         //setupTable();
1776         // If an entry is currently being shown, make sure it stays shown,
1777         // otherwise set the bottom component to null.
1778         if (mode == SHOWING_PREVIEW) {
1779             mode = SHOWING_NOTHING;
1780             int row = mainTable.findEntry(currentPreview.entry);
1781             if (row >= 0)
1782                 mainTable.setRowSelectionInterval(row, row);
1783
1784         }
1785         else if (mode == SHOWING_EDITOR) {
1786             mode = SHOWING_NOTHING;
1787             /*int row = mainTable.findEntry(currentEditor.entry);
1788             if (row >= 0)
1789                 mainTable.setRowSelectionInterval(row, row);
1790             */
1791             //showEntryEditor(currentEditor);
1792         } else
1793             splitPane.setBottomComponent(null);
1794
1795
1796         setLayout(new BorderLayout());
1797         removeAll();
1798         add(splitPane, BorderLayout.CENTER);
1799         //add(contentPane, BorderLayout.CENTER);
1800
1801         //add(sidePaneManager.getPanel(), BorderLayout.WEST);
1802         //add(splitPane, BorderLayout.CENTER);
1803
1804     //setLayout(gbl);
1805     //con.fill = GridBagConstraints.BOTH;
1806     //con.weighty = 1;
1807     //con.weightx = 0;
1808     //gbl.setConstraints(sidePaneManager.getPanel(), con);
1809     //con.weightx = 1;
1810     //gbl.setConstraints(splitPane, con);
1811         //mainPanel.setDividerLocation(GUIGlobals.SPLIT_PANE_DIVIDER_LOCATION);
1812         //setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
1813         //setResizeWeight(0);
1814         splitPane.revalidate();
1815         revalidate();
1816         repaint();
1817     }
1818
1819
1820     /**
1821      * This method is called after a database has been parsed. The
1822      * hashmap contains the contents of all comments in the .bib file
1823      * that started with the meta flag (GUIGlobals.META_FLAG).
1824      * In this method, the meta data are input to their respective
1825      * handlers.
1826      *
1827      * @param meta Metadata to input.
1828      */
1829     public void parseMetaData(HashMap meta) {
1830         metaData = new MetaData(meta,database());
1831
1832     }
1833
1834     /*
1835     public void refreshTable() {
1836         //System.out.println("hiding="+hidingNonHits+"\tlastHits="+lastSearchHits);
1837         // This method is called by EntryTypeForm when a field value is
1838         // stored. The table is scheduled for repaint.
1839         entryTable.assureNotEditing();
1840         //entryTable.invalidate();
1841         BibtexEntry[] bes = entryTable.getSelectedEntries();
1842     if (hidingNonHits)
1843         tableModel.update(lastSearchHits);
1844     else
1845         tableModel.update();
1846     //tableModel.remap();
1847         if ((bes != null) && (bes.length > 0))
1848             selectEntries(bes, 0);
1849
1850     //long toc = System.currentTimeMillis();
1851     //  Util.pr("Refresh took: "+(toc-tic)+" ms");
1852     } */
1853
1854     public void updatePreamble() {
1855         if (preambleEditor != null)
1856             preambleEditor.updatePreamble();
1857     }
1858
1859     public void assureStringDialogNotEditing() {
1860         if (stringDialog != null)
1861             stringDialog.assureNotEditing();
1862     }
1863
1864     public void updateStringDialog() {
1865         if (stringDialog != null)
1866             stringDialog.refreshTable();
1867     }
1868
1869     public void updateEntryPreviewToRow(BibtexEntry e) {
1870
1871     }
1872
1873     public void adjustSplitter() {
1874         int mode = getMode();
1875         if (mode == SHOWING_PREVIEW) {
1876             splitPane.setDividerLocation(splitPane.getHeight()-GUIGlobals.PREVIEW_PANEL_HEIGHT);
1877         } else {
1878             splitPane.setDividerLocation(GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1879
1880         }
1881     }
1882
1883
1884
1885     /**
1886      * Stores the source view in the entry editor, if one is open, has the source view
1887      * selected and the source has been edited.
1888      * @return boolean false if there is a validation error in the source panel, true otherwise.
1889      */
1890     public boolean entryEditorAllowsChange() {
1891       Component c = splitPane.getBottomComponent();
1892       if ((c != null) && (c instanceof EntryEditor)) {
1893         return ((EntryEditor)c).lastSourceAccepted();
1894       }
1895       else
1896         return true;
1897     }
1898
1899     public void moveFocusToEntryEditor() {
1900       Component c = splitPane.getBottomComponent();
1901       if ((c != null) && (c instanceof EntryEditor)) {
1902         new FocusRequester(c);
1903       }
1904     }
1905
1906     /**
1907      * Ensure that no preview is shown. Called when preview is turned off. Must chech if
1908      * a preview is in fact visible before doing anything rash.
1909      */
1910     public void hidePreview() {
1911         Globals.prefs.putBoolean("previewEnabled", false);
1912
1913       Component c = splitPane.getBottomComponent();
1914       if ((c != null) && !(c instanceof EntryEditor))
1915         splitPane.setBottomComponent(null);
1916     }
1917
1918     public boolean isShowingEditor() {
1919       return ((splitPane.getBottomComponent() != null)
1920               && (splitPane.getBottomComponent() instanceof EntryEditor));
1921     }
1922
1923     public void showEntry(final BibtexEntry be) {
1924         if (showing == be) {
1925             if (splitPane.getBottomComponent() == null) {
1926                 // This is the special occasion when showing is set to an
1927                 // entry, but no entry editor is in fact shown. This happens
1928                 // after Preferences dialog is closed, and it means that we
1929                 // must make sure the same entry is shown again. We do this by
1930                 // setting showing to null, and recursively calling this method.
1931                 showing = null;
1932                 showEntry(be);
1933             } else {
1934               // The correct entry is already being shown. Make sure the editor
1935               // is updated.
1936               ((EntryEditor)splitPane.getBottomComponent()).updateAllFields();
1937
1938             }
1939             return;
1940
1941         }
1942
1943         EntryEditor form;
1944         int divLoc = -1;
1945         String visName = null;
1946         if (showing != null) {
1947             visName = ((EntryEditor)splitPane.getBottomComponent()).
1948                 getVisiblePanelName();
1949         }
1950         if (showing != null)
1951             divLoc = splitPane.getDividerLocation();
1952
1953         if (entryEditors.containsKey(be.getType().getName())) {
1954             // We already have an editor for this entry type.
1955             form = (EntryEditor)entryEditors.get
1956                 ((be.getType().getName()));
1957             form.switchTo(be);
1958             if (visName != null)
1959                 form.setVisiblePanel(visName);
1960             splitPane.setBottomComponent(form);
1961             //highlightEntry(be);
1962         } else {
1963             // We must instantiate a new editor for this type.
1964             form = new EntryEditor(frame, BasePanel.this, be);
1965             if (visName != null)
1966                 form.setVisiblePanel(visName);
1967             splitPane.setBottomComponent(form);
1968
1969             //highlightEntry(be);
1970             entryEditors.put(be.getType().getName(), form);
1971
1972         }
1973         if (divLoc > 0) {
1974           splitPane.setDividerLocation(divLoc);
1975         }
1976         else
1977             splitPane.setDividerLocation
1978                 (GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1979         //new FocusRequester(form);
1980         //form.requestFocus();
1981
1982         showing = be;
1983         setEntryEditorEnabled(true); // Make sure it is enabled.
1984     }
1985
1986     /**
1987      * Get an entry editor ready to edit the given entry. If an appropriate editor is already
1988      * cached, it will be updated and returned.
1989      * @param entry The entry to be edited.
1990      * @return A suitable entry editor.
1991      */
1992     public EntryEditor getEntryEditor(BibtexEntry entry) {
1993         EntryEditor form;
1994         if (entryEditors.containsKey(entry.getType().getName())) {
1995             EntryEditor visibleNow = currentEditor;
1996             // We already have an editor for this entry type.
1997             form = (EntryEditor)entryEditors.get
1998                 ((entry.getType().getName()));
1999
2000             form.switchTo(entry);
2001             //if (visName != null)
2002             //    form.setVisiblePanel(visName);
2003         } else {
2004             // We must instantiate a new editor for this type.
2005             form = new EntryEditor(frame, BasePanel.this, entry);
2006             //if (visName != null)
2007             //    form.setVisiblePanel(visName);
2008
2009             entryEditors.put(entry.getType().getName(), form);
2010         }
2011         return form;
2012     }
2013
2014     public EntryEditor getCurrentEditor() {
2015         return currentEditor;
2016     }
2017
2018     /**
2019      * Sets the given entry editor as the bottom component in the split pane. If an entry editor already
2020      * was shown, makes sure that the divider doesn't move.
2021      * Updates the mode to SHOWING_EDITOR.
2022      * @param editor The entry editor to add.
2023      */
2024     public void showEntryEditor(EntryEditor editor) {
2025         int oldSplitterLocation = -1;
2026         if (mode == SHOWING_EDITOR)
2027             oldSplitterLocation = splitPane.getDividerLocation();
2028         boolean adjustSplitter = (mode == WILL_SHOW_EDITOR);
2029         mode = SHOWING_EDITOR;
2030         currentEditor = editor;
2031         splitPane.setBottomComponent(editor);
2032         if (oldSplitterLocation > 0)
2033             splitPane.setDividerLocation(oldSplitterLocation);
2034         if (adjustSplitter) {
2035             adjustSplitter();
2036             //new FocusRequester(editor);
2037         }
2038     }
2039
2040     /**
2041      * Sets the given preview panel as the bottom component in the split panel.
2042      * Updates the mode to SHOWING_PREVIEW.
2043      * @param preview The preview to show.
2044      */
2045     public void showPreview(PreviewPanel preview) {
2046         mode = SHOWING_PREVIEW;
2047         currentPreview = preview;
2048         splitPane.setBottomComponent(preview.getPane());
2049     }
2050
2051     /**
2052      * Removes the bottom component.
2053      */
2054     public void hideBottomComponent() {
2055         mode = SHOWING_NOTHING;
2056         splitPane.setBottomComponent(null);
2057     }
2058
2059     /**
2060      * This method selects the given entry, and scrolls it into view in the table.
2061      * If an entryEditor is shown, it is given focus afterwards.
2062      */
2063     public void highlightEntry(final BibtexEntry be) {
2064         //SwingUtilities.invokeLater(new Thread() {
2065         //     public void run() {
2066                  final int row = mainTable.findEntry(be);
2067                  if (row >= 0) {
2068                     mainTable.setRowSelectionInterval(row, row);
2069                     //entryTable.setActiveRow(row);
2070                     mainTable.ensureVisible(row);
2071                  }
2072         //     }
2073         //});
2074     }
2075
2076
2077     /**
2078      * This method is called from an EntryEditor when it should be closed. We relay
2079      * to the selection listener, which takes care of the rest.
2080      * @param editor The entry editor to close.
2081      */
2082     public void entryEditorClosing(EntryEditor editor) {
2083         selectionListener.entryEditorClosing(editor);
2084     }
2085
2086     /**
2087      * This method selects the given enties.
2088      * If an entryEditor is shown, it is given focus afterwards.
2089      */
2090     /*public void selectEntries(final BibtexEntry[] bes, final int toScrollTo) {
2091
2092         SwingUtilities.invokeLater(new Thread() {
2093              public void run() {
2094                  int rowToScrollTo = 0;
2095                  entryTable.revalidate();
2096                  entryTable.clearSelection();
2097                  loop: for (int i=0; i<bes.length; i++) {
2098                     if (bes[i] == null)
2099                         continue loop;
2100                     int row = tableModel.getNumberFromName(bes[i].getId());
2101                     if (i==toScrollTo)
2102                     rowToScrollTo = row;
2103                     if (row >= 0)
2104                         entryTable.addRowSelectionIntervalQuietly(row, row);
2105                  }
2106                  entryTable.ensureVisible(rowToScrollTo);
2107                  Component comp = splitPane.getBottomComponent();
2108                  //if (comp instanceof EntryEditor)
2109                  //    comp.requestFocus();
2110              }
2111         });
2112     } */
2113
2114     /**
2115      * Closes the entry editor if it is showing the given entry.
2116      *
2117      * @param be a <code>BibtexEntry</code> value
2118      */
2119     public void ensureNotShowing(BibtexEntry be) {
2120         if ((mode == SHOWING_EDITOR) && (currentEditor.getEntry() == be)) {
2121             selectionListener.entryEditorClosing(currentEditor);
2122         }
2123     }
2124
2125     public void updateEntryEditorIfShowing() {
2126         if (mode == SHOWING_EDITOR) {
2127             if (currentEditor.getType() != currentEditor.getEntry().getType()) {
2128                 // The entry has changed type, so we must get a new editor.
2129                 showing = null;
2130                 EntryEditor newEditor = getEntryEditor(currentEditor.getEntry());
2131                 showEntryEditor(newEditor);
2132             } else {
2133                 currentEditor.updateAllFields();
2134                 currentEditor.updateSource();
2135             }
2136         }
2137     }
2138
2139     /**
2140      * If an entry editor is showing, make sure its currently focused field
2141      * stores its changes, if any.
2142      */
2143     public void storeCurrentEdit() {
2144         if (isShowingEditor()) {
2145             EntryEditor editor = (EntryEditor)splitPane.getBottomComponent();
2146             editor.storeCurrentEdit();
2147         }
2148
2149     }
2150
2151     /**
2152      * This method iterates through all existing entry editors in this
2153      * BasePanel, telling each to update all its instances of
2154      * FieldContentSelector. This is done to ensure that the list of words
2155      * in each selector is up-to-date after the user has made changes in
2156      * the Manage dialog.
2157      */
2158     public void updateAllContentSelectors() {
2159         for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2160             EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2161             ed.updateAllContentSelectors();
2162         }
2163     }
2164
2165     public void rebuildAllEntryEditors() {
2166         for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2167             EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2168             ed.rebuildPanels();
2169         }
2170
2171     }
2172
2173     public void markBaseChanged() {
2174         baseChanged = true;
2175
2176         // Put an asterix behind the file name to indicate the
2177         // database has changed.
2178         String oldTitle = frame.getTabTitle(this);
2179         if (!oldTitle.endsWith("*"))
2180             frame.setTabTitle(this, oldTitle+"*", frame.getTabTooltip(this));
2181
2182         // If the status line states that the base has been saved, we
2183         // remove this message, since it is no longer relevant. If a
2184         // different message is shown, we leave it.
2185         if (frame.statusLine.getText().startsWith("Saved database"))
2186             frame.output(" ");
2187     }
2188
2189     public void markNonUndoableBaseChanged() {
2190         nonUndoableChange = true;
2191         markBaseChanged();
2192     }
2193
2194     public synchronized void markChangedOrUnChanged() {
2195         if (undoManager.hasChanged()) {
2196             if (!baseChanged)
2197                 markBaseChanged();
2198         }
2199         else if (baseChanged && !nonUndoableChange) {
2200             baseChanged = false;
2201             if (getFile() != null)
2202                 frame.setTabTitle(BasePanel.this, getFile().getName(),
2203                         getFile().getAbsolutePath());
2204             else
2205                 frame.setTabTitle(BasePanel.this, Globals.lang("untitled"), null);
2206         }
2207     }
2208
2209     /**
2210      * Selects a single entry, and scrolls the table to center it.
2211      *
2212      * @param pos Current position of entry to select.
2213      *
2214      */
2215     public void selectSingleEntry(int pos) {
2216         mainTable.clearSelection();
2217         mainTable.addRowSelectionInterval(pos, pos);
2218         mainTable.scrollToCenter(pos, 0);
2219     }
2220
2221     /* *
2222      * Selects all entries with a non-zero value in the field
2223      * @param field <code>String</code> field name.
2224      */
2225 /*    public void selectResults(String field) {
2226       LinkedList intervals = new LinkedList();
2227       int prevStart = -1, prevToSel = 0;
2228       // First we build a list of intervals to select, without touching the table.
2229       for (int i = 0; i < entryTable.getRowCount(); i++) {
2230         String value = (String) (database.getEntryById
2231                                  (tableModel.getIdForRow(i)))
2232             .getField(field);
2233         if ( (value != null) && !value.equals("0")) {
2234           if (prevStart < 0)
2235             prevStart = i;
2236           prevToSel = i;
2237         }
2238         else if (prevStart >= 0) {
2239           intervals.add(new int[] {prevStart, prevToSel});
2240           prevStart = -1;
2241         }
2242       }
2243       // Then select those intervals, if any.
2244       if (intervals.size() > 0) {
2245         entryTable.setSelectionListenerEnabled(false);
2246         entryTable.clearSelection();
2247         for (Iterator i=intervals.iterator(); i.hasNext();) {
2248           int[] interval = (int[])i.next();
2249           entryTable.addRowSelectionInterval(interval[0], interval[1]);
2250         }
2251         entryTable.setSelectionListenerEnabled(true);
2252       }
2253   */
2254
2255     public void setSearchMatcher(SearchMatcher matcher) {
2256         searchFilterList.setMatcher(matcher);
2257     }
2258
2259     public void setGroupMatcher(Matcher matcher) {
2260         groupFilterList.setMatcher(matcher);
2261     }
2262
2263     public void stopShowingSearchResults() {
2264         searchFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2265     }
2266
2267     public void stopShowingGroup() {
2268         groupFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2269
2270      }
2271
2272      public BibtexDatabase getDatabase(){
2273         return database ;
2274     }
2275
2276     public void preambleEditorClosing() {
2277         preambleEditor = null;
2278     }
2279
2280     public void stringsClosing() {
2281         stringDialog = null;
2282     }
2283
2284     public void changeType(BibtexEntry entry, BibtexEntryType type) {
2285       changeType(new BibtexEntry[] {entry}, type);
2286     }
2287
2288     public void changeType(BibtexEntryType type) {
2289       BibtexEntry[] bes = mainTable.getSelectedEntries();
2290       changeType(bes, type);
2291     }
2292
2293     public void changeType(BibtexEntry[] bes, BibtexEntryType type) {
2294
2295         if ((bes == null) || (bes.length == 0)) {
2296             output("First select the entries you wish to change type "+
2297                    "for.");
2298             return;
2299         }
2300         if (bes.length > 1) {
2301             int choice = JOptionPane.showConfirmDialog
2302                 (this, "Multiple entries selected. Do you want to change"
2303                  +"\nthe type of all these to '"+type.getName()+"'?",
2304                  "Change type", JOptionPane.YES_NO_OPTION,
2305                  JOptionPane.WARNING_MESSAGE);
2306             if (choice == JOptionPane.NO_OPTION)
2307                 return;
2308         }
2309
2310         NamedCompound ce = new NamedCompound(Globals.lang("change type"));
2311         for (int i=0; i<bes.length; i++) {
2312             ce.addEdit(new UndoableChangeType(bes[i],
2313                                               bes[i].getType(),
2314                                               type));
2315             bes[i].setType(type);
2316         }
2317
2318         output(Globals.lang("Changed type to")+" '"+type.getName()+"' "
2319                +Globals.lang("for")+" "+bes.length
2320                +" "+Globals.lang("entries")+".");
2321         ce.end();
2322         undoManager.addEdit(ce);
2323         markBaseChanged();
2324         updateEntryEditorIfShowing();
2325     }
2326
2327     public boolean showDeleteConfirmationDialog(int numberOfEntries) {
2328         if (Globals.prefs.getBoolean("confirmDelete")) {
2329             String msg = Globals.lang("Really delete the selected")
2330                 + " " + Globals.lang("entry") + "?",
2331                 title = Globals.lang("Delete entry");
2332             if (numberOfEntries > 1) {
2333                 msg = Globals.lang("Really delete the selected")
2334                     + " " + numberOfEntries + " " + Globals.lang("entries") + "?";
2335                 title = Globals.lang("Delete multiple entries");
2336             }
2337
2338             CheckBoxMessage cb = new CheckBoxMessage
2339                 (msg, Globals.lang("Disable this confirmation dialog"), false);
2340
2341             int answer = JOptionPane.showConfirmDialog(frame, cb, title,
2342                                                        JOptionPane.YES_NO_OPTION,
2343                                                        JOptionPane.QUESTION_MESSAGE);
2344             if (cb.isSelected())
2345                 Globals.prefs.putBoolean("confirmDelete", false);
2346             return (answer == JOptionPane.YES_OPTION);
2347         } else return true;
2348
2349     }
2350
2351     /**
2352      * Activates or deactivates the entry preview, depending on the argument.
2353      * When deactivating, makes sure that any visible preview is hidden.
2354      * @param enabled
2355      */
2356     public void setPreviewActive(boolean enabled) {
2357         selectionListener.setPreviewActive(enabled);
2358     }
2359
2360
2361     class UndoAction extends BaseAction {
2362         public void action() {
2363             try {
2364                 String name = undoManager.getUndoPresentationName();
2365                 undoManager.undo();
2366                 markBaseChanged();
2367                 frame.output(name);
2368             } catch (CannotUndoException ex) {
2369                 frame.output(Globals.lang("Nothing to undo")+".");
2370             }
2371             // After everything, enable/disable the undo/redo actions
2372             // appropriately.
2373             //updateUndoState();
2374             //redoAction.updateRedoState();
2375             markChangedOrUnChanged();
2376         }
2377     }
2378
2379     class RedoAction extends BaseAction {
2380         public void action() {
2381             try {
2382                 String name = undoManager.getRedoPresentationName();
2383                 undoManager.redo();
2384                 markBaseChanged();
2385                 frame.output(name);
2386             } catch (CannotRedoException ex) {
2387                 frame.output(Globals.lang("Nothing to redo")+".");
2388             }
2389             // After everything, enable/disable the undo/redo actions
2390             // appropriately.
2391             //updateRedoState();
2392             //undoAction.updateUndoState();
2393             markChangedOrUnChanged();
2394         }
2395     }
2396
2397     // Method pertaining to the ClipboardOwner interface.
2398     public void lostOwnership(Clipboard clipboard, Transferable contents) {}
2399
2400
2401   public void setEntryEditorEnabled(boolean enabled) {
2402     if ((showing != null) && (splitPane.getBottomComponent() instanceof EntryEditor)) {
2403           EntryEditor ed = (EntryEditor)splitPane.getBottomComponent();
2404           if (ed.isEnabled() != enabled)
2405             ed.setEnabled(enabled);
2406     }
2407   }
2408
2409   public String fileMonitorHandle() { return fileMonitorHandle; }
2410
2411     public void fileUpdated() {
2412       if (saving)
2413         return; // We are just saving the file, so this message is most likely due
2414       // to bad timing. If not, we'll handle it on the next polling.
2415       //Util.pr("File '"+file.getPath()+"' has been modified.");
2416       updatedExternally = true;
2417
2418       // Adding the sidepane component is Swing work, so we must do this in the Swing
2419       // thread:
2420       Thread t = new Thread() {
2421         public void run() {
2422             // Test: running scan automatically in background
2423             ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this);
2424             scanner.changeScan(BasePanel.this.getFile());
2425             try {
2426                 scanner.join();
2427             } catch (InterruptedException e) {
2428                 e.printStackTrace();
2429             }
2430
2431             if (scanner.changesFound()) {
2432                 FileUpdatePanel pan = new FileUpdatePanel(frame, BasePanel.this,
2433                         sidePaneManager, getFile(), scanner);
2434           sidePaneManager.register("fileUpdate", pan);
2435           sidePaneManager.show("fileUpdate");
2436                 setUpdatedExternally(false);
2437                 //scanner.displayResult();
2438             } else {
2439                 setUpdatedExternally(false);
2440                 //System.out.println("No changes found.");
2441         }
2442
2443         }
2444       };
2445       SwingUtilities.invokeLater(t);
2446
2447     }
2448
2449       public void fileRemoved() {
2450         Util.pr("File '"+getFile().getPath()+"' has been deleted.");
2451       }
2452
2453
2454       public void cleanUp() {
2455         if (fileMonitorHandle != null)
2456           Globals.fileUpdateMonitor.removeUpdateListener(fileMonitorHandle);
2457       }
2458
2459   public void setUpdatedExternally(boolean b) {
2460     updatedExternally = b;
2461   }
2462
2463     /**
2464      * Get an array containing the currently selected entries.
2465      *
2466      * @return An array containing the selected entries.
2467      */
2468     public BibtexEntry[] getSelectedEntries() {
2469         return mainTable.getSelectedEntries();
2470     }
2471
2472     /**
2473      * Get the file where this database was last saved to or loaded from, if any.
2474      *
2475      * @return The relevant File, or null if none is defined.
2476      */
2477     public File getFile() {
2478         return metaData.getFile();
2479     }
2480     
2481     /**
2482      * Get a String containing a comma-separated list of the bibtex keys
2483      * of the selected entries.
2484      *
2485      * @return A comma-separated list of the keys of the selected entries.
2486      */
2487     public String getKeysForSelection() {
2488         List entries = mainTable.getSelected();
2489         StringBuffer result = new StringBuffer();
2490         String citeKey = "";//, message = "";
2491         boolean first = true;
2492         for (Iterator i = entries.iterator(); i.hasNext();) {
2493             BibtexEntry bes = (BibtexEntry) i.next();
2494             citeKey = (String) bes.getField(BibtexFields.KEY_FIELD);
2495             // if the key is empty we give a warning and ignore this entry
2496             if (citeKey == null || citeKey.equals(""))
2497                 continue;
2498             if (first) {
2499                 result.append(citeKey);
2500                 first = false;
2501             } else {
2502                 result.append(",").append(citeKey);
2503             }
2504         }
2505         return result.toString();
2506     }
2507
2508     public GroupSelector getGroupSelector() {
2509         return frame.groupSelector;
2510     }
2511
2512 }