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;
45 import java.awt.event.ActionListener;
47 import java.io.IOException;
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;
55 import java.util.Vector;
57 import javax.swing.filechooser.FileFilter;
58 import javax.swing.tree.TreePath;
59 import javax.swing.undo.CannotRedoException;
60 import javax.swing.undo.CannotUndoException;
62 import net.sf.jabref.collab.ChangeScanner;
63 import net.sf.jabref.collab.FileUpdateListener;
64 import net.sf.jabref.collab.FileUpdatePanel;
65 import net.sf.jabref.export.*;
66 import net.sf.jabref.external.*;
67 import net.sf.jabref.groups.GroupSelector;
68 import net.sf.jabref.groups.GroupTreeNode;
69 import net.sf.jabref.gui.*;
70 import net.sf.jabref.imports.AppendDatabaseAction;
71 import net.sf.jabref.imports.BibtexParser;
72 import net.sf.jabref.journals.AbbreviateAction;
73 import net.sf.jabref.journals.UnabbreviateAction;
74 import net.sf.jabref.labelPattern.LabelPatternUtil;
75 import net.sf.jabref.search.NoSearchMatcher;
76 import net.sf.jabref.search.SearchMatcher;
77 import net.sf.jabref.undo.CountingUndoManager;
78 import net.sf.jabref.undo.NamedCompound;
79 import net.sf.jabref.undo.UndoableChangeType;
80 import net.sf.jabref.undo.UndoableInsertEntry;
81 import net.sf.jabref.undo.UndoableKeyChange;
82 import net.sf.jabref.undo.UndoableRemoveEntry;
83 import net.sf.jabref.wizard.text.gui.TextInputDialog;
84 import ca.odell.glazedlists.FilterList;
85 import ca.odell.glazedlists.event.ListEvent;
86 import ca.odell.glazedlists.event.ListEventListener;
87 import ca.odell.glazedlists.matchers.Matcher;
89 import com.jgoodies.forms.builder.DefaultFormBuilder;
90 import com.jgoodies.forms.layout.FormLayout;
91 import com.jgoodies.uif_lite.component.UIFSplitPane;
95 public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener {
97 public final static int SHOWING_NOTHING=0, SHOWING_PREVIEW=1, SHOWING_EDITOR=2, WILL_SHOW_EDITOR=3;
99 private EntryEditor currentEditor = null;
100 private PreviewPanel currentPreview = null;
104 private MainTableSelectionListener selectionListener = null;
105 private ListEventListener groupsHighlightListener;
106 UIFSplitPane contentPane = new UIFSplitPane();
108 JSplitPane splitPane;
109 //BibtexEntry testE = new BibtexEntry("tt");
110 //boolean previewActive = true;
113 BibtexDatabase database;
114 // The database shown in this panel.
116 // Moving file to MetaData (Morten, 2006.08.29)
117 // private File fileToOpen = null;
119 String fileMonitorHandle = null;
120 boolean saving = false, updatedExternally = false;
121 private String encoding;
123 GridBagLayout gbl = new GridBagLayout();
124 GridBagConstraints con = new GridBagConstraints();
126 HashMap autoCompleters = new HashMap();
127 // Hashtable that holds as keys the names of the fields where
128 // autocomplete is active, and references to the autocompleter objects.
131 public CountingUndoManager undoManager = new CountingUndoManager(this);
132 UndoAction undoAction = new UndoAction();
133 RedoAction redoAction = new RedoAction();
135 //ExampleFileFilter fileFilter;
136 // File filter for .bib files.
138 boolean baseChanged = false, nonUndoableChange = false;
139 // Used to track whether the base has changed since last save.
141 //EntryTableModel tableModel = null;
142 //public EntryTable entryTable = null;
143 public MainTable mainTable = null;
144 public FilterList searchFilterList = null, groupFilterList = null;
146 public RightClickMenu rcm;
148 BibtexEntry showing = null;
149 // To indicate which entry is currently shown.
150 public HashMap entryEditors = new HashMap();
151 // To contain instantiated entry editors. This is to save time
152 // in switching between entries.
154 //HashMap entryTypeForms = new HashMap();
155 // Hashmap to keep track of which entries currently have open
156 // EntryTypeForm dialogs.
158 PreambleEditor preambleEditor = null;
159 // Keeps track of the preamble dialog if it is open.
161 StringDialog stringDialog = null;
162 // Keeps track of the string dialog if it is open.
164 SaveDatabaseAction saveAction;
167 * The group selector component for this database. Instantiated by the
168 * SidePaneManager if necessary, or from this class if merging groups from a
169 * different database.
171 //GroupSelector groupSelector;
173 public boolean sortingBySearchResults = false,
174 coloringBySearchResults = false,
175 hidingNonHits = false,
176 sortingByGroup = false,
177 sortingByCiteSeerResults = false,
178 coloringByGroup = false;
179 //previewEnabled = Globals.prefs.getBoolean("previewEnabled");
180 int lastSearchHits = -1; // The number of hits in the latest search.
181 // Potential use in hiding non-hits completely.
183 // MetaData parses, keeps and writes meta data.
185 HashMap fieldExtras = new HashMap();
186 //## keep track of all keys for duplicate key warning and unique key generation
187 //private HashMap allKeys = new HashMap(); // use a map instead of a set since i need to know how many of each key is inthere
189 private boolean suppressOutput = false;
191 private HashMap<String, Object> actions = new HashMap<String, Object>();
192 private SidePaneManager sidePaneManager;
195 * Create a new BasePanel with an empty database.
196 * @param frame The application window.
198 public BasePanel(JabRefFrame frame) {
199 this.sidePaneManager = Globals.sidePaneManager;
200 database = new BibtexDatabase();
201 metaData = new MetaData();
202 metaData.initializeNewDatabase();
206 encoding = Globals.prefs.get("defaultEncoding");
207 //System.out.println("Default: "+encoding);
210 public BasePanel(JabRefFrame frame, BibtexDatabase db, File file,
211 HashMap meta, String encoding) {
213 this.encoding = encoding;
214 // System.out.println(encoding);
215 //super(JSplitPane.HORIZONTAL_SPLIT, true);
216 this.sidePaneManager = Globals.sidePaneManager;
222 metaData = new MetaData();
223 metaData.initializeNewDatabase();
228 metaData.setFile(file);
230 // Register so we get notifications about outside changes to the file.
233 fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(this,
235 } catch (IOException ex) {
239 public boolean isBaseChanged(){
243 public int getMode() {
247 public BibtexDatabase database() {
251 public MetaData metaData() {
255 public JabRefFrame frame() {
259 public JabRefPreferences prefs() {
260 return Globals.prefs;
263 public String getEncoding() {
267 public void setEncoding(String encoding) {
268 this.encoding = encoding;
271 public void output(String s) {
272 //Util.pr("\""+s+"\""+(SwingUtilities.isEventDispatchThread()));
277 private void setupActions() {
278 saveAction = new SaveDatabaseAction(this);
280 actions.put("undo", undoAction);
281 actions.put("redo", redoAction);
283 // The action for opening an entry editor.
284 actions.put("edit", new BaseAction() {
285 public void action() {
286 selectionListener.editSignalled();
289 if (isShowingEditor()) {
290 new FocusRequester(splitPane.getBottomComponent());
296 //public void run() {
298 // We demand that one and only one row is selected.
299 if (entryTable.getSelectedRowCount() == 1) {
300 clickedOn = entryTable.getSelectedRow();
302 if (clickedOn >= 0) {
303 String id = tableModel.getIdForRow(clickedOn);
304 BibtexEntry be = database.getEntryById(id);
307 if (splitPane.getBottomComponent() != null) {
308 new FocusRequester(splitPane.getBottomComponent());
318 actions.put("test", new BaseAction () {
319 public void action() throws Throwable {
321 ArrayList<BibtexEntry> entries = new ArrayList<BibtexEntry>();
322 BibtexEntry[] sel = getSelectedEntries();
323 for (int i = 0; i < sel.length; i++) {
324 BibtexEntry bibtexEntry = sel[i];
325 entries.add(bibtexEntry);
327 final List<FileListEntry> links =
328 AccessLinksForEntries.getExternalLinksForEntries(entries);
329 for (Iterator<FileListEntry> iterator = links.iterator(); iterator.hasNext();) {
330 FileListEntry entry = iterator.next();
331 System.out.println("Link: "+entry.getLink());
334 final JProgressBar prog = new JProgressBar();
335 prog.setIndeterminate(true);
336 final JDialog diag = new JDialog(frame, false);
337 diag.getContentPane().add(prog, BorderLayout.CENTER);
339 diag.setLocationRelativeTo(frame);
340 diag.setVisible(true);
341 Thread t = new Thread(new Runnable() {
343 AccessLinksForEntries.copyExternalLinksToDirectory(links,
344 new File("/home/alver/tmp"), metaData, prog, false,
345 new ActionListener() {
346 public void actionPerformed(ActionEvent actionEvent) {
354 //CheckBoxFileChooser cb = new CheckBoxFileChooser(new File(""), "Selected only");
355 //cb.showSaveDialog(frame);
357 //ExternalFileTypeEditor efte = new ExternalFileTypeEditor(frame);
358 //efte.setVisible(true);
360 /*NamedCompound ce = Util.upgradePdfPsToFile(database,
361 new String[] {"pdf", "ps"});
362 undoManager.addEdit(ce);
368 // The action for saving a database.
369 actions.put("save", saveAction);
371 actions.put("saveAs", new BaseAction() {
372 public void action() throws Throwable {
377 actions.put("saveSelectedAs", new BaseAction () {
378 public void action() throws Throwable {
380 String chosenFile = Globals.getNewFile(frame, new File(Globals.prefs.get("workingDirectory")), ".bib",
381 JFileChooser.SAVE_DIALOG, false);
382 if (chosenFile != null) {
383 File expFile = new File(chosenFile);
384 if (!expFile.exists() ||
385 (JOptionPane.showConfirmDialog
386 (frame, "'"+expFile.getName()+"' "+
387 Globals.lang("exists. Overwrite file?"),
388 Globals.lang("Save database"), JOptionPane.OK_CANCEL_OPTION)
389 == JOptionPane.OK_OPTION)) {
391 saveDatabase(expFile, true, Globals.prefs.get("defaultEncoding"));
392 //runCommand("save");
393 frame.getFileHistory().newFile(expFile.getPath());
394 frame.output(Globals.lang("Saved selected to")+" '"
395 +expFile.getPath()+"'.");
401 // The action for copying selected entries.
402 actions.put("copy", new BaseAction() {
403 public void action() {
404 BibtexEntry[] bes = mainTable.getSelectedEntries();
406 if ((bes != null) && (bes.length > 0)) {
407 TransferableBibtexEntry trbe
408 = new TransferableBibtexEntry(bes);
409 // ! look at ClipBoardManager
410 Toolkit.getDefaultToolkit().getSystemClipboard()
411 .setContents(trbe, BasePanel.this);
412 output(Globals.lang("Copied")+" "+(bes.length>1 ? bes.length+" "
413 +Globals.lang("entries")
414 : "1 "+Globals.lang("entry")+"."));
416 // The user maybe selected a single cell.
417 int[] rows = mainTable.getSelectedRows(),
418 cols = mainTable.getSelectedColumns();
419 if ((cols.length == 1) && (rows.length == 1)) {
420 // Copy single value.
421 Object o = mainTable.getValueAt(rows[0], cols[0]);
423 StringSelection ss = new StringSelection(o.toString());
424 Toolkit.getDefaultToolkit().getSystemClipboard()
425 .setContents(ss, BasePanel.this);
427 output(Globals.lang("Copied cell contents")+".");
434 actions.put("cut", new BaseAction() {
435 public void action() throws Throwable {
437 BibtexEntry[] bes = mainTable.getSelectedEntries();
438 //int row0 = mainTable.getSelectedRow();
439 if ((bes != null) && (bes.length > 0)) {
440 // Create a CompoundEdit to make the action undoable.
441 NamedCompound ce = new NamedCompound
442 (Globals.lang(bes.length > 1 ? "cut entries" : "cut entry"));
443 // Loop through the array of entries, and delete them.
444 for (int i=0; i<bes.length; i++) {
445 database.removeEntry(bes[i].getId());
446 ensureNotShowing(bes[i]);
447 ce.addEdit(new UndoableRemoveEntry
448 (database, bes[i], BasePanel.this));
450 //entryTable.clearSelection();
451 frame.output(Globals.lang("Cut_pr")+" "+
452 (bes.length>1 ? bes.length
453 +" "+ Globals.lang("entries")
454 : Globals.lang("entry"))+".");
456 undoManager.addEdit(ce);
459 // Reselect the entry in the first prev. selected position:
460 /*if (row0 >= entryTable.getRowCount())
461 row0 = entryTable.getRowCount()-1;
463 entryTable.addRowSelectionInterval(row0, row0);*/
468 actions.put("delete", new BaseAction() {
469 public void action() {
470 boolean cancelled = false;
471 BibtexEntry[] bes = mainTable.getSelectedEntries();
472 int row0 = mainTable.getSelectedRow();
473 if ((bes != null) && (bes.length > 0)) {
475 boolean goOn = showDeleteConfirmationDialog(bes.length);
480 // Create a CompoundEdit to make the action undoable.
481 NamedCompound ce = new NamedCompound
482 (Globals.lang(bes.length > 1 ? "delete entries" : "delete entry"));
483 // Loop through the array of entries, and delete them.
484 for (int i = 0; i < bes.length; i++) {
485 database.removeEntry(bes[i].getId());
486 ensureNotShowing(bes[i]);
487 ce.addEdit(new UndoableRemoveEntry(database, bes[i], BasePanel.this));
490 frame.output(Globals.lang("Deleted") + " " +
491 (bes.length > 1 ? bes.length
492 + " " + Globals.lang("entries")
493 : Globals.lang("entry")) + ".");
495 undoManager.addEdit(ce);
496 //entryTable.clearSelection();
500 // Reselect the entry in the first prev. selected position:
501 /*if (row0 >= entryTable.getRowCount())
502 row0 = entryTable.getRowCount()-1;
504 final int toSel = row0;
506 SwingUtilities.invokeLater(new Runnable() {
508 entryTable.addRowSelectionInterval(toSel, toSel);
509 //entryTable.ensureVisible(toSel);
519 // The action for pasting entries or cell contents.
520 // Edited by Seb Wills <saw27@mrao.cam.ac.uk> on 14-Apr-04:
521 // - more robust detection of available content flavors (doesn't only look at first one offered)
522 // - support for parsing string-flavor clipboard contents which are bibtex entries.
523 // This allows you to (a) paste entire bibtex entries from a text editor, web browser, etc
524 // (b) copy and paste entries between multiple instances of JabRef (since
525 // only the text representation seems to get as far as the X clipboard, at least on my system)
526 actions.put("paste", new BaseAction() {
527 public void action() {
528 // Get clipboard contents, and see if TransferableBibtexEntry is among the content flavors offered
529 Transferable content = Toolkit.getDefaultToolkit()
530 .getSystemClipboard().getContents(null);
531 if (content != null) {
532 BibtexEntry[] bes = null;
533 if (content.isDataFlavorSupported(TransferableBibtexEntry.entryFlavor)) {
534 // We have determined that the clipboard data is a set of entries.
536 bes = (BibtexEntry[])(content.getTransferData(TransferableBibtexEntry.entryFlavor));
538 } catch (UnsupportedFlavorException ex) {
539 ex.printStackTrace();
540 } catch (IOException ex) {
541 ex.printStackTrace();
543 } else if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
544 // We have determined that no TransferableBibtexEntry is available, but
545 // there is a string, which we will handle according to context:
546 int[] rows = mainTable.getSelectedRows();
547 //cols = entryTable.getSelectedColumns();
548 //Util.pr(rows.length+" x "+cols.length);
549 /*if ((cols != null) && (cols.length == 1) && (cols[0] != 0)
550 && (rows != null) && (rows.length == 1)) {
551 // A single cell is highlighted, so paste the string straight into it without parsing
553 tableModel.setValueAt((String)(content.getTransferData(DataFlavor.stringFlavor)), rows[0], cols[0]);
556 output("Pasted cell contents");
557 } catch (UnsupportedFlavorException ex) {
558 ex.printStackTrace();
559 } catch (IOException ex) {
560 ex.printStackTrace();
561 } catch (IllegalArgumentException ex) {
562 output("Can't paste.");
565 // no single cell is selected, so try parsing the clipboard contents as bibtex entries instead
567 BibtexParser bp = new BibtexParser
568 (new java.io.StringReader( (String) (content.getTransferData(
569 DataFlavor.stringFlavor))));
570 BibtexDatabase db = bp.parse().getDatabase();
571 Util.pr("Parsed " + db.getEntryCount() + " entries from clipboard text");
572 if(db.getEntryCount()>0) {
573 Set keySet = db.getKeySet();
574 if (keySet != null) {
575 // Copy references to the entries into a BibtexEntry array.
576 // Could import directly from db, but going via bes allows re-use
577 // of the same pasting code as used for TransferableBibtexEntries
578 bes = new BibtexEntry[db.getEntryCount()];
579 Iterator it = keySet.iterator();
580 for (int i=0; it.hasNext();i++) {
581 bes[i]=db.getEntryById((String) (it.next()));
585 String cont = (String)(content.getTransferData(DataFlavor.stringFlavor));
586 Util.pr("----------------\n"+cont+"\n---------------------");
587 TextAnalyzer ta = new TextAnalyzer(cont);
588 output(Globals.lang("Unable to parse clipboard text as Bibtex entries."));
590 } catch (UnsupportedFlavorException ex) {
591 ex.printStackTrace();
592 } catch (Throwable ex) {
593 ex.printStackTrace();
598 // finally we paste in the entries (if any), which either came from TransferableBibtexEntries
599 // or were parsed from a string
600 if ((bes != null) && (bes.length > 0)) {
602 NamedCompound ce = new NamedCompound
603 (Globals.lang(bes.length > 1 ? "paste entries" : "paste entry"));
604 for (int i=0; i<bes.length; i++) {
606 BibtexEntry be = (BibtexEntry)(bes[i].clone());
607 Util.setAutomaticFields(be,
608 Globals.prefs.getBoolean("overwriteOwner"),
609 Globals.prefs.getBoolean("overwriteTimeStamp"));
611 // We have to clone the
612 // entries, since the pasted
613 // entries must exist
614 // independently of the copied
616 be.setId(Util.createNeutralId());
617 database.insertEntry(be);
618 ce.addEdit(new UndoableInsertEntry
619 (database, be, BasePanel.this));
620 } catch (KeyCollisionException ex) {
621 Util.pr("KeyCollisionException... this shouldn't happen.");
625 undoManager.addEdit(ce);
626 //entryTable.clearSelection();
627 //entryTable.revalidate();
628 output(Globals.lang("Pasted")+" "+
629 (bes.length>1 ? bes.length+" "+
630 Globals.lang("entries") : "1 "+Globals.lang("entry"))
640 actions.put("selectAll", new BaseAction() {
641 public void action() {
642 mainTable.selectAll();
646 // The action for opening the preamble editor
647 actions.put("editPreamble", new BaseAction() {
648 public void action() {
649 if (preambleEditor == null) {
650 PreambleEditor form = new PreambleEditor
651 (frame, BasePanel.this, database, Globals.prefs);
652 Util.placeDialog(form, frame);
653 form.setVisible(true);
654 preambleEditor = form;
656 preambleEditor.setVisible(true);
662 // The action for opening the string editor
663 actions.put("editStrings", new BaseAction() {
664 public void action() {
665 if (stringDialog == null) {
666 StringDialog form = new StringDialog
667 (frame, BasePanel.this, database, Globals.prefs);
668 Util.placeDialog(form, frame);
669 form.setVisible(true);
672 stringDialog.setVisible(true);
678 // The action for toggling the groups interface
679 actions.put("toggleGroups", new BaseAction() {
680 public void action() {
681 sidePaneManager.toggle("groups");
682 frame.groupToggle.setSelected(sidePaneManager.isComponentVisible("groups"));
687 // The action for auto-generating keys.
688 actions.put("makeKey", new AbstractWorker() {
692 boolean cancelled = false;
694 // Run first, in EDT:
697 entries = new ArrayList(Arrays.asList(getSelectedEntries()));
698 //rows = entryTable.getSelectedRows() ;
699 numSelected = entries.size();
701 if (entries.size() == 0) { // None selected. Inform the user to select entries first.
702 JOptionPane.showMessageDialog(frame, Globals.lang("First select the entries you want keys to be generated for."),
703 Globals.lang("Autogenerate BibTeX key"), JOptionPane.INFORMATION_MESSAGE);
707 output(Globals.lang("Generating BibTeX key for")+" "+
708 numSelected+" "+(numSelected>1 ? Globals.lang("entries")
709 : Globals.lang("entry"))+"...");
712 // Run second, on a different thread:
714 BibtexEntry bes = null ;
715 NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
718 boolean hasShownWarning = false;
719 // First check if any entries have keys set already. If so, possibly remove
720 // them from consideration, or warn about overwriting keys.
721 loop: for (Iterator i=entries.iterator(); i.hasNext();) {
722 bes = (BibtexEntry)i.next();
723 if (bes.getField(BibtexFields.KEY_FIELD) != null) {
724 if (Globals.prefs.getBoolean("avoidOverwritingKey"))
725 // Rmove the entry, because its key is already set:
727 else if (Globals.prefs.getBoolean("warnBeforeOverwritingKey")) {
728 // Ask if the user wants to cancel the operation:
729 CheckBoxMessage cbm = new CheckBoxMessage(Globals.lang("One or more keys will be overwritten. Continue?"),
730 Globals.lang("Disable this confirmation dialog"), false);
731 int answer = JOptionPane.showConfirmDialog(frame, cbm, Globals.lang("Overwrite keys"),
732 JOptionPane.YES_NO_OPTION);
733 if (cbm.isSelected())
734 Globals.prefs.putBoolean("warnBeforeOverwritingKey", false);
735 if (answer == JOptionPane.NO_OPTION) {
736 // Ok, break off the operation.
740 // No need to check more entries, because the user has already confirmed
741 // that it's ok to overwrite keys:
747 HashMap oldvals = new HashMap();
748 // Iterate again, removing already set keys. This is skipped if overwriting
749 // is disabled, since all entries with keys set will have been removed.
750 if (!Globals.prefs.getBoolean("avoidOverwritingKey")) for (Iterator i=entries.iterator(); i.hasNext();) {
751 bes = (BibtexEntry)i.next();
752 // Store the old value:
753 oldvals.put(bes, bes.getField(BibtexFields.KEY_FIELD));
754 database.setCiteKeyForEntry(bes.getId(), null);
757 // Finally, set the new keys:
758 for (Iterator i=entries.iterator(); i.hasNext();) {
759 bes = (BibtexEntry)i.next();
760 bes = LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, bes);
761 ce.addEdit(new UndoableKeyChange
762 (database, bes.getId(), (String)oldvals.get(bes),
763 (String)bes.getField(BibtexFields.KEY_FIELD)));
766 undoManager.addEdit(ce);
769 // Run third, on EDT:
770 public void update() {
776 numSelected = entries.size();
777 output(Globals.lang("Generated BibTeX key for")+" "+
778 numSelected+" "+(numSelected!=1 ? Globals.lang("entries")
779 : Globals.lang("entry")));
784 actions.put("search", new BaseAction() {
785 public void action() {
786 //sidePaneManager.togglePanel("search");
787 sidePaneManager.show("search");
788 //boolean on = sidePaneManager.isPanelVisible("search");
789 frame.searchToggle.setSelected(true);
791 frame.searchManager.startSearch();
795 actions.put("toggleSearch", new BaseAction() {
796 public void action() {
797 //sidePaneManager.togglePanel("search");
798 sidePaneManager.toggle("search");
799 boolean on = sidePaneManager.isComponentVisible("search");
800 frame.searchToggle.setSelected(on);
802 frame.searchManager.startSearch();
806 actions.put("incSearch", new BaseAction() {
807 public void action() {
808 sidePaneManager.show("search");
809 frame.searchToggle.setSelected(true);
810 frame.searchManager.startIncrementalSearch();
814 // The action for copying the selected entry's key.
815 actions.put("copyKey", new BaseAction() {
816 public void action() {
817 BibtexEntry[] bes = mainTable.getSelectedEntries();
818 if ((bes != null) && (bes.length > 0)) {
820 //String[] keys = new String[bes.length];
821 Vector keys = new Vector();
822 // Collect all non-null keys.
823 for (int i=0; i<bes.length; i++)
824 if (bes[i].getField(BibtexFields.KEY_FIELD) != null)
825 keys.add(bes[i].getField(BibtexFields.KEY_FIELD));
826 if (keys.size() == 0) {
827 output("None of the selected entries have BibTeX keys.");
830 StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
831 for (int i=1; i<keys.size(); i++) {
833 sb.append((String)keys.elementAt(i));
836 StringSelection ss = new StringSelection(sb.toString());
837 Toolkit.getDefaultToolkit().getSystemClipboard()
838 .setContents(ss, BasePanel.this);
840 if (keys.size() == bes.length)
841 // All entries had keys.
842 output(Globals.lang((bes.length > 1) ? "Copied keys"
843 : "Copied key")+".");
845 output(Globals.lang("Warning")+": "+(bes.length-keys.size())
846 +" "+Globals.lang("out of")+" "+bes.length+" "+
847 Globals.lang("entries have undefined BibTeX key")+".");
852 // The action for copying a cite for the selected entry.
853 actions.put("copyCiteKey", new BaseAction() {
854 public void action() {
855 BibtexEntry[] bes = mainTable.getSelectedEntries();
856 if ((bes != null) && (bes.length > 0)) {
858 //String[] keys = new String[bes.length];
859 Vector keys = new Vector();
860 // Collect all non-null keys.
861 for (int i=0; i<bes.length; i++)
862 if (bes[i].getField(BibtexFields.KEY_FIELD) != null)
863 keys.add(bes[i].getField(BibtexFields.KEY_FIELD));
864 if (keys.size() == 0) {
865 output("None of the selected entries have BibTeX keys.");
868 StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
869 for (int i=1; i<keys.size(); i++) {
871 sb.append((String)keys.elementAt(i));
874 StringSelection ss = new StringSelection
875 ("\\cite{"+sb.toString()+"}");
876 Toolkit.getDefaultToolkit().getSystemClipboard()
877 .setContents(ss, BasePanel.this);
879 if (keys.size() == bes.length)
880 // All entries had keys.
881 output(Globals.lang((bes.length > 1) ? "Copied keys"
882 : "Copied key")+".");
884 output(Globals.lang("Warning")+": "+(bes.length-keys.size())
885 +" "+Globals.lang("out of")+" "+bes.length+" "+
886 Globals.lang("entries have undefined BibTeX key")+".");
891 actions.put("mergeDatabase", new AppendDatabaseAction(frame, this));
894 actions.put("openFile", new BaseAction() {
895 public void action() {
898 BibtexEntry[] bes = mainTable.getSelectedEntries();
900 if ((bes != null) && (bes.length == 1)) {
901 Object link = bes[0].getField("ps");
902 if (bes[0].getField("pdf") != null) {
903 link = bes[0].getField("pdf");
906 String filepath = null;
908 filepath = link.toString();
911 // see if we can fall back to a filename based on the bibtex key
913 Object key = bes[0].getField(BibtexFields.KEY_FIELD);
915 basefile = key.toString();
916 final String[] types = new String[]{"pdf", "ps"};
917 final String sep = System.getProperty("file.separator");
918 for (int i = 0; i < types.length; i++) {
919 String dir = Globals.prefs.get(types[i] + "Directory");
921 if (dir.endsWith(sep)) {
922 dir = dir.substring(0, dir.length() - sep.length());
926 String found = Util.findPdf(basefile, types[i], dir, new OpenFileFilter("." + types[i]));
928 filepath = dir + sep + found;
936 if (filepath != null) {
937 //output(Globals.lang("Calling external viewer..."));
939 Util.openExternalViewer(metaData(), filepath, field);
940 output(Globals.lang("External viewer called") + ".");
942 catch (IOException ex) {
943 output(Globals.lang("Error") + ": " + ex.getMessage());
947 "No pdf or ps defined, and no file matching Bibtex key found") +
950 output(Globals.lang("No entries or multiple entries selected."));
956 actions.put("openExternalFile", new BaseAction() {
957 public void action() {
960 BibtexEntry[] bes = mainTable.getSelectedEntries();
961 String field = GUIGlobals.FILE_FIELD;
962 if ((bes != null) && (bes.length == 1)) {
963 Object link = bes[0].getField(field);
965 runCommand("openFile"); // Fall back on PDF/PS fields???
968 FileListTableModel tableModel = new FileListTableModel();
969 tableModel.setContent((String)link);
970 if (tableModel.getRowCount() == 0) {
971 runCommand("openFile"); // Fall back on PDF/PS fields???
974 FileListEntry flEntry = tableModel.getEntry(0);
975 ExternalFileMenuItem item = new ExternalFileMenuItem
976 (frame(), bes[0], "",
977 flEntry.getLink(), flEntry.getType().getIcon(),
978 metaData(), flEntry.getType());
979 item.actionPerformed(null);
981 output(Globals.lang("No entries or multiple entries selected."));
988 actions.put("openUrl", new BaseAction() {
989 public void action() {
990 BibtexEntry[] bes = mainTable.getSelectedEntries();
991 String field = "doi";
992 if ((bes != null) && (bes.length == 1)) {
993 Object link = bes[0].getField("doi");
994 if (bes[0].getField("url") != null) {
995 link = bes[0].getField("url");
999 //output(Globals.lang("Calling external viewer..."));
1001 Util.openExternalViewer(metaData(), link.toString(), field);
1002 output(Globals.lang("External viewer called")+".");
1003 } catch (IOException ex) {
1004 output(Globals.lang("Error") + ": " + ex.getMessage());
1008 output(Globals.lang("No url defined")+".");
1010 output(Globals.lang("No entries or multiple entries selected."));
1014 actions.put("replaceAll", new BaseAction() {
1015 public void action() {
1016 ReplaceStringDialog rsd = new ReplaceStringDialog(frame);
1017 rsd.setVisible(true);
1018 if (!rsd.okPressed())
1021 NamedCompound ce = new NamedCompound(Globals.lang("Replace string"));
1022 if (!rsd.selOnly()) {
1023 for (Iterator i=database.getKeySet().iterator();
1025 counter += rsd.replace(database.getEntryById((String)i.next()), ce);
1027 BibtexEntry[] bes = mainTable.getSelectedEntries();
1028 for (int i=0; i<bes.length; i++)
1029 counter += rsd.replace(bes[i], ce);
1032 output(Globals.lang("Replaced")+" "+counter+" "+
1033 Globals.lang(counter==1?"occurence":"occurences")+".");
1036 undoManager.addEdit(ce);
1042 actions.put("dupliCheck", new BaseAction() {
1043 public void action() {
1044 DuplicateSearch ds = new DuplicateSearch(BasePanel.this);
1049 /*actions.put("strictDupliCheck", new BaseAction() {
1050 public void action() {
1051 StrictDuplicateSearch ds = new StrictDuplicateSearch(BasePanel.this);
1056 actions.put("plainTextImport", new BaseAction() {
1057 public void action()
1059 // get Type of new entry
1060 EntryTypeDialog etd = new EntryTypeDialog(frame);
1061 Util.placeDialog(etd, BasePanel.this);
1062 etd.setVisible(true);
1063 BibtexEntryType tp = etd.getChoice();
1067 String id = Util.createNeutralId();
1068 BibtexEntry bibEntry = new BibtexEntry(id, tp) ;
1069 TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1072 Util.placeDialog(tidialog, BasePanel.this);
1073 tidialog.setVisible(true);
1075 if (tidialog.okPressed())
1077 Util.setAutomaticFields(Arrays.asList(new BibtexEntry[] {bibEntry}),
1079 insertEntry(bibEntry) ;
1084 // The action starts the "import from plain text" dialog
1085 /*actions.put("importPlainText", new BaseAction() {
1086 public void action()
1088 BibtexEntry bibEntry = null ;
1089 // try to get the first marked entry
1090 BibtexEntry[] bes = entryTable.getSelectedEntries();
1091 if ((bes != null) && (bes.length > 0))
1094 if (bibEntry != null)
1096 // Create an UndoableInsertEntry object.
1097 undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1099 TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1102 Util.placeDialog(tidialog, BasePanel.this);
1103 tidialog.setVisible(true);
1105 if (tidialog.okPressed())
1107 output(Globals.lang("changed ")+" '"
1108 +bibEntry.getType().getName().toLowerCase()+"' "
1109 +Globals.lang("entry")+".");
1111 int row = tableModel.getNumberFromName(bibEntry.getId());
1113 entryTable.clearSelection();
1114 entryTable.scrollTo(row);
1115 markBaseChanged(); // The database just changed.
1116 if (Globals.prefs.getBoolean("autoOpenForm"))
1118 showEntry(bibEntry);
1125 actions.put("markEntries", new AbstractWorker() {
1126 private int besLength = -1;
1129 NamedCompound ce = new NamedCompound(Globals.lang("Mark entries"));
1130 BibtexEntry[] bes = mainTable.getSelectedEntries();
1131 besLength = bes.length;
1134 for (int i=0; i<bes.length; i++) {
1135 Util.markEntry(bes[i], ce);
1138 undoManager.addEdit(ce);
1141 public void update() {
1143 output(Globals.lang("Marked selected")+" "+Globals.lang(besLength>0?"entry":"entries"));
1148 actions.put("unmarkEntries", new BaseAction() {
1149 public void action() {
1151 NamedCompound ce = new NamedCompound(Globals.lang("Unmark entries"));
1152 BibtexEntry[] bes = mainTable.getSelectedEntries();
1155 for (int i=0; i<bes.length; i++) {
1156 Util.unmarkEntry(bes[i], database, ce);
1159 undoManager.addEdit(ce);
1161 output(Globals.lang("Unmarked selected")+" "+Globals.lang(bes.length>0?"entry":"entries"));
1162 } catch (Throwable ex) { ex.printStackTrace(); }
1166 actions.put("unmarkAll", new BaseAction() {
1167 public void action() {
1168 NamedCompound ce = new NamedCompound(Globals.lang("Unmark all"));
1169 Set keySet = database.getKeySet();
1170 for (Iterator i = keySet.iterator(); i.hasNext(); ) {
1171 BibtexEntry be = database.getEntryById( (String) i.next());
1172 Util.unmarkEntry(be, database, ce);
1176 undoManager.addEdit(ce);
1181 actions.put("togglePreview", new BaseAction() {
1182 public void action() {
1183 boolean enabled = !Globals.prefs.getBoolean("previewEnabled");
1184 Globals.prefs.putBoolean("previewEnabled", enabled);
1185 frame.setPreviewActive(enabled);
1186 frame.previewToggle.setSelected(enabled);
1190 actions.put("toggleHighlightGroupsMatchingAny", new BaseAction() {
1191 public void action() {
1192 boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAny");
1193 Globals.prefs.putBoolean("highlightGroupsMatchingAny", enabled);
1194 frame.highlightAny.setSelected(enabled);
1196 frame.highlightAll.setSelected(false);
1197 Globals.prefs.putBoolean("highlightGroupsMatchingAll", false);
1199 // ping the listener so it updates:
1200 groupsHighlightListener.listChanged(null);
1204 actions.put("toggleHighlightGroupsMatchingAll", new BaseAction() {
1205 public void action() {
1206 boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAll");
1207 Globals.prefs.putBoolean("highlightGroupsMatchingAll", enabled);
1208 frame.highlightAll.setSelected(enabled);
1210 frame.highlightAny.setSelected(false);
1211 Globals.prefs.putBoolean("highlightGroupsMatchingAny", false);
1213 // ping the listener so it updates:
1214 groupsHighlightListener.listChanged(null);
1218 actions.put("switchPreview", new BaseAction() {
1219 public void action() {
1220 selectionListener.switchPreview();
1224 actions.put("manageSelectors", new BaseAction() {
1225 public void action() {
1226 ContentSelectorDialog2 csd = new ContentSelectorDialog2
1227 (frame, frame, BasePanel.this, false, metaData, null);
1228 Util.placeDialog(csd, frame);
1229 csd.setVisible(true);
1234 actions.put("exportToClipboard", new ExportToClipboardAction(frame, database()));
1236 actions.put("writeXMP", new WriteXMPAction(this));
1238 actions.put("abbreviateIso", new AbbreviateAction(this, true));
1239 actions.put("abbreviateMedline", new AbbreviateAction(this, false));
1240 actions.put("unabbreviate", new UnabbreviateAction(this));
1241 actions.put("autoSetPdf", new AutoSetExternalFileForEntries(this, "pdf"));
1242 actions.put("autoSetPs", new AutoSetExternalFileForEntries(this, "ps"));
1243 actions.put("autoSetFile", new SynchronizeFileField(this));
1244 actions.put("upgradeLinks", new UpgradeExternalLinks(this));
1249 * This method is called from JabRefFrame is a database specific
1250 * action is requested by the user. Runs the command if it is
1251 * defined, or prints an error message to the standard error
1254 * @param _command The name of the command to run.
1256 public void runCommand(String _command) {
1257 final String command = _command;
1259 // public void run() {
1260 if (actions.get(command) == null)
1261 Util.pr("No action defined for'" + command + "'");
1263 Object o = actions.get(command);
1265 if (o instanceof BaseAction)
1266 ((BaseAction)o).action();
1268 // This part uses Spin's features:
1269 Worker wrk = ((AbstractWorker)o).getWorker();
1270 // The Worker returned by getWorker() has been wrapped
1271 // by Spin.off(), which makes its methods be run in
1272 // a different thread from the EDT.
1273 CallBack clb = ((AbstractWorker)o).getCallBack();
1275 ((AbstractWorker)o).init(); // This method runs in this same thread, the EDT.
1276 // Useful for initial GUI actions, like printing a message.
1278 // The CallBack returned by getCallBack() has been wrapped
1279 // by Spin.over(), which makes its methods be run on
1281 wrk.run(); // Runs the potentially time-consuming action
1282 // without freezing the GUI. The magic is that THIS line
1283 // of execution will not continue until run() is finished.
1284 clb.update(); // Runs the update() method on the EDT.
1286 } catch (Throwable ex) {
1287 // If the action has blocked the JabRefFrame before crashing, we need to unblock it.
1288 // The call to unblock will simply hide the glasspane, so there is no harm in calling
1289 // it even if the frame hasn't been blocked.
1291 ex.printStackTrace();
1298 private boolean saveDatabase(File file, boolean selectedOnly, String encoding) throws SaveException {
1299 SaveSession session;
1303 session = FileActions.saveDatabase(database, metaData, file,
1304 Globals.prefs, false, false, encoding);
1306 session = FileActions.savePartOfDatabase(database, metaData, file,
1307 Globals.prefs, mainTable.getSelectedEntries(), encoding);
1309 } catch (UnsupportedCharsetException ex2) {
1310 JOptionPane.showMessageDialog(frame, Globals.lang("Could not save file. "
1311 +"Character encoding '%0' is not supported.", encoding),
1312 Globals.lang("Save database"), JOptionPane.ERROR_MESSAGE);
1313 throw new SaveException("rt");
1314 } catch (SaveException ex) {
1315 if (ex.specificEntry()) {
1316 // Error occured during processing of
1317 // be. Highlight it:
1318 int row = mainTable.findEntry(ex.getEntry()),
1319 topShow = Math.max(0, row-3);
1320 mainTable.setRowSelectionInterval(row, row);
1321 mainTable.scrollTo(topShow);
1322 showEntry(ex.getEntry());
1324 else ex.printStackTrace();
1326 JOptionPane.showMessageDialog
1327 (frame, Globals.lang("Could not save file")
1328 +".\n"+ex.getMessage(),
1329 Globals.lang("Save database"),
1330 JOptionPane.ERROR_MESSAGE);
1331 throw new SaveException("rt");
1337 boolean commit = true;
1338 if (!session.getWriter().couldEncodeAll()) {
1339 DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("left:pref, 4dlu, fill:pref", ""));
1340 JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters());
1341 ta.setEditable(false);
1342 builder.append(Globals.lang("The chosen encoding '%0' could not encode the following characters: ",
1343 session.getEncoding()));
1345 builder.append(Globals.lang("What do you want to do?"));
1346 String tryDiff = Globals.lang("Try different encoding");
1347 int answer = JOptionPane.showOptionDialog(frame, builder.getPanel(), Globals.lang("Save database"),
1348 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
1349 new String[] {Globals.lang("Save"), tryDiff, Globals.lang("Cancel")}, tryDiff);
1351 if (answer == JOptionPane.NO_OPTION) {
1352 // The user wants to use another encoding.
1353 Object choice = JOptionPane.showInputDialog(frame, Globals.lang("Select encoding"), Globals.lang("Save database"),
1354 JOptionPane.QUESTION_MESSAGE, null, Globals.ENCODINGS, encoding);
1355 if (choice != null) {
1356 String newEncoding = (String)choice;
1357 return saveDatabase(file, selectedOnly, newEncoding);
1360 } else if (answer == JOptionPane.CANCEL_OPTION)
1369 this.encoding = encoding; // Make sure to remember which encoding we used.
1373 } catch (IOException e) {
1374 e.printStackTrace();
1382 * This method is called from JabRefFrame when the user wants to
1383 * create a new entry. If the argument is null, the user is
1384 * prompted for an entry type.
1386 * @param type The type of the entry to create.
1387 * @return The newly created BibtexEntry or null the operation was canceled by the user.
1389 public BibtexEntry newEntry(BibtexEntryType type) {
1391 // Find out what type is wanted.
1392 EntryTypeDialog etd = new EntryTypeDialog(frame);
1393 // We want to center the dialog, to make it look nicer.
1394 Util.placeDialog(etd, frame);
1395 etd.setVisible(true);
1396 type = etd.getChoice();
1398 if (type != null) { // Only if the dialog was not cancelled.
1399 String id = Util.createNeutralId();
1400 final BibtexEntry be = new BibtexEntry(id, type);
1402 database.insertEntry(be);
1404 // Set owner/timestamp if options are enabled:
1405 ArrayList list = new ArrayList();
1407 Util.setAutomaticFields(list, true, true);
1409 // Create an UndoableInsertEntry object.
1410 undoManager.addEdit(new UndoableInsertEntry(database, be, BasePanel.this));
1411 output(Globals.lang("Added new")+" '"+type.getName().toLowerCase()+"' "
1412 +Globals.lang("entry")+".");
1413 final int row = mainTable.findEntry(be);
1415 // We are going to select the new entry. Before that, make sure that we are in
1416 // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR
1417 // mode which makes sure the selection will trigger display of the entry editor
1418 // and adjustment of the splitter.
1419 if (mode != SHOWING_EDITOR) {
1420 mode = WILL_SHOW_EDITOR;
1423 highlightEntry(be); // Selects the entry. The selection listener will open the editor.
1425 markBaseChanged(); // The database just changed.
1426 new FocusRequester(getEntryEditor(be));
1428 } catch (KeyCollisionException ex) {
1429 Util.pr(ex.getMessage());
1438 * This method is called from JabRefFrame when the user wants to
1439 * create a new entry.
1440 * @param bibEntry The new entry.
1442 public void insertEntry(BibtexEntry bibEntry)
1444 if (bibEntry != null)
1448 database.insertEntry(bibEntry) ;
1449 if (Globals.prefs.getBoolean("useOwner"))
1450 // Set owner field to default value
1451 bibEntry.setField(BibtexFields.OWNER, Globals.prefs.get("defaultOwner") );
1452 // Create an UndoableInsertEntry object.
1453 undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1454 output(Globals.lang("Added new")+" '"
1455 +bibEntry.getType().getName().toLowerCase()+"' "
1456 +Globals.lang("entry")+".");
1457 int row = mainTable.findEntry(bibEntry);
1459 mainTable.clearSelection();
1460 mainTable.scrollTo(row);
1461 markBaseChanged(); // The database just changed.
1462 if (Globals.prefs.getBoolean("autoOpenForm"))
1464 showEntry(bibEntry);
1466 } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); }
1470 public void createMainTable() {
1471 //Comparator comp = new FieldComparator("author");
1473 GlazedEntrySorter eventList = new GlazedEntrySorter(database.getEntryMap(), null);
1474 // Must initialize sort columns somehow:
1476 database.addDatabaseChangeListener(eventList);
1477 groupFilterList = new FilterList(eventList.getTheList(), NoSearchMatcher.INSTANCE);
1478 searchFilterList = new FilterList(groupFilterList, NoSearchMatcher.INSTANCE);
1479 //final SortedList sortedList = new SortedList(searchFilterList, null);
1480 MainTableFormat tableFormat = new MainTableFormat(this);
1481 tableFormat.updateTableFormat();
1482 //EventTableModel tableModel = new EventTableModel(sortedList, tableFormat);
1483 mainTable = new MainTable(/*tableModel, */tableFormat, searchFilterList, frame, this);
1485 selectionListener = new MainTableSelectionListener(this, mainTable);
1486 mainTable.updateFont();
1487 mainTable.addSelectionListener(selectionListener);
1488 mainTable.addMouseListener(selectionListener);
1489 mainTable.addKeyListener(selectionListener);
1490 mainTable.addFocusListener(selectionListener);
1492 // Add the listener that will take care of highlighting groups as the selection changes:
1493 groupsHighlightListener = new ListEventListener() {
1494 public void listChanged(ListEvent listEvent) {
1495 if (Globals.prefs.getBoolean("highlightGroupsMatchingAny"))
1496 getGroupSelector().showMatchingGroups(
1497 mainTable.getSelectedEntries(), false);
1498 else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll"))
1499 getGroupSelector().showMatchingGroups(
1500 mainTable.getSelectedEntries(), true);
1501 else // no highlight
1502 getGroupSelector().showMatchingGroups(null, true);
1505 mainTable.addSelectionListener(groupsHighlightListener);
1507 mainTable.getActionMap().put("cut", new AbstractAction() {
1508 public void actionPerformed(ActionEvent e) {
1509 try { runCommand("cut");
1510 } catch (Throwable ex) {
1511 ex.printStackTrace();
1515 mainTable.getActionMap().put("copy", new AbstractAction() {
1516 public void actionPerformed(ActionEvent e) {
1517 try { runCommand("copy");
1518 } catch (Throwable ex) {
1519 ex.printStackTrace();
1523 mainTable.getActionMap().put("paste", new AbstractAction() {
1524 public void actionPerformed(ActionEvent e) {
1525 try { runCommand("paste");
1526 } catch (Throwable ex) {
1527 ex.printStackTrace();
1532 mainTable.addKeyListener(new KeyAdapter() {
1534 public void keyPressed(KeyEvent e) {
1535 final int keyCode = e.getKeyCode();
1536 final TreePath path = frame.groupSelector.getSelectionPath();
1537 final GroupTreeNode node = path == null ? null : (GroupTreeNode) path.getLastPathComponent();
1539 if (e.isControlDown()) {
1541 // The up/down/left/rightkeystrokes are displayed in the
1542 // GroupSelector's popup menu, so if they are to be changed,
1543 // edit GroupSelector.java accordingly!
1544 case KeyEvent.VK_UP:
1547 frame.groupSelector.moveNodeUp(node, true);
1549 case KeyEvent.VK_DOWN:
1552 frame.groupSelector.moveNodeDown(node, true);
1554 case KeyEvent.VK_LEFT:
1557 frame.groupSelector.moveNodeLeft(node, true);
1559 case KeyEvent.VK_RIGHT:
1562 frame.groupSelector.moveNodeRight(node, true);
1564 case KeyEvent.VK_PAGE_DOWN:
1565 frame.nextTab.actionPerformed(null);
1568 case KeyEvent.VK_PAGE_UP:
1569 frame.prevTab.actionPerformed(null);
1573 } else if (keyCode == KeyEvent.VK_ENTER){
1575 try { runCommand("edit");
1576 } catch (Throwable ex) {
1577 ex.printStackTrace();
1584 public void setupMainPanel() {
1585 //System.out.println("setupMainPanel");
1586 //splitPane = new com.jgoodies.uif_lite.component.UIFSplitPane(JSplitPane.VERTICAL_SPLIT);
1587 splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
1588 splitPane.setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
1589 // We replace the default FocusTraversalPolicy with a subclass
1590 // that only allows FieldEditor components to gain keyboard focus,
1591 // if there is an entry editor open.
1592 /*splitPane.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
1593 protected boolean accept(Component c) {
1595 if (showing == null)
1596 return super.accept(c);
1598 return (super.accept(c) &&
1599 (c instanceof FieldEditor));
1605 splitPane.setTopComponent(mainTable.getPane());
1608 // If an entry is currently being shown, make sure it stays shown,
1609 // otherwise set the bottom component to null.
1610 if (mode == SHOWING_PREVIEW) {
1611 mode = SHOWING_NOTHING;
1612 int row = mainTable.findEntry(currentPreview.entry);
1614 mainTable.setRowSelectionInterval(row, row);
1617 else if (mode == SHOWING_EDITOR) {
1618 mode = SHOWING_NOTHING;
1619 /*int row = mainTable.findEntry(currentEditor.entry);
1621 mainTable.setRowSelectionInterval(row, row);
1623 //showEntryEditor(currentEditor);
1625 splitPane.setBottomComponent(null);
1628 setLayout(new BorderLayout());
1630 add(splitPane, BorderLayout.CENTER);
1632 // Set up AutoCompleters for this panel:
1633 if (Globals.prefs.getBoolean("autoComplete")) {
1634 instantiateAutoCompleters();
1637 splitPane.revalidate();
1642 public HashMap getAutoCompleters() {
1643 return autoCompleters;
1646 public AutoCompleter getAutoCompleter(String fieldName) {
1647 return (AutoCompleter)autoCompleters.get(fieldName);
1650 private void instantiateAutoCompleters() {
1651 autoCompleters.clear();
1652 String[] completeFields = Globals.prefs.getStringArray("autoCompleteFields");
1653 for (int i = 0; i < completeFields.length; i++) {
1654 String field = completeFields[i];
1655 autoCompleters.put(field, new AutoCompleter(field));
1657 for (Iterator i=database.getKeySet().iterator(); i.hasNext();) {
1658 BibtexEntry entry = database.getEntryById((String)i.next());
1659 Util.updateCompletersForEntry(autoCompleters, entry);
1665 * This method is called after a database has been parsed. The
1666 * hashmap contains the contents of all comments in the .bib file
1667 * that started with the meta flag (GUIGlobals.META_FLAG).
1668 * In this method, the meta data are input to their respective
1671 * @param meta Metadata to input.
1673 public void parseMetaData(HashMap meta) {
1674 metaData = new MetaData(meta,database());
1679 public void refreshTable() {
1680 //System.out.println("hiding="+hidingNonHits+"\tlastHits="+lastSearchHits);
1681 // This method is called by EntryTypeForm when a field value is
1682 // stored. The table is scheduled for repaint.
1683 entryTable.assureNotEditing();
1684 //entryTable.invalidate();
1685 BibtexEntry[] bes = entryTable.getSelectedEntries();
1687 tableModel.update(lastSearchHits);
1689 tableModel.update();
1690 //tableModel.remap();
1691 if ((bes != null) && (bes.length > 0))
1692 selectEntries(bes, 0);
1694 //long toc = System.currentTimeMillis();
1695 // Util.pr("Refresh took: "+(toc-tic)+" ms");
1698 public void updatePreamble() {
1699 if (preambleEditor != null)
1700 preambleEditor.updatePreamble();
1703 public void assureStringDialogNotEditing() {
1704 if (stringDialog != null)
1705 stringDialog.assureNotEditing();
1708 public void updateStringDialog() {
1709 if (stringDialog != null)
1710 stringDialog.refreshTable();
1713 public void updateEntryPreviewToRow(BibtexEntry e) {
1717 public void adjustSplitter() {
1718 int mode = getMode();
1719 if (mode == SHOWING_PREVIEW) {
1720 splitPane.setDividerLocation(splitPane.getHeight()-GUIGlobals.PREVIEW_PANEL_HEIGHT);
1722 splitPane.setDividerLocation(GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1730 * Stores the source view in the entry editor, if one is open, has the source view
1731 * selected and the source has been edited.
1732 * @return boolean false if there is a validation error in the source panel, true otherwise.
1734 public boolean entryEditorAllowsChange() {
1735 Component c = splitPane.getBottomComponent();
1736 if ((c != null) && (c instanceof EntryEditor)) {
1737 return ((EntryEditor)c).lastSourceAccepted();
1743 public void moveFocusToEntryEditor() {
1744 Component c = splitPane.getBottomComponent();
1745 if ((c != null) && (c instanceof EntryEditor)) {
1746 new FocusRequester(c);
1751 * Ensure that no preview is shown. Called when preview is turned off. Must chech if
1752 * a preview is in fact visible before doing anything rash.
1754 public void hidePreview() {
1755 Globals.prefs.putBoolean("previewEnabled", false);
1757 Component c = splitPane.getBottomComponent();
1758 if ((c != null) && !(c instanceof EntryEditor))
1759 splitPane.setBottomComponent(null);
1762 public boolean isShowingEditor() {
1763 return ((splitPane.getBottomComponent() != null)
1764 && (splitPane.getBottomComponent() instanceof EntryEditor));
1767 public void showEntry(final BibtexEntry be) {
1768 if (showing == be) {
1769 if (splitPane.getBottomComponent() == null) {
1770 // This is the special occasion when showing is set to an
1771 // entry, but no entry editor is in fact shown. This happens
1772 // after Preferences dialog is closed, and it means that we
1773 // must make sure the same entry is shown again. We do this by
1774 // setting showing to null, and recursively calling this method.
1778 // The correct entry is already being shown. Make sure the editor
1780 ((EntryEditor)splitPane.getBottomComponent()).updateAllFields();
1789 String visName = null;
1790 if (showing != null) {
1791 visName = ((EntryEditor)splitPane.getBottomComponent()).
1792 getVisiblePanelName();
1794 if (showing != null)
1795 divLoc = splitPane.getDividerLocation();
1797 if (entryEditors.containsKey(be.getType().getName())) {
1798 // We already have an editor for this entry type.
1799 form = (EntryEditor)entryEditors.get
1800 ((be.getType().getName()));
1802 if (visName != null)
1803 form.setVisiblePanel(visName);
1804 splitPane.setBottomComponent(form);
1805 //highlightEntry(be);
1807 // We must instantiate a new editor for this type.
1808 form = new EntryEditor(frame, BasePanel.this, be);
1809 if (visName != null)
1810 form.setVisiblePanel(visName);
1811 splitPane.setBottomComponent(form);
1813 //highlightEntry(be);
1814 entryEditors.put(be.getType().getName(), form);
1818 splitPane.setDividerLocation(divLoc);
1821 splitPane.setDividerLocation
1822 (GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1823 //new FocusRequester(form);
1824 //form.requestFocus();
1827 setEntryEditorEnabled(true); // Make sure it is enabled.
1831 * Get an entry editor ready to edit the given entry. If an appropriate editor is already
1832 * cached, it will be updated and returned.
1833 * @param entry The entry to be edited.
1834 * @return A suitable entry editor.
1836 public EntryEditor getEntryEditor(BibtexEntry entry) {
1838 if (entryEditors.containsKey(entry.getType().getName())) {
1839 EntryEditor visibleNow = currentEditor;
1840 // We already have an editor for this entry type.
1841 form = (EntryEditor)entryEditors.get
1842 ((entry.getType().getName()));
1844 form.switchTo(entry);
1845 //if (visName != null)
1846 // form.setVisiblePanel(visName);
1848 // We must instantiate a new editor for this type.
1849 form = new EntryEditor(frame, BasePanel.this, entry);
1850 //if (visName != null)
1851 // form.setVisiblePanel(visName);
1853 entryEditors.put(entry.getType().getName(), form);
1858 public EntryEditor getCurrentEditor() {
1859 return currentEditor;
1863 * Sets the given entry editor as the bottom component in the split pane. If an entry editor already
1864 * was shown, makes sure that the divider doesn't move.
1865 * Updates the mode to SHOWING_EDITOR.
1866 * @param editor The entry editor to add.
1868 public void showEntryEditor(EntryEditor editor) {
1869 int oldSplitterLocation = -1;
1870 if (mode == SHOWING_EDITOR)
1871 oldSplitterLocation = splitPane.getDividerLocation();
1872 boolean adjustSplitter = (mode == WILL_SHOW_EDITOR);
1873 mode = SHOWING_EDITOR;
1874 currentEditor = editor;
1875 splitPane.setBottomComponent(editor);
1876 if (oldSplitterLocation > 0)
1877 splitPane.setDividerLocation(oldSplitterLocation);
1878 if (adjustSplitter) {
1880 //new FocusRequester(editor);
1885 * Sets the given preview panel as the bottom component in the split panel.
1886 * Updates the mode to SHOWING_PREVIEW.
1887 * @param preview The preview to show.
1889 public void showPreview(PreviewPanel preview) {
1890 mode = SHOWING_PREVIEW;
1891 currentPreview = preview;
1892 splitPane.setBottomComponent(preview.getPane());
1896 * Removes the bottom component.
1898 public void hideBottomComponent() {
1899 mode = SHOWING_NOTHING;
1900 splitPane.setBottomComponent(null);
1904 * This method selects the given entry, and scrolls it into view in the table.
1905 * If an entryEditor is shown, it is given focus afterwards.
1907 public void highlightEntry(final BibtexEntry be) {
1908 //SwingUtilities.invokeLater(new Thread() {
1909 // public void run() {
1910 final int row = mainTable.findEntry(be);
1912 mainTable.setRowSelectionInterval(row, row);
1913 //entryTable.setActiveRow(row);
1914 mainTable.ensureVisible(row);
1922 * This method is called from an EntryEditor when it should be closed. We relay
1923 * to the selection listener, which takes care of the rest.
1924 * @param editor The entry editor to close.
1926 public void entryEditorClosing(EntryEditor editor) {
1927 selectionListener.entryEditorClosing(editor);
1931 * This method selects the given enties.
1932 * If an entryEditor is shown, it is given focus afterwards.
1934 /*public void selectEntries(final BibtexEntry[] bes, final int toScrollTo) {
1936 SwingUtilities.invokeLater(new Thread() {
1938 int rowToScrollTo = 0;
1939 entryTable.revalidate();
1940 entryTable.clearSelection();
1941 loop: for (int i=0; i<bes.length; i++) {
1944 int row = tableModel.getNumberFromName(bes[i].getId());
1946 rowToScrollTo = row;
1948 entryTable.addRowSelectionIntervalQuietly(row, row);
1950 entryTable.ensureVisible(rowToScrollTo);
1951 Component comp = splitPane.getBottomComponent();
1952 //if (comp instanceof EntryEditor)
1953 // comp.requestFocus();
1959 * Closes the entry editor if it is showing the given entry.
1961 * @param be a <code>BibtexEntry</code> value
1963 public void ensureNotShowing(BibtexEntry be) {
1964 if ((mode == SHOWING_EDITOR) && (currentEditor.getEntry() == be)) {
1965 selectionListener.entryEditorClosing(currentEditor);
1969 public void updateEntryEditorIfShowing() {
1970 if (mode == SHOWING_EDITOR) {
1971 if (currentEditor.getType() != currentEditor.getEntry().getType()) {
1972 // The entry has changed type, so we must get a new editor.
1974 EntryEditor newEditor = getEntryEditor(currentEditor.getEntry());
1975 showEntryEditor(newEditor);
1977 currentEditor.updateAllFields();
1978 currentEditor.updateSource();
1984 * If an entry editor is showing, make sure its currently focused field
1985 * stores its changes, if any.
1987 public void storeCurrentEdit() {
1988 if (isShowingEditor()) {
1989 EntryEditor editor = (EntryEditor)splitPane.getBottomComponent();
1990 editor.storeCurrentEdit();
1996 * This method iterates through all existing entry editors in this
1997 * BasePanel, telling each to update all its instances of
1998 * FieldContentSelector. This is done to ensure that the list of words
1999 * in each selector is up-to-date after the user has made changes in
2000 * the Manage dialog.
2002 public void updateAllContentSelectors() {
2003 for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2004 EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2005 ed.updateAllContentSelectors();
2009 public void rebuildAllEntryEditors() {
2010 for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2011 EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2017 public void markBaseChanged() {
2020 // Put an asterix behind the file name to indicate the
2021 // database has changed.
2022 String oldTitle = frame.getTabTitle(this);
2023 if (!oldTitle.endsWith("*"))
2024 frame.setTabTitle(this, oldTitle+"*", frame.getTabTooltip(this));
2026 // If the status line states that the base has been saved, we
2027 // remove this message, since it is no longer relevant. If a
2028 // different message is shown, we leave it.
2029 if (frame.statusLine.getText().startsWith("Saved database"))
2033 public void markNonUndoableBaseChanged() {
2034 nonUndoableChange = true;
2038 public synchronized void markChangedOrUnChanged() {
2039 if (undoManager.hasChanged()) {
2043 else if (baseChanged && !nonUndoableChange) {
2044 baseChanged = false;
2045 if (getFile() != null)
2046 frame.setTabTitle(BasePanel.this, getFile().getName(),
2047 getFile().getAbsolutePath());
2049 frame.setTabTitle(BasePanel.this, Globals.lang("untitled"), null);
2054 * Selects a single entry, and scrolls the table to center it.
2056 * @param pos Current position of entry to select.
2059 public void selectSingleEntry(int pos) {
2060 mainTable.clearSelection();
2061 mainTable.addRowSelectionInterval(pos, pos);
2062 mainTable.scrollToCenter(pos, 0);
2066 * Selects all entries with a non-zero value in the field
2067 * @param field <code>String</code> field name.
2069 /* public void selectResults(String field) {
2070 LinkedList intervals = new LinkedList();
2071 int prevStart = -1, prevToSel = 0;
2072 // First we build a list of intervals to select, without touching the table.
2073 for (int i = 0; i < entryTable.getRowCount(); i++) {
2074 String value = (String) (database.getEntryById
2075 (tableModel.getIdForRow(i)))
2077 if ( (value != null) && !value.equals("0")) {
2082 else if (prevStart >= 0) {
2083 intervals.add(new int[] {prevStart, prevToSel});
2087 // Then select those intervals, if any.
2088 if (intervals.size() > 0) {
2089 entryTable.setSelectionListenerEnabled(false);
2090 entryTable.clearSelection();
2091 for (Iterator i=intervals.iterator(); i.hasNext();) {
2092 int[] interval = (int[])i.next();
2093 entryTable.addRowSelectionInterval(interval[0], interval[1]);
2095 entryTable.setSelectionListenerEnabled(true);
2099 public void setSearchMatcher(SearchMatcher matcher) {
2100 searchFilterList.setMatcher(matcher);
2103 public void setGroupMatcher(Matcher matcher) {
2104 groupFilterList.setMatcher(matcher);
2107 public void stopShowingSearchResults() {
2108 searchFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2111 public void stopShowingGroup() {
2112 groupFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2116 public BibtexDatabase getDatabase(){
2120 public void preambleEditorClosing() {
2121 preambleEditor = null;
2124 public void stringsClosing() {
2125 stringDialog = null;
2128 public void changeType(BibtexEntry entry, BibtexEntryType type) {
2129 changeType(new BibtexEntry[] {entry}, type);
2132 public void changeType(BibtexEntryType type) {
2133 BibtexEntry[] bes = mainTable.getSelectedEntries();
2134 changeType(bes, type);
2137 public void changeType(BibtexEntry[] bes, BibtexEntryType type) {
2139 if ((bes == null) || (bes.length == 0)) {
2140 output("First select the entries you wish to change type "+
2144 if (bes.length > 1) {
2145 int choice = JOptionPane.showConfirmDialog
2146 (this, "Multiple entries selected. Do you want to change"
2147 +"\nthe type of all these to '"+type.getName()+"'?",
2148 "Change type", JOptionPane.YES_NO_OPTION,
2149 JOptionPane.WARNING_MESSAGE);
2150 if (choice == JOptionPane.NO_OPTION)
2154 NamedCompound ce = new NamedCompound(Globals.lang("change type"));
2155 for (int i=0; i<bes.length; i++) {
2156 ce.addEdit(new UndoableChangeType(bes[i],
2159 bes[i].setType(type);
2162 output(Globals.lang("Changed type to")+" '"+type.getName()+"' "
2163 +Globals.lang("for")+" "+bes.length
2164 +" "+Globals.lang("entries")+".");
2166 undoManager.addEdit(ce);
2168 updateEntryEditorIfShowing();
2171 public boolean showDeleteConfirmationDialog(int numberOfEntries) {
2172 if (Globals.prefs.getBoolean("confirmDelete")) {
2173 String msg = Globals.lang("Really delete the selected")
2174 + " " + Globals.lang("entry") + "?",
2175 title = Globals.lang("Delete entry");
2176 if (numberOfEntries > 1) {
2177 msg = Globals.lang("Really delete the selected")
2178 + " " + numberOfEntries + " " + Globals.lang("entries") + "?";
2179 title = Globals.lang("Delete multiple entries");
2182 CheckBoxMessage cb = new CheckBoxMessage
2183 (msg, Globals.lang("Disable this confirmation dialog"), false);
2185 int answer = JOptionPane.showConfirmDialog(frame, cb, title,
2186 JOptionPane.YES_NO_OPTION,
2187 JOptionPane.QUESTION_MESSAGE);
2188 if (cb.isSelected())
2189 Globals.prefs.putBoolean("confirmDelete", false);
2190 return (answer == JOptionPane.YES_OPTION);
2196 * If the relevant option is set, autogenerate keys for all entries that are
2199 public void autoGenerateKeysBeforeSaving() {
2200 if (Globals.prefs.getBoolean("generateKeysBeforeSaving")) {
2202 NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
2203 boolean any = false;
2204 for (Iterator i=database.getKeySet().iterator(); i.hasNext();) {
2205 bes = database.getEntryById((String)i.next());
2206 String oldKey = bes.getCiteKey();
2207 if ((oldKey == null) || (oldKey.equals(""))) {
2208 LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, bes);
2209 ce.addEdit(new UndoableKeyChange(database, bes.getId(), null,
2210 (String)bes.getField(BibtexFields.KEY_FIELD)));
2214 // Store undo information, if any:
2217 undoManager.addEdit(ce);
2223 * Activates or deactivates the entry preview, depending on the argument.
2224 * When deactivating, makes sure that any visible preview is hidden.
2227 public void setPreviewActive(boolean enabled) {
2228 selectionListener.setPreviewActive(enabled);
2232 class UndoAction extends BaseAction {
2233 public void action() {
2235 String name = undoManager.getUndoPresentationName();
2239 } catch (CannotUndoException ex) {
2240 frame.output(Globals.lang("Nothing to undo")+".");
2242 // After everything, enable/disable the undo/redo actions
2244 //updateUndoState();
2245 //redoAction.updateRedoState();
2246 markChangedOrUnChanged();
2250 class RedoAction extends BaseAction {
2251 public void action() {
2253 String name = undoManager.getRedoPresentationName();
2257 } catch (CannotRedoException ex) {
2258 frame.output(Globals.lang("Nothing to redo")+".");
2260 // After everything, enable/disable the undo/redo actions
2262 //updateRedoState();
2263 //undoAction.updateUndoState();
2264 markChangedOrUnChanged();
2268 // Method pertaining to the ClipboardOwner interface.
2269 public void lostOwnership(Clipboard clipboard, Transferable contents) {}
2272 public void setEntryEditorEnabled(boolean enabled) {
2273 if ((showing != null) && (splitPane.getBottomComponent() instanceof EntryEditor)) {
2274 EntryEditor ed = (EntryEditor)splitPane.getBottomComponent();
2275 if (ed.isEnabled() != enabled)
2276 ed.setEnabled(enabled);
2280 public String fileMonitorHandle() { return fileMonitorHandle; }
2282 public void fileUpdated() {
2284 return; // We are just saving the file, so this message is most likely due
2285 // to bad timing. If not, we'll handle it on the next polling.
2286 //Util.pr("File '"+file.getPath()+"' has been modified.");
2287 updatedExternally = true;
2289 final ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this);
2291 // Adding the sidepane component is Swing work, so we must do this in the Swing
2293 Thread t = new Thread() {
2296 // Check if there is already a notification about external
2298 boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.NAME);
2300 sidePaneManager.hideComponent(FileUpdatePanel.NAME);
2301 sidePaneManager.unregisterComponent(FileUpdatePanel.NAME);
2303 FileUpdatePanel pan = new FileUpdatePanel(frame, BasePanel.this,
2304 sidePaneManager, getFile(), scanner);
2305 sidePaneManager.register(FileUpdatePanel.NAME, pan);
2306 sidePaneManager.show(FileUpdatePanel.NAME);
2307 setUpdatedExternally(false);
2308 //scanner.displayResult();
2312 // Test: running scan automatically in background
2313 scanner.changeScan(BasePanel.this.getFile());
2316 } catch (InterruptedException e) {
2317 e.printStackTrace();
2320 if (scanner.changesFound()) {
2321 SwingUtilities.invokeLater(t);
2323 setUpdatedExternally(false);
2324 //System.out.println("No changes found.");
2328 public void fileRemoved() {
2329 Util.pr("File '"+getFile().getPath()+"' has been deleted.");
2333 public void cleanUp() {
2334 if (fileMonitorHandle != null)
2335 Globals.fileUpdateMonitor.removeUpdateListener(fileMonitorHandle);
2338 public void setUpdatedExternally(boolean b) {
2339 updatedExternally = b;
2343 * Get an array containing the currently selected entries.
2345 * @return An array containing the selected entries.
2347 public BibtexEntry[] getSelectedEntries() {
2348 return mainTable.getSelectedEntries();
2352 * Get the file where this database was last saved to or loaded from, if any.
2354 * @return The relevant File, or null if none is defined.
2356 public File getFile() {
2357 return metaData.getFile();
2361 * Get a String containing a comma-separated list of the bibtex keys
2362 * of the selected entries.
2364 * @return A comma-separated list of the keys of the selected entries.
2366 public String getKeysForSelection() {
2367 List entries = mainTable.getSelected();
2368 StringBuffer result = new StringBuffer();
2369 String citeKey = "";//, message = "";
2370 boolean first = true;
2371 for (Iterator i = entries.iterator(); i.hasNext();) {
2372 BibtexEntry bes = (BibtexEntry) i.next();
2373 citeKey = (String) bes.getField(BibtexFields.KEY_FIELD);
2374 // if the key is empty we give a warning and ignore this entry
2375 if (citeKey == null || citeKey.equals(""))
2378 result.append(citeKey);
2381 result.append(",").append(citeKey);
2384 return result.toString();
2387 public GroupSelector getGroupSelector() {
2388 return frame.groupSelector;
2392 public boolean isUpdatedExternally() {
2393 return updatedExternally;
2397 public String getFileMonitorHandle() {
2398 return fileMonitorHandle;
2402 public void setFileMonitorHandle(String fileMonitorHandle) {
2403 this.fileMonitorHandle = fileMonitorHandle;
2406 public SidePaneManager getSidePaneManager() {
2407 return sidePaneManager;
2411 public void setNonUndoableChange(boolean nonUndoableChange) {
2412 this.nonUndoableChange = nonUndoableChange;
2415 public void setBaseChanged(boolean baseChanged) {
2416 this.baseChanged = baseChanged;
2420 public void setSaving(boolean saving) {
2421 this.saving = saving;