2 Copyright (C) 2003 Morten O. Alver and Nizar N. Batada
4 All programs in this directory and
5 subdirectories are published under the GNU General Public License as
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.
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.
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
23 Further information about the GNU GPL is available at:
24 http://www.gnu.org/copyleft/gpl.ja.html
29 package net.sf.jabref;
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;
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;
56 import java.util.Vector;
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;
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;
104 import com.jgoodies.forms.builder.DefaultFormBuilder;
105 import com.jgoodies.forms.layout.FormLayout;
106 import com.jgoodies.uif_lite.component.UIFSplitPane;
108 public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener {
110 public final static int SHOWING_NOTHING=0, SHOWING_PREVIEW=1, SHOWING_EDITOR=2, WILL_SHOW_EDITOR=3;
112 private EntryEditor currentEditor = null;
113 private PreviewPanel currentPreview = null;
117 private MainTableSelectionListener selectionListener = null;
118 private ListEventListener groupsHighlightListener;
119 UIFSplitPane contentPane = new UIFSplitPane();
121 JSplitPane splitPane;
122 //BibtexEntry testE = new BibtexEntry("tt");
123 //boolean previewActive = true;
126 BibtexDatabase database;
127 // The database shown in this panel.
129 // Moving file to MetaData (Morten, 2006.08.29)
130 // private File fileToOpen = null;
132 String fileMonitorHandle = null;
133 boolean saving = false, updatedExternally = false;
134 private String encoding;
136 GridBagLayout gbl = new GridBagLayout();
137 GridBagConstraints con = new GridBagConstraints();
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.
144 public CountingUndoManager undoManager = new CountingUndoManager(this);
145 UndoAction undoAction = new UndoAction();
146 RedoAction redoAction = new RedoAction();
148 //ExampleFileFilter fileFilter;
149 // File filter for .bib files.
151 boolean baseChanged = false, nonUndoableChange = false;
152 // Used to track whether the base has changed since last save.
154 //EntryTableModel tableModel = null;
155 //public EntryTable entryTable = null;
156 public MainTable mainTable = null;
157 public FilterList searchFilterList = null, groupFilterList = null;
159 public RightClickMenu rcm;
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.
167 //HashMap entryTypeForms = new HashMap();
168 // Hashmap to keep track of which entries currently have open
169 // EntryTypeForm dialogs.
171 PreambleEditor preambleEditor = null;
172 // Keeps track of the preamble dialog if it is open.
174 StringDialog stringDialog = null;
175 // Keeps track of the string dialog if it is open.
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.
182 //GroupSelector groupSelector;
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.
194 // MetaData parses, keeps and writes meta data.
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
200 private boolean suppressOutput = false;
202 private HashMap actions = new HashMap();
203 private SidePaneManager sidePaneManager;
206 * Create a new BasePanel with an empty database.
207 * @param frame The application window.
209 public BasePanel(JabRefFrame frame) {
210 this.sidePaneManager = Globals.sidePaneManager;
211 database = new BibtexDatabase();
212 metaData = new MetaData();
213 metaData.initializeNewDatabase();
217 encoding = Globals.prefs.get("defaultEncoding");
218 //System.out.println("Default: "+encoding);
221 public BasePanel(JabRefFrame frame, BibtexDatabase db, File file,
222 HashMap meta, String encoding) {
224 this.encoding = encoding;
225 // System.out.println(encoding);
226 //super(JSplitPane.HORIZONTAL_SPLIT, true);
227 this.sidePaneManager = Globals.sidePaneManager;
233 metaData = new MetaData();
234 metaData.initializeNewDatabase();
238 /*if (Globals.prefs.getBoolean("autoComplete")) {
239 db.setCompleters(autoCompleters);
242 metaData.setFile(file);
244 // Register so we get notifications about outside changes to the file.
247 fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(this,
249 } catch (IOException ex) {
253 public boolean isBaseChanged(){
257 public int getMode() {
261 public BibtexDatabase database() {
265 public MetaData metaData() {
269 public JabRefFrame frame() {
273 public JabRefPreferences prefs() {
274 return Globals.prefs;
277 public String getEncoding() {
281 public void setEncoding(String encoding) {
282 this.encoding = encoding;
285 public void output(String s) {
286 //Util.pr("\""+s+"\""+(SwingUtilities.isEventDispatchThread()));
291 private void setupActions() {
293 actions.put("undo", undoAction);
294 actions.put("redo", redoAction);
296 // The action for opening an entry editor.
297 actions.put("edit", new BaseAction() {
298 public void action() {
299 selectionListener.editSignalled();
302 if (isShowingEditor()) {
303 new FocusRequester(splitPane.getBottomComponent());
309 //public void run() {
311 // We demand that one and only one row is selected.
312 if (entryTable.getSelectedRowCount() == 1) {
313 clickedOn = entryTable.getSelectedRow();
315 if (clickedOn >= 0) {
316 String id = tableModel.getIdForRow(clickedOn);
317 BibtexEntry be = database.getEntryById(id);
320 if (splitPane.getBottomComponent() != null) {
321 new FocusRequester(splitPane.getBottomComponent());
331 actions.put("test", new BaseAction () {
332 public void action() throws Throwable {
334 ExportFormats.initAllExports();
335 JFileChooser fc = ExportFormats.createExportFileChooser("/home/alver/Documents");
336 fc.showSaveDialog(frame);
337 File file = fc.getSelectedFile();
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
346 Globals.prefs.put("lastUsedExport", format.getConsoleName());
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 {
360 if (getFile() == null)
361 runCommand("saveAs");
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);*/
375 if (answer == JOptionPane.CANCEL_OPTION)
377 else if (answer == JOptionPane.YES_OPTION) {
378 ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this); //, panel.database(), panel.metaData());
380 scanner.changeScan(getFile());
381 setUpdatedExternally(false);
382 SwingUtilities.invokeLater(new Runnable() {
384 sidePaneManager.hide("fileUpdate");
388 //} catch (IOException ex) {
389 // ex.printStackTrace();
396 frame.output(Globals.lang("Saving database") + "...");
401 public void update() {
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"));
414 if (getFile() == null) {
420 // If the option is set, autogenerate keys for all entries that are
421 // lacking keys, before saving:
422 if (Globals.prefs.getBoolean("generateKeysBeforeSaving")) {
424 NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
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)));
436 // Store undo information, if any:
439 undoManager.addEdit(ce);
442 // Done with autosetting keys. Now save the database:
444 success = saveDatabase(getFile(), false, encoding);
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."));
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.
459 undoManager.markUnchanged();
460 // (Only) after a successful save the following
461 // statement marks that the base is unchanged
463 nonUndoableChange = false;
465 updatedExternally = false;
467 } catch (SaveException ex2) {
468 ex2.printStackTrace();
473 actions.put("saveAs", new BaseAction () {
474 public void action() throws Throwable {
476 String chosenFile = Globals.getNewFile(frame, new File(Globals.prefs.get("workingDirectory")), ".bib",
477 JFileChooser.SAVE_DIALOG, false);
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)) {
489 // Register so we get notifications about outside changes to the file.
491 fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(BasePanel.this,getFile());
492 } catch (IOException ex) {
493 ex.printStackTrace();
496 Globals.prefs.put("workingDirectory", metaData.getFile().getParent());
497 frame.getFileHistory().newFile(metaData.getFile().getPath());
500 metaData.setFile(null);
506 actions.put("saveSelectedAs", new BaseAction () {
507 public void action() throws Throwable {
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()+"'.");
529 // The action for copying selected entries.
530 actions.put("copy", new BaseAction() {
531 public void action() {
532 BibtexEntry[] bes = mainTable.getSelectedEntries();
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")+"."));
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]);
551 StringSelection ss = new StringSelection(o.toString());
552 Toolkit.getDefaultToolkit().getSystemClipboard()
553 .setContents(ss, BasePanel.this);
555 output(Globals.lang("Copied cell contents")+".");
562 actions.put("cut", new BaseAction() {
563 public void action() throws Throwable {
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));
578 //entryTable.clearSelection();
579 frame.output(Globals.lang("Cut_pr")+" "+
580 (bes.length>1 ? bes.length
581 +" "+ Globals.lang("entries")
582 : Globals.lang("entry"))+".");
584 undoManager.addEdit(ce);
587 // Reselect the entry in the first prev. selected position:
588 /*if (row0 >= entryTable.getRowCount())
589 row0 = entryTable.getRowCount()-1;
591 entryTable.addRowSelectionInterval(row0, row0);*/
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)) {
603 boolean goOn = showDeleteConfirmationDialog(bes.length);
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));
618 frame.output(Globals.lang("Deleted") + " " +
619 (bes.length > 1 ? bes.length
620 + " " + Globals.lang("entries")
621 : Globals.lang("entry")) + ".");
623 undoManager.addEdit(ce);
624 //entryTable.clearSelection();
628 // Reselect the entry in the first prev. selected position:
629 /*if (row0 >= entryTable.getRowCount())
630 row0 = entryTable.getRowCount()-1;
632 final int toSel = row0;
634 SwingUtilities.invokeLater(new Runnable() {
636 entryTable.addRowSelectionInterval(toSel, toSel);
637 //entryTable.ensureVisible(toSel);
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.
664 bes = (BibtexEntry[])(content.getTransferData(TransferableBibtexEntry.entryFlavor));
666 } catch (UnsupportedFlavorException ex) {
667 ex.printStackTrace();
668 } catch (IOException ex) {
669 ex.printStackTrace();
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
681 tableModel.setValueAt((String)(content.getTransferData(DataFlavor.stringFlavor)), rows[0], cols[0]);
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.");
693 // no single cell is selected, so try parsing the clipboard contents as bibtex entries instead
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()));
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."));
718 } catch (UnsupportedFlavorException ex) {
719 ex.printStackTrace();
720 } catch (Throwable ex) {
721 ex.printStackTrace();
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)) {
730 NamedCompound ce = new NamedCompound
731 (Globals.lang(bes.length > 1 ? "paste entries" : "paste entry"));
732 for (int i=0; i<bes.length; i++) {
734 BibtexEntry be = (BibtexEntry)(bes[i].clone());
735 Util.setAutomaticFields(be);
737 // We have to clone the
738 // entries, since the pasted
739 // entries must exist
740 // independently of the copied
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.");
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"))
766 actions.put("selectAll", new BaseAction() {
767 public void action() {
768 mainTable.selectAll();
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;
782 preambleEditor.setVisible(true);
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);
798 stringDialog.setVisible(true);
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"));
813 // The action for auto-generating keys.
814 actions.put("makeKey", new AbstractWorker() {
818 boolean cancelled = false;
820 // Run first, in EDT:
823 entries = new ArrayList(Arrays.asList(getSelectedEntries()));
824 //rows = entryTable.getSelectedRows() ;
825 numSelected = entries.size();
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);
833 output(Globals.lang("Generating BibTeX key for")+" "+
834 numSelected+" "+(numSelected>1 ? Globals.lang("entries")
835 : Globals.lang("entry"))+"...");
838 // Run second, on a different thread:
840 BibtexEntry bes = null ;
841 NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
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:
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.
866 // No need to check more entries, because the user has already confirmed
867 // that it's ok to overwrite keys:
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);
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)));
892 undoManager.addEdit(ce);
895 // Run third, on EDT:
896 public void update() {
902 numSelected = entries.size();
903 output(Globals.lang("Generated BibTeX key for")+" "+
904 numSelected+" "+(numSelected!=1 ? Globals.lang("entries")
905 : Globals.lang("entry")));
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);
917 frame.searchManager.startSearch();
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);
928 frame.searchManager.startSearch();
932 actions.put("incSearch", new BaseAction() {
933 public void action() {
934 sidePaneManager.show("search");
935 frame.searchToggle.setSelected(true);
936 frame.searchManager.startIncrementalSearch();
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)) {
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.");
956 StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
957 for (int i=1; i<keys.size(); i++) {
959 sb.append((String)keys.elementAt(i));
962 StringSelection ss = new StringSelection(sb.toString());
963 Toolkit.getDefaultToolkit().getSystemClipboard()
964 .setContents(ss, BasePanel.this);
966 if (keys.size() == bes.length)
967 // All entries had keys.
968 output(Globals.lang((bes.length > 1) ? "Copied keys"
969 : "Copied key")+".");
971 output(Globals.lang("Warning")+": "+(bes.length-keys.size())
972 +" "+Globals.lang("out of")+" "+bes.length+" "+
973 Globals.lang("entries have undefined BibTeX key")+".");
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)) {
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.");
994 StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
995 for (int i=1; i<keys.size(); i++) {
997 sb.append((String)keys.elementAt(i));
1000 StringSelection ss = new StringSelection
1001 ("\\cite{"+sb.toString()+"}");
1002 Toolkit.getDefaultToolkit().getSystemClipboard()
1003 .setContents(ss, BasePanel.this);
1005 if (keys.size() == bes.length)
1006 // All entries had keys.
1007 output(Globals.lang((bes.length > 1) ? "Copied keys"
1008 : "Copied key")+".");
1010 output(Globals.lang("Warning")+": "+(bes.length-keys.size())
1011 +" "+Globals.lang("out of")+" "+bes.length+" "+
1012 Globals.lang("entries have undefined BibTeX key")+".");
1017 actions.put("mergeDatabase", new AppendDatabaseAction(frame, this));
1023 actions.put("openFile", new BaseAction() {
1024 public void action() {
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");
1035 String filepath = null;
1037 filepath = link.toString();
1041 // see if we can fall back to a filename based on the bibtex key
1043 Object key = bes[0].getField(BibtexFields.KEY_FIELD);
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());
1053 String found = Util.findPdf(basefile, types[i], dir, new OpenFileFilter("."+types[i]));
1054 if (found != null) {
1055 filepath = dir+sep+found;
1063 if (filepath != null) {
1064 //output(Globals.lang("Calling external viewer..."));
1066 Util.openExternalViewer(metaData(), filepath, field);
1067 output(Globals.lang("External viewer called") + ".");
1069 catch (IOException ex) {
1070 output(Globals.lang("Error") + ": " + ex.getMessage());
1074 output(Globals.lang(
1075 "No pdf or ps defined, and no file matching Bibtex key found") +
1079 output(Globals.lang("No entries or multiple entries selected."));
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");
1097 //output(Globals.lang("Calling external viewer..."));
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());
1106 output(Globals.lang("No url defined")+".");
1108 output(Globals.lang("No entries or multiple entries selected."));
1112 actions.put("replaceAll", new BaseAction() {
1113 public void action() {
1114 ReplaceStringDialog rsd = new ReplaceStringDialog(frame);
1115 rsd.setVisible(true);
1116 if (!rsd.okPressed())
1119 NamedCompound ce = new NamedCompound(Globals.lang("Replace string"));
1120 if (!rsd.selOnly()) {
1121 for (Iterator i=database.getKeySet().iterator();
1123 counter += rsd.replace(database.getEntryById((String)i.next()), ce);
1125 BibtexEntry[] bes = mainTable.getSelectedEntries();
1126 for (int i=0; i<bes.length; i++)
1127 counter += rsd.replace(bes[i], ce);
1130 output(Globals.lang("Replaced")+" "+counter+" "+
1131 Globals.lang(counter==1?"occurence":"occurences")+".");
1134 undoManager.addEdit(ce);
1140 actions.put("dupliCheck", new BaseAction() {
1141 public void action() {
1142 DuplicateSearch ds = new DuplicateSearch(BasePanel.this);
1147 actions.put("strictDupliCheck", new BaseAction() {
1148 public void action() {
1149 StrictDuplicateSearch ds = new StrictDuplicateSearch(BasePanel.this);
1154 actions.put("plainTextImport", new BaseAction() {
1155 public void action()
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();
1165 String id = Util.createNeutralId();
1166 BibtexEntry bibEntry = new BibtexEntry(id, tp) ;
1167 TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1170 Util.placeDialog(tidialog, BasePanel.this);
1171 tidialog.setVisible(true);
1173 if (tidialog.okPressed())
1175 Util.setAutomaticFields(Arrays.asList(new BibtexEntry[] {bibEntry}));
1176 insertEntry(bibEntry) ;
1181 // The action starts the "import from plain text" dialog
1182 /*actions.put("importPlainText", new BaseAction() {
1183 public void action()
1185 BibtexEntry bibEntry = null ;
1186 // try to get the first marked entry
1187 BibtexEntry[] bes = entryTable.getSelectedEntries();
1188 if ((bes != null) && (bes.length > 0))
1191 if (bibEntry != null)
1193 // Create an UndoableInsertEntry object.
1194 undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1196 TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1199 Util.placeDialog(tidialog, BasePanel.this);
1200 tidialog.setVisible(true);
1202 if (tidialog.okPressed())
1204 output(Globals.lang("changed ")+" '"
1205 +bibEntry.getType().getName().toLowerCase()+"' "
1206 +Globals.lang("entry")+".");
1208 int row = tableModel.getNumberFromName(bibEntry.getId());
1210 entryTable.clearSelection();
1211 entryTable.scrollTo(row);
1212 markBaseChanged(); // The database just changed.
1213 if (Globals.prefs.getBoolean("autoOpenForm"))
1215 showEntry(bibEntry);
1222 actions.put("markEntries", new AbstractWorker() {
1223 private int besLength = -1;
1226 NamedCompound ce = new NamedCompound(Globals.lang("Mark entries"));
1227 BibtexEntry[] bes = mainTable.getSelectedEntries();
1228 besLength = bes.length;
1231 for (int i=0; i<bes.length; i++) {
1232 Util.markEntry(bes[i], ce);
1235 undoManager.addEdit(ce);
1238 public void update() {
1240 output(Globals.lang("Marked selected")+" "+Globals.lang(besLength>0?"entry":"entries"));
1245 actions.put("unmarkEntries", new BaseAction() {
1246 public void action() {
1248 NamedCompound ce = new NamedCompound(Globals.lang("Unmark entries"));
1249 BibtexEntry[] bes = mainTable.getSelectedEntries();
1252 for (int i=0; i<bes.length; i++) {
1253 Util.unmarkEntry(bes[i], database, ce);
1256 undoManager.addEdit(ce);
1258 output(Globals.lang("Unmarked selected")+" "+Globals.lang(bes.length>0?"entry":"entries"));
1259 } catch (Throwable ex) { ex.printStackTrace(); }
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);
1273 undoManager.addEdit(ce);
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);
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);
1293 frame.highlightAll.setSelected(false);
1294 Globals.prefs.putBoolean("highlightGroupsMatchingAll", false);
1296 // ping the listener so it updates:
1297 groupsHighlightListener.listChanged(null);
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);
1307 frame.highlightAny.setSelected(false);
1308 Globals.prefs.putBoolean("highlightGroupsMatchingAny", false);
1310 // ping the listener so it updates:
1311 groupsHighlightListener.listChanged(null);
1315 actions.put("switchPreview", new BaseAction() {
1316 public void action() {
1317 selectionListener.switchPreview();
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);
1331 actions.put("exportToClipboard", new AbstractWorker() {
1332 String message = null;
1334 if (mainTable.getSelected().size() == 0) {
1335 message = Globals.lang("No entries selected")+".";
1336 getCallBack().update();
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];
1351 Object[] array = formats.keySet().toArray();
1353 JList list = new JList(array);
1354 list.setBorder(BorderFactory.createEtchedBorder());
1355 list.setSelectionInterval(0,0);
1356 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
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"));
1364 if (answer == JOptionPane.NO_OPTION)
1367 String lfName = (String)(formats.get(list.getSelectedValue()));
1368 final boolean custom = (list.getSelectedIndex() >= Globals.STANDARD_EXPORT_COUNT);
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");
1379 final String format = lfName,
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) {}
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();
1400 public void update() {
1406 actions.put("writeXMP", new WriteXMPAction(this));
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"));
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
1422 * @param _command The name of the command to run.
1424 public void runCommand(String _command) {
1425 final String command = _command;
1427 // public void run() {
1428 if (actions.get(command) == null)
1429 Util.pr("No action defined for'" + command + "'");
1431 Object o = actions.get(command);
1433 if (o instanceof BaseAction)
1434 ((BaseAction)o).action();
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();
1443 ((AbstractWorker)o).init(); // This method runs in this same thread, the EDT.
1444 // Useful for initial GUI actions, like printing a message.
1446 // The CallBack returned by getCallBack() has been wrapped
1447 // by Spin.over(), which makes its methods be run on
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.
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.
1459 ex.printStackTrace();
1466 private boolean saveDatabase(File file, boolean selectedOnly, String encoding) throws SaveException {
1467 SaveSession session;
1471 session = FileActions.saveDatabase(database, metaData, file,
1472 Globals.prefs, false, false, encoding);
1474 session = FileActions.savePartOfDatabase(database, metaData, file,
1475 Globals.prefs, mainTable.getSelectedEntries(), encoding);
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());
1492 else ex.printStackTrace();
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");
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()));
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);
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);
1528 } else if (answer == JOptionPane.CANCEL_OPTION)
1537 this.encoding = encoding; // Make sure to remember which encoding we used.
1541 } catch (IOException e) {
1542 e.printStackTrace();
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.
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.
1557 public BibtexEntry newEntry(BibtexEntryType type) {
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();
1566 if (type != null) { // Only if the dialog was not cancelled.
1567 String id = Util.createNeutralId();
1568 final BibtexEntry be = new BibtexEntry(id, type);
1570 database.insertEntry(be);
1572 // Set owner/timestamp if options are enabled:
1573 ArrayList list = new ArrayList();
1575 Util.setAutomaticFields(list);
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);
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;
1591 highlightEntry(be); // Selects the entry. The selection listener will open the editor.
1593 markBaseChanged(); // The database just changed.
1594 new FocusRequester(getEntryEditor(be));
1596 } catch (KeyCollisionException ex) {
1597 Util.pr(ex.getMessage());
1606 * This method is called from JabRefFrame when the user wants to
1607 * create a new entry.
1608 * @param bibEntry The new entry.
1610 public void insertEntry(BibtexEntry bibEntry)
1612 if (bibEntry != null)
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);
1627 mainTable.clearSelection();
1628 mainTable.scrollTo(row);
1629 markBaseChanged(); // The database just changed.
1630 if (Globals.prefs.getBoolean("autoOpenForm"))
1632 showEntry(bibEntry);
1634 } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); }
1638 public void createMainTable() {
1639 //Comparator comp = new FieldComparator("author");
1641 GlazedEntrySorter eventList = new GlazedEntrySorter(database.getEntryMap(), null);
1642 // Must initialize sort columns somehow:
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);
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);
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);
1673 mainTable.addSelectionListener(groupsHighlightListener);
1675 mainTable.getActionMap().put("cut", new AbstractAction() {
1676 public void actionPerformed(ActionEvent e) {
1677 try { runCommand("cut");
1678 } catch (Throwable ex) {
1679 ex.printStackTrace();
1683 mainTable.getActionMap().put("copy", new AbstractAction() {
1684 public void actionPerformed(ActionEvent e) {
1685 try { runCommand("copy");
1686 } catch (Throwable ex) {
1687 ex.printStackTrace();
1691 mainTable.getActionMap().put("paste", new AbstractAction() {
1692 public void actionPerformed(ActionEvent e) {
1693 try { runCommand("paste");
1694 } catch (Throwable ex) {
1695 ex.printStackTrace();
1700 mainTable.addKeyListener(new KeyAdapter() {
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();
1707 if (e.isControlDown()) {
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:
1715 frame.groupSelector.moveNodeUp(node, true);
1717 case KeyEvent.VK_DOWN:
1720 frame.groupSelector.moveNodeDown(node, true);
1722 case KeyEvent.VK_LEFT:
1725 frame.groupSelector.moveNodeLeft(node, true);
1727 case KeyEvent.VK_RIGHT:
1730 frame.groupSelector.moveNodeRight(node, true);
1732 case KeyEvent.VK_PAGE_DOWN:
1733 frame.nextTab.actionPerformed(null);
1736 case KeyEvent.VK_PAGE_UP:
1737 frame.prevTab.actionPerformed(null);
1741 } else if (keyCode == KeyEvent.VK_ENTER){
1743 try { runCommand("edit");
1744 } catch (Throwable ex) {
1745 ex.printStackTrace();
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) {
1763 if (showing == null)
1764 return super.accept(c);
1766 return (super.accept(c) &&
1767 (c instanceof FieldEditor));
1773 splitPane.setTopComponent(mainTable.getPane());
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);
1782 mainTable.setRowSelectionInterval(row, row);
1785 else if (mode == SHOWING_EDITOR) {
1786 mode = SHOWING_NOTHING;
1787 /*int row = mainTable.findEntry(currentEditor.entry);
1789 mainTable.setRowSelectionInterval(row, row);
1791 //showEntryEditor(currentEditor);
1793 splitPane.setBottomComponent(null);
1796 setLayout(new BorderLayout());
1798 add(splitPane, BorderLayout.CENTER);
1799 //add(contentPane, BorderLayout.CENTER);
1801 //add(sidePaneManager.getPanel(), BorderLayout.WEST);
1802 //add(splitPane, BorderLayout.CENTER);
1805 //con.fill = GridBagConstraints.BOTH;
1808 //gbl.setConstraints(sidePaneManager.getPanel(), con);
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();
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
1827 * @param meta Metadata to input.
1829 public void parseMetaData(HashMap meta) {
1830 metaData = new MetaData(meta,database());
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();
1843 tableModel.update(lastSearchHits);
1845 tableModel.update();
1846 //tableModel.remap();
1847 if ((bes != null) && (bes.length > 0))
1848 selectEntries(bes, 0);
1850 //long toc = System.currentTimeMillis();
1851 // Util.pr("Refresh took: "+(toc-tic)+" ms");
1854 public void updatePreamble() {
1855 if (preambleEditor != null)
1856 preambleEditor.updatePreamble();
1859 public void assureStringDialogNotEditing() {
1860 if (stringDialog != null)
1861 stringDialog.assureNotEditing();
1864 public void updateStringDialog() {
1865 if (stringDialog != null)
1866 stringDialog.refreshTable();
1869 public void updateEntryPreviewToRow(BibtexEntry e) {
1873 public void adjustSplitter() {
1874 int mode = getMode();
1875 if (mode == SHOWING_PREVIEW) {
1876 splitPane.setDividerLocation(splitPane.getHeight()-GUIGlobals.PREVIEW_PANEL_HEIGHT);
1878 splitPane.setDividerLocation(GUIGlobals.VERTICAL_DIVIDER_LOCATION);
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.
1890 public boolean entryEditorAllowsChange() {
1891 Component c = splitPane.getBottomComponent();
1892 if ((c != null) && (c instanceof EntryEditor)) {
1893 return ((EntryEditor)c).lastSourceAccepted();
1899 public void moveFocusToEntryEditor() {
1900 Component c = splitPane.getBottomComponent();
1901 if ((c != null) && (c instanceof EntryEditor)) {
1902 new FocusRequester(c);
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.
1910 public void hidePreview() {
1911 Globals.prefs.putBoolean("previewEnabled", false);
1913 Component c = splitPane.getBottomComponent();
1914 if ((c != null) && !(c instanceof EntryEditor))
1915 splitPane.setBottomComponent(null);
1918 public boolean isShowingEditor() {
1919 return ((splitPane.getBottomComponent() != null)
1920 && (splitPane.getBottomComponent() instanceof EntryEditor));
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.
1934 // The correct entry is already being shown. Make sure the editor
1936 ((EntryEditor)splitPane.getBottomComponent()).updateAllFields();
1945 String visName = null;
1946 if (showing != null) {
1947 visName = ((EntryEditor)splitPane.getBottomComponent()).
1948 getVisiblePanelName();
1950 if (showing != null)
1951 divLoc = splitPane.getDividerLocation();
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()));
1958 if (visName != null)
1959 form.setVisiblePanel(visName);
1960 splitPane.setBottomComponent(form);
1961 //highlightEntry(be);
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);
1969 //highlightEntry(be);
1970 entryEditors.put(be.getType().getName(), form);
1974 splitPane.setDividerLocation(divLoc);
1977 splitPane.setDividerLocation
1978 (GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1979 //new FocusRequester(form);
1980 //form.requestFocus();
1983 setEntryEditorEnabled(true); // Make sure it is enabled.
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.
1992 public EntryEditor getEntryEditor(BibtexEntry entry) {
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()));
2000 form.switchTo(entry);
2001 //if (visName != null)
2002 // form.setVisiblePanel(visName);
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);
2009 entryEditors.put(entry.getType().getName(), form);
2014 public EntryEditor getCurrentEditor() {
2015 return currentEditor;
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.
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) {
2036 //new FocusRequester(editor);
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.
2045 public void showPreview(PreviewPanel preview) {
2046 mode = SHOWING_PREVIEW;
2047 currentPreview = preview;
2048 splitPane.setBottomComponent(preview.getPane());
2052 * Removes the bottom component.
2054 public void hideBottomComponent() {
2055 mode = SHOWING_NOTHING;
2056 splitPane.setBottomComponent(null);
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.
2063 public void highlightEntry(final BibtexEntry be) {
2064 //SwingUtilities.invokeLater(new Thread() {
2065 // public void run() {
2066 final int row = mainTable.findEntry(be);
2068 mainTable.setRowSelectionInterval(row, row);
2069 //entryTable.setActiveRow(row);
2070 mainTable.ensureVisible(row);
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.
2082 public void entryEditorClosing(EntryEditor editor) {
2083 selectionListener.entryEditorClosing(editor);
2087 * This method selects the given enties.
2088 * If an entryEditor is shown, it is given focus afterwards.
2090 /*public void selectEntries(final BibtexEntry[] bes, final int toScrollTo) {
2092 SwingUtilities.invokeLater(new Thread() {
2094 int rowToScrollTo = 0;
2095 entryTable.revalidate();
2096 entryTable.clearSelection();
2097 loop: for (int i=0; i<bes.length; i++) {
2100 int row = tableModel.getNumberFromName(bes[i].getId());
2102 rowToScrollTo = row;
2104 entryTable.addRowSelectionIntervalQuietly(row, row);
2106 entryTable.ensureVisible(rowToScrollTo);
2107 Component comp = splitPane.getBottomComponent();
2108 //if (comp instanceof EntryEditor)
2109 // comp.requestFocus();
2115 * Closes the entry editor if it is showing the given entry.
2117 * @param be a <code>BibtexEntry</code> value
2119 public void ensureNotShowing(BibtexEntry be) {
2120 if ((mode == SHOWING_EDITOR) && (currentEditor.getEntry() == be)) {
2121 selectionListener.entryEditorClosing(currentEditor);
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.
2130 EntryEditor newEditor = getEntryEditor(currentEditor.getEntry());
2131 showEntryEditor(newEditor);
2133 currentEditor.updateAllFields();
2134 currentEditor.updateSource();
2140 * If an entry editor is showing, make sure its currently focused field
2141 * stores its changes, if any.
2143 public void storeCurrentEdit() {
2144 if (isShowingEditor()) {
2145 EntryEditor editor = (EntryEditor)splitPane.getBottomComponent();
2146 editor.storeCurrentEdit();
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.
2158 public void updateAllContentSelectors() {
2159 for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2160 EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2161 ed.updateAllContentSelectors();
2165 public void rebuildAllEntryEditors() {
2166 for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2167 EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2173 public void markBaseChanged() {
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));
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"))
2189 public void markNonUndoableBaseChanged() {
2190 nonUndoableChange = true;
2194 public synchronized void markChangedOrUnChanged() {
2195 if (undoManager.hasChanged()) {
2199 else if (baseChanged && !nonUndoableChange) {
2200 baseChanged = false;
2201 if (getFile() != null)
2202 frame.setTabTitle(BasePanel.this, getFile().getName(),
2203 getFile().getAbsolutePath());
2205 frame.setTabTitle(BasePanel.this, Globals.lang("untitled"), null);
2210 * Selects a single entry, and scrolls the table to center it.
2212 * @param pos Current position of entry to select.
2215 public void selectSingleEntry(int pos) {
2216 mainTable.clearSelection();
2217 mainTable.addRowSelectionInterval(pos, pos);
2218 mainTable.scrollToCenter(pos, 0);
2222 * Selects all entries with a non-zero value in the field
2223 * @param field <code>String</code> field name.
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)))
2233 if ( (value != null) && !value.equals("0")) {
2238 else if (prevStart >= 0) {
2239 intervals.add(new int[] {prevStart, prevToSel});
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]);
2251 entryTable.setSelectionListenerEnabled(true);
2255 public void setSearchMatcher(SearchMatcher matcher) {
2256 searchFilterList.setMatcher(matcher);
2259 public void setGroupMatcher(Matcher matcher) {
2260 groupFilterList.setMatcher(matcher);
2263 public void stopShowingSearchResults() {
2264 searchFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2267 public void stopShowingGroup() {
2268 groupFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2272 public BibtexDatabase getDatabase(){
2276 public void preambleEditorClosing() {
2277 preambleEditor = null;
2280 public void stringsClosing() {
2281 stringDialog = null;
2284 public void changeType(BibtexEntry entry, BibtexEntryType type) {
2285 changeType(new BibtexEntry[] {entry}, type);
2288 public void changeType(BibtexEntryType type) {
2289 BibtexEntry[] bes = mainTable.getSelectedEntries();
2290 changeType(bes, type);
2293 public void changeType(BibtexEntry[] bes, BibtexEntryType type) {
2295 if ((bes == null) || (bes.length == 0)) {
2296 output("First select the entries you wish to change type "+
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)
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],
2315 bes[i].setType(type);
2318 output(Globals.lang("Changed type to")+" '"+type.getName()+"' "
2319 +Globals.lang("for")+" "+bes.length
2320 +" "+Globals.lang("entries")+".");
2322 undoManager.addEdit(ce);
2324 updateEntryEditorIfShowing();
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");
2338 CheckBoxMessage cb = new CheckBoxMessage
2339 (msg, Globals.lang("Disable this confirmation dialog"), false);
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);
2352 * Activates or deactivates the entry preview, depending on the argument.
2353 * When deactivating, makes sure that any visible preview is hidden.
2356 public void setPreviewActive(boolean enabled) {
2357 selectionListener.setPreviewActive(enabled);
2361 class UndoAction extends BaseAction {
2362 public void action() {
2364 String name = undoManager.getUndoPresentationName();
2368 } catch (CannotUndoException ex) {
2369 frame.output(Globals.lang("Nothing to undo")+".");
2371 // After everything, enable/disable the undo/redo actions
2373 //updateUndoState();
2374 //redoAction.updateRedoState();
2375 markChangedOrUnChanged();
2379 class RedoAction extends BaseAction {
2380 public void action() {
2382 String name = undoManager.getRedoPresentationName();
2386 } catch (CannotRedoException ex) {
2387 frame.output(Globals.lang("Nothing to redo")+".");
2389 // After everything, enable/disable the undo/redo actions
2391 //updateRedoState();
2392 //undoAction.updateUndoState();
2393 markChangedOrUnChanged();
2397 // Method pertaining to the ClipboardOwner interface.
2398 public void lostOwnership(Clipboard clipboard, Transferable contents) {}
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);
2409 public String fileMonitorHandle() { return fileMonitorHandle; }
2411 public void fileUpdated() {
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;
2418 // Adding the sidepane component is Swing work, so we must do this in the Swing
2420 Thread t = new Thread() {
2422 // Test: running scan automatically in background
2423 ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this);
2424 scanner.changeScan(BasePanel.this.getFile());
2427 } catch (InterruptedException e) {
2428 e.printStackTrace();
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();
2439 setUpdatedExternally(false);
2440 //System.out.println("No changes found.");
2445 SwingUtilities.invokeLater(t);
2449 public void fileRemoved() {
2450 Util.pr("File '"+getFile().getPath()+"' has been deleted.");
2454 public void cleanUp() {
2455 if (fileMonitorHandle != null)
2456 Globals.fileUpdateMonitor.removeUpdateListener(fileMonitorHandle);
2459 public void setUpdatedExternally(boolean b) {
2460 updatedExternally = b;
2464 * Get an array containing the currently selected entries.
2466 * @return An array containing the selected entries.
2468 public BibtexEntry[] getSelectedEntries() {
2469 return mainTable.getSelectedEntries();
2473 * Get the file where this database was last saved to or loaded from, if any.
2475 * @return The relevant File, or null if none is defined.
2477 public File getFile() {
2478 return metaData.getFile();
2482 * Get a String containing a comma-separated list of the bibtex keys
2483 * of the selected entries.
2485 * @return A comma-separated list of the keys of the selected entries.
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(""))
2499 result.append(citeKey);
2502 result.append(",").append(citeKey);
2505 return result.toString();
2508 public GroupSelector getGroupSelector() {
2509 return frame.groupSelector;