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;
32 import java.awt.datatransfer.*;
33 import java.awt.event.*;
36 import java.util.List;
37 import java.nio.charset.UnsupportedCharsetException;
39 import javax.swing.tree.TreePath;
40 import javax.swing.undo.*;
41 import net.sf.jabref.collab.*;
42 import net.sf.jabref.export.*;
43 import net.sf.jabref.external.AutoSetExternalFileForEntries;
44 import net.sf.jabref.groups.*;
45 import net.sf.jabref.imports.*;
46 import net.sf.jabref.labelPattern.LabelPatternUtil;
47 import net.sf.jabref.undo.*;
48 import net.sf.jabref.wizard.text.gui.TextInputDialog;
49 import net.sf.jabref.journals.AbbreviateAction;
50 import net.sf.jabref.journals.UnabbreviateAction;
51 import net.sf.jabref.gui.*;
52 import net.sf.jabref.search.NoSearchMatcher;
53 import net.sf.jabref.search.SearchMatcher;
54 import com.jgoodies.uif_lite.component.UIFSplitPane;
55 import com.jgoodies.forms.builder.DefaultFormBuilder;
56 import com.jgoodies.forms.layout.FormLayout;
57 import ca.odell.glazedlists.FilterList;
58 import ca.odell.glazedlists.matchers.Matcher;
59 import ca.odell.glazedlists.event.ListEventListener;
60 import ca.odell.glazedlists.event.ListEvent;
62 public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener {
64 public final static int SHOWING_NOTHING=0, SHOWING_PREVIEW=1, SHOWING_EDITOR=2, WILL_SHOW_EDITOR=3;
66 private EntryEditor currentEditor = null;
67 private PreviewPanel currentPreview = null;
71 private MainTableSelectionListener selectionListener = null;
72 private ListEventListener groupsHighlightListener;
73 UIFSplitPane contentPane = new UIFSplitPane();
76 //BibtexEntry testE = new BibtexEntry("tt");
77 //boolean previewActive = true;
80 BibtexDatabase database;
81 // The database shown in this panel.
83 fileToOpen = null; // The filename of the database.
84 String fileMonitorHandle = null;
85 boolean saving = false, updatedExternally = false;
86 private String encoding;
88 GridBagLayout gbl = new GridBagLayout();
89 GridBagConstraints con = new GridBagConstraints();
91 //Hashtable autoCompleters = new Hashtable();
92 // Hashtable that holds as keys the names of the fields where
93 // autocomplete is active, and references to the autocompleter objects.
96 public CountingUndoManager undoManager = new CountingUndoManager(this);
97 UndoAction undoAction = new UndoAction();
98 RedoAction redoAction = new RedoAction();
100 //ExampleFileFilter fileFilter;
101 // File filter for .bib files.
103 boolean baseChanged = false, nonUndoableChange = false;
104 // Used to track whether the base has changed since last save.
106 //EntryTableModel tableModel = null;
107 //public EntryTable entryTable = null;
108 public MainTable mainTable = null;
109 public FilterList searchFilterList = null, groupFilterList = null;
111 public RightClickMenu rcm;
113 BibtexEntry showing = null;
114 // To indicate which entry is currently shown.
115 public HashMap entryEditors = new HashMap();
116 // To contain instantiated entry editors. This is to save time
117 // in switching between entries.
119 //HashMap entryTypeForms = new HashMap();
120 // Hashmap to keep track of which entries currently have open
121 // EntryTypeForm dialogs.
123 PreambleEditor preambleEditor = null;
124 // Keeps track of the preamble dialog if it is open.
126 StringDialog stringDialog = null;
127 // Keeps track of the string dialog if it is open.
130 * The group selector component for this database. Instantiated by the
131 * SidePaneManager if necessary, or from this class if merging groups from a
132 * different database.
134 //GroupSelector groupSelector;
136 public boolean sortingBySearchResults = false,
137 coloringBySearchResults = false,
138 hidingNonHits = false,
139 sortingByGroup = false,
140 sortingByCiteSeerResults = false,
141 coloringByGroup = false;
142 //previewEnabled = Globals.prefs.getBoolean("previewEnabled");
143 int lastSearchHits = -1; // The number of hits in the latest search.
144 // Potential use in hiding non-hits completely.
146 // MetaData parses, keeps and writes meta data.
148 HashMap fieldExtras = new HashMap();
149 //## keep track of all keys for duplicate key warning and unique key generation
150 //private HashMap allKeys = new HashMap(); // use a map instead of a set since i need to know how many of each key is inthere
152 private boolean suppressOutput = false;
154 private HashMap actions = new HashMap();
155 private SidePaneManager sidePaneManager;
158 * Create a new BasePanel with an empty database.
159 * @param frame The application window.
161 public BasePanel(JabRefFrame frame) {
162 this.sidePaneManager = Globals.sidePaneManager;
163 database = new BibtexDatabase();
164 metaData = new MetaData();
165 metaData.initializeNewDatabase();
169 encoding = Globals.prefs.get("defaultEncoding");
170 //System.out.println("Default: "+encoding);
173 public BasePanel(JabRefFrame frame, BibtexDatabase db, File file,
174 HashMap meta, String encoding) {
176 this.encoding = encoding;
177 // System.out.println(encoding);
178 //super(JSplitPane.HORIZONTAL_SPLIT, true);
179 this.sidePaneManager = Globals.sidePaneManager;
185 metaData = new MetaData();
186 metaData.initializeNewDatabase();
190 /*if (Globals.prefs.getBoolean("autoComplete")) {
191 db.setCompleters(autoCompleters);
196 // Register so we get notifications about outside changes to the file.
199 fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(this,
201 } catch (IOException ex) {
205 public int getMode() {
209 public BibtexDatabase database() { return database; }
210 public MetaData metaData() { return metaData; }
211 public File file() { return file; }
212 public JabRefFrame frame() { return frame; }
213 public JabRefPreferences prefs() { return Globals.prefs; }
215 public String getEncoding() { return encoding; }
216 public void setEncoding(String encoding) {
217 this.encoding = encoding;
220 public void output(String s) {
221 //Util.pr("\""+s+"\""+(SwingUtilities.isEventDispatchThread()));
226 private void setupActions() {
228 actions.put("undo", undoAction);
229 actions.put("redo", redoAction);
231 // The action for opening an entry editor.
232 actions.put("edit", new BaseAction() {
233 public void action() {
234 selectionListener.editSignalled();
237 if (isShowingEditor()) {
238 new FocusRequester(splitPane.getBottomComponent());
244 //public void run() {
246 // We demand that one and only one row is selected.
247 if (entryTable.getSelectedRowCount() == 1) {
248 clickedOn = entryTable.getSelectedRow();
250 if (clickedOn >= 0) {
251 String id = tableModel.getIdForRow(clickedOn);
252 BibtexEntry be = database.getEntryById(id);
255 if (splitPane.getBottomComponent() != null) {
256 new FocusRequester(splitPane.getBottomComponent());
266 actions.put("test", new BaseAction () {
267 public void action() throws Throwable {
276 // The action for saving a database.
277 actions.put("save", new AbstractWorker() {
278 private boolean success = false, cancelled = false;
279 public void init() throws Throwable {
283 runCommand("saveAs");
286 if (updatedExternally || Globals.fileUpdateMonitor.hasBeenModified(fileMonitorHandle)) {
287 String[] opts = new String[]{Globals.lang("Review changes"), Globals.lang("Save"),
288 Globals.lang("Cancel")};
289 int answer = JOptionPane.showOptionDialog(frame, Globals.lang("File has been updated externally. "
290 + "What do you want to do?"), Globals.lang("File updated externally"),
291 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE,
292 null, opts, opts[0]);
293 /* int choice = JOptionPane.showConfirmDialog(frame, Globals.lang("File has been updated externally. "
294 +"Are you sure you want to save?"), Globals.lang("File updated externally"),
295 JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);*/
297 if (answer == JOptionPane.CANCEL_OPTION)
299 else if (answer == JOptionPane.YES_OPTION) {
300 ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this); //, panel.database(), panel.metaData());
302 scanner.changeScan(file());
303 setUpdatedExternally(false);
304 SwingUtilities.invokeLater(new Runnable() {
306 sidePaneManager.hideAway("fileUpdate");
310 //} catch (IOException ex) {
311 // ex.printStackTrace();
318 frame.output(Globals.lang("Saving database") + "...");
323 public void update() {
325 frame.setTabTitle(BasePanel.this, file.getName());
326 frame.output(Globals.lang("Saved database")+" '"
327 +file.getPath()+"'.");
328 } else if (!cancelled) {
329 frame.output(Globals.lang("Save failed"));
340 // If the option is set, autogenerate keys for all entries that are
341 // lacking keys, before saving:
342 if (Globals.prefs.getBoolean("generateKeysBeforeSaving")) {
344 NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
346 for (Iterator i=database.getKeySet().iterator(); i.hasNext();) {
347 bes = database.getEntryById((String)i.next());
348 String oldKey = bes.getCiteKey();
349 if ((oldKey == null) || (oldKey.equals(""))) {
350 LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, bes);
351 ce.addEdit(new UndoableKeyChange(database, bes.getId(), null,
352 (String)bes.getField(BibtexFields.KEY_FIELD)));
356 // Store undo information, if any:
359 undoManager.addEdit(ce);
362 // Done with autosetting keys. Now save the database:
364 success = saveDatabase(file, false, encoding);
366 //Util.pr("Testing resolve string... BasePanel line 237");
367 //Util.pr("Resolve aq: "+database.resolveString("aq"));
368 //Util.pr("Resolve text: "+database.resolveForStrings("A text which refers to the string #aq# and #billball#, hurra."));
371 Globals.fileUpdateMonitor.updateTimeStamp(fileMonitorHandle);
372 } catch (IllegalArgumentException ex) {
373 // This means the file has not yet been registered, which is the case
374 // when doing a "Save as". Maybe we should change the monitor so no
375 // exception is cast.
379 undoManager.markUnchanged();
380 // (Only) after a successful save the following
381 // statement marks that the base is unchanged
383 nonUndoableChange = false;
385 updatedExternally = false;
387 } catch (SaveException ex2) {
388 ex2.printStackTrace();
393 actions.put("saveAs", new BaseAction () {
394 public void action() throws Throwable {
396 String chosenFile = Globals.getNewFile(frame, new File(Globals.prefs.get("workingDirectory")), ".bib",
397 JFileChooser.SAVE_DIALOG, false);
399 if (chosenFile != null) {
400 file = new File(chosenFile);
401 if (!file.exists() ||
402 (JOptionPane.showConfirmDialog
403 (frame, "'"+file.getName()+"' "+Globals.lang("exists. Overwrite file?"),
404 Globals.lang("Save database"), JOptionPane.OK_CANCEL_OPTION)
405 == JOptionPane.OK_OPTION)) {
409 // Register so we get notifications about outside changes to the file.
411 fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(BasePanel.this,file);
412 } catch (IOException ex) {
413 ex.printStackTrace();
416 Globals.prefs.put("workingDirectory", file.getParent());
417 frame.getFileHistory().newFile(file.getPath());
425 actions.put("saveSelectedAs", new BaseAction () {
426 public void action() throws Throwable {
428 String chosenFile = Globals.getNewFile(frame, new File(Globals.prefs.get("workingDirectory")), ".bib",
429 JFileChooser.SAVE_DIALOG, false);
430 if (chosenFile != null) {
431 File expFile = new File(chosenFile);
432 if (!expFile.exists() ||
433 (JOptionPane.showConfirmDialog
434 (frame, "'"+expFile.getName()+"' "+
435 Globals.lang("exists. Overwrite file?"),
436 Globals.lang("Save database"), JOptionPane.OK_CANCEL_OPTION)
437 == JOptionPane.OK_OPTION)) {
438 saveDatabase(expFile, true, Globals.prefs.get("defaultEncoding"));
439 //runCommand("save");
440 frame.getFileHistory().newFile(expFile.getPath());
441 frame.output(Globals.lang("Saved selected to")+" '"
442 +expFile.getPath()+"'.");
448 // The action for copying selected entries.
449 actions.put("copy", new BaseAction() {
450 public void action() {
451 BibtexEntry[] bes = mainTable.getSelectedEntries();
453 if ((bes != null) && (bes.length > 0)) {
454 TransferableBibtexEntry trbe
455 = new TransferableBibtexEntry(bes);
456 // ! look at ClipBoardManager
457 Toolkit.getDefaultToolkit().getSystemClipboard()
458 .setContents(trbe, BasePanel.this);
459 output(Globals.lang("Copied")+" "+(bes.length>1 ? bes.length+" "
460 +Globals.lang("entries")
461 : "1 "+Globals.lang("entry")+"."));
463 // The user maybe selected a single cell.
464 int[] rows = mainTable.getSelectedRows(),
465 cols = mainTable.getSelectedColumns();
466 if ((cols.length == 1) && (rows.length == 1)) {
467 // Copy single value.
468 Object o = mainTable.getValueAt(rows[0], cols[0]);
470 StringSelection ss = new StringSelection(o.toString());
471 Toolkit.getDefaultToolkit().getSystemClipboard()
472 .setContents(ss, BasePanel.this);
474 output(Globals.lang("Copied cell contents")+".");
481 actions.put("cut", new BaseAction() {
482 public void action() throws Throwable {
484 BibtexEntry[] bes = mainTable.getSelectedEntries();
485 //int row0 = mainTable.getSelectedRow();
486 if ((bes != null) && (bes.length > 0)) {
487 // Create a CompoundEdit to make the action undoable.
488 NamedCompound ce = new NamedCompound
489 (Globals.lang(bes.length > 1 ? "cut entries" : "cut entry"));
490 // Loop through the array of entries, and delete them.
491 for (int i=0; i<bes.length; i++) {
492 database.removeEntry(bes[i].getId());
493 ensureNotShowing(bes[i]);
494 ce.addEdit(new UndoableRemoveEntry
495 (database, bes[i], BasePanel.this));
497 //entryTable.clearSelection();
498 frame.output(Globals.lang("Cut_pr")+" "+
499 (bes.length>1 ? bes.length
500 +" "+ Globals.lang("entries")
501 : Globals.lang("entry"))+".");
503 undoManager.addEdit(ce);
506 // Reselect the entry in the first prev. selected position:
507 /*if (row0 >= entryTable.getRowCount())
508 row0 = entryTable.getRowCount()-1;
510 entryTable.addRowSelectionInterval(row0, row0);*/
515 actions.put("delete", new BaseAction() {
516 public void action() {
517 boolean cancelled = false;
518 BibtexEntry[] bes = mainTable.getSelectedEntries();
519 int row0 = mainTable.getSelectedRow();
520 if ((bes != null) && (bes.length > 0)) {
522 boolean goOn = showDeleteConfirmationDialog(bes.length);
527 // Create a CompoundEdit to make the action undoable.
528 NamedCompound ce = new NamedCompound
529 (Globals.lang(bes.length > 1 ? "delete entries" : "delete entry"));
530 // Loop through the array of entries, and delete them.
531 for (int i = 0; i < bes.length; i++) {
532 database.removeEntry(bes[i].getId());
533 ensureNotShowing(bes[i]);
534 ce.addEdit(new UndoableRemoveEntry(database, bes[i], BasePanel.this));
537 frame.output(Globals.lang("Deleted") + " " +
538 (bes.length > 1 ? bes.length
539 + " " + Globals.lang("entries")
540 : Globals.lang("entry")) + ".");
542 undoManager.addEdit(ce);
543 //entryTable.clearSelection();
547 // Reselect the entry in the first prev. selected position:
548 /*if (row0 >= entryTable.getRowCount())
549 row0 = entryTable.getRowCount()-1;
551 final int toSel = row0;
553 SwingUtilities.invokeLater(new Runnable() {
555 entryTable.addRowSelectionInterval(toSel, toSel);
556 //entryTable.ensureVisible(toSel);
566 // The action for pasting entries or cell contents.
567 // Edited by Seb Wills <saw27@mrao.cam.ac.uk> on 14-Apr-04:
568 // - more robust detection of available content flavors (doesn't only look at first one offered)
569 // - support for parsing string-flavor clipboard contents which are bibtex entries.
570 // This allows you to (a) paste entire bibtex entries from a text editor, web browser, etc
571 // (b) copy and paste entries between multiple instances of JabRef (since
572 // only the text representation seems to get as far as the X clipboard, at least on my system)
573 actions.put("paste", new BaseAction() {
574 public void action() {
575 // Get clipboard contents, and see if TransferableBibtexEntry is among the content flavors offered
576 Transferable content = Toolkit.getDefaultToolkit()
577 .getSystemClipboard().getContents(null);
578 if (content != null) {
579 BibtexEntry[] bes = null;
580 if (content.isDataFlavorSupported(TransferableBibtexEntry.entryFlavor)) {
581 // We have determined that the clipboard data is a set of entries.
583 bes = (BibtexEntry[])(content.getTransferData(TransferableBibtexEntry.entryFlavor));
585 } catch (UnsupportedFlavorException ex) {
586 ex.printStackTrace();
587 } catch (IOException ex) {
588 ex.printStackTrace();
590 } else if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
591 // We have determined that no TransferableBibtexEntry is available, but
592 // there is a string, which we will handle according to context:
593 int[] rows = mainTable.getSelectedRows();
594 //cols = entryTable.getSelectedColumns();
595 //Util.pr(rows.length+" x "+cols.length);
596 /*if ((cols != null) && (cols.length == 1) && (cols[0] != 0)
597 && (rows != null) && (rows.length == 1)) {
598 // A single cell is highlighted, so paste the string straight into it without parsing
600 tableModel.setValueAt((String)(content.getTransferData(DataFlavor.stringFlavor)), rows[0], cols[0]);
603 output("Pasted cell contents");
604 } catch (UnsupportedFlavorException ex) {
605 ex.printStackTrace();
606 } catch (IOException ex) {
607 ex.printStackTrace();
608 } catch (IllegalArgumentException ex) {
609 output("Can't paste.");
612 // no single cell is selected, so try parsing the clipboard contents as bibtex entries instead
614 BibtexParser bp = new BibtexParser
615 (new java.io.StringReader( (String) (content.getTransferData(
616 DataFlavor.stringFlavor))));
617 BibtexDatabase db = bp.parse().getDatabase();
618 Util.pr("Parsed " + db.getEntryCount() + " entries from clipboard text");
619 if(db.getEntryCount()>0) {
620 Set keySet = db.getKeySet();
621 if (keySet != null) {
622 // Copy references to the entries into a BibtexEntry array.
623 // Could import directly from db, but going via bes allows re-use
624 // of the same pasting code as used for TransferableBibtexEntries
625 bes = new BibtexEntry[db.getEntryCount()];
626 Iterator it = keySet.iterator();
627 for (int i=0; it.hasNext();i++) {
628 bes[i]=db.getEntryById((String) (it.next()));
632 String cont = (String)(content.getTransferData(DataFlavor.stringFlavor));
633 Util.pr("----------------\n"+cont+"\n---------------------");
634 TextAnalyzer ta = new TextAnalyzer(cont);
635 output(Globals.lang("Unable to parse clipboard text as Bibtex entries."));
637 } catch (UnsupportedFlavorException ex) {
638 ex.printStackTrace();
639 } catch (Throwable ex) {
640 ex.printStackTrace();
645 // finally we paste in the entries (if any), which either came from TransferableBibtexEntries
646 // or were parsed from a string
647 if ((bes != null) && (bes.length > 0)) {
649 NamedCompound ce = new NamedCompound
650 (Globals.lang(bes.length > 1 ? "paste entries" : "paste entry"));
651 for (int i=0; i<bes.length; i++) {
653 BibtexEntry be = (BibtexEntry)(bes[i].clone());
654 Util.setAutomaticFields(be);
656 // We have to clone the
657 // entries, since the pasted
658 // entries must exist
659 // independently of the copied
661 be.setId(Util.createNeutralId());
662 database.insertEntry(be);
663 ce.addEdit(new UndoableInsertEntry
664 (database, be, BasePanel.this));
665 } catch (KeyCollisionException ex) {
666 Util.pr("KeyCollisionException... this shouldn't happen.");
670 undoManager.addEdit(ce);
671 //entryTable.clearSelection();
672 //entryTable.revalidate();
673 output(Globals.lang("Pasted")+" "+
674 (bes.length>1 ? bes.length+" "+
675 Globals.lang("entries") : "1 "+Globals.lang("entry"))
685 actions.put("selectAll", new BaseAction() {
686 public void action() {
687 mainTable.selectAll();
691 // The action for opening the preamble editor
692 actions.put("editPreamble", new BaseAction() {
693 public void action() {
694 if (preambleEditor == null) {
695 PreambleEditor form = new PreambleEditor
696 (frame, BasePanel.this, database, Globals.prefs);
697 Util.placeDialog(form, frame);
698 form.setVisible(true);
699 preambleEditor = form;
701 preambleEditor.setVisible(true);
707 // The action for opening the string editor
708 actions.put("editStrings", new BaseAction() {
709 public void action() {
710 if (stringDialog == null) {
711 StringDialog form = new StringDialog
712 (frame, BasePanel.this, database, Globals.prefs);
713 Util.placeDialog(form, frame);
714 form.setVisible(true);
717 stringDialog.setVisible(true);
723 // The action for toggling the groups interface
724 actions.put("toggleGroups", new BaseAction() {
725 public void action() {
726 sidePaneManager.togglePanel("groups");
727 frame.groupToggle.setSelected(sidePaneManager.isPanelVisible("groups"));
732 // The action for auto-generating keys.
733 actions.put("makeKey", new AbstractWorker() {
737 boolean cancelled = false;
739 // Run first, in EDT:
742 entries = new ArrayList(Arrays.asList(getSelectedEntries()));
743 //rows = entryTable.getSelectedRows() ;
744 numSelected = entries.size();
746 if (entries.size() == 0) { // None selected. Inform the user to select entries first.
747 JOptionPane.showMessageDialog(frame, Globals.lang("First select the entries you want keys to be generated for."),
748 Globals.lang("Autogenerate BibTeX key"), JOptionPane.INFORMATION_MESSAGE);
752 output(Globals.lang("Generating BibTeX key for")+" "+
753 numSelected+" "+(numSelected>1 ? Globals.lang("entries")
754 : Globals.lang("entry"))+"...");
757 // Run second, on a different thread:
759 BibtexEntry bes = null ;
760 NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
763 boolean hasShownWarning = false;
764 // First check if any entries have keys set already. If so, possibly remove
765 // them from consideration, or warn about overwriting keys.
766 loop: for (Iterator i=entries.iterator(); i.hasNext();) {
767 bes = (BibtexEntry)i.next();
768 if (bes.getField(BibtexFields.KEY_FIELD) != null) {
769 if (Globals.prefs.getBoolean("avoidOverwritingKey"))
770 // Rmove the entry, because its key is already set:
772 else if (Globals.prefs.getBoolean("warnBeforeOverwritingKey")) {
773 // Ask if the user wants to cancel the operation:
774 CheckBoxMessage cbm = new CheckBoxMessage(Globals.lang("One or more keys will be overwritten. Continue?"),
775 Globals.lang("Disable this confirmation dialog"), false);
776 int answer = JOptionPane.showConfirmDialog(frame, cbm, Globals.lang("Overwrite keys"),
777 JOptionPane.YES_NO_OPTION);
778 if (cbm.isSelected())
779 Globals.prefs.putBoolean("warnBeforeOverwritingKey", false);
780 if (answer == JOptionPane.NO_OPTION) {
781 // Ok, break off the operation.
785 // No need to check more entries, because the user has already confirmed
786 // that it's ok to overwrite keys:
792 HashMap oldvals = new HashMap();
793 // Iterate again, removing already set keys. This is skipped if overwriting
794 // is disabled, since all entries with keys set will have been removed.
795 if (!Globals.prefs.getBoolean("avoidOverwritingKey")) for (Iterator i=entries.iterator(); i.hasNext();) {
796 bes = (BibtexEntry)i.next();
797 // Store the old value:
798 oldvals.put(bes, bes.getField(BibtexFields.KEY_FIELD));
799 database.setCiteKeyForEntry(bes.getId(), null);
802 // Finally, set the new keys:
803 for (Iterator i=entries.iterator(); i.hasNext();) {
804 bes = (BibtexEntry)i.next();
805 bes = LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, bes);
806 ce.addEdit(new UndoableKeyChange
807 (database, bes.getId(), (String)oldvals.get(bes),
808 (String)bes.getField(BibtexFields.KEY_FIELD)));
811 undoManager.addEdit(ce);
814 // Run third, on EDT:
815 public void update() {
821 numSelected = entries.size();
822 output(Globals.lang("Generated BibTeX key for")+" "+
823 numSelected+" "+(numSelected!=1 ? Globals.lang("entries")
824 : Globals.lang("entry")));
829 actions.put("search", new BaseAction() {
830 public void action() {
831 //sidePaneManager.togglePanel("search");
832 sidePaneManager.ensureVisible("search");
833 //boolean on = sidePaneManager.isPanelVisible("search");
834 frame.searchToggle.setSelected(true);
836 frame.searchManager.startSearch();
840 actions.put("toggleSearch", new BaseAction() {
841 public void action() {
842 //sidePaneManager.togglePanel("search");
843 sidePaneManager.togglePanel("search");
844 boolean on = sidePaneManager.isPanelVisible("search");
845 frame.searchToggle.setSelected(on);
847 frame.searchManager.startSearch();
851 actions.put("incSearch", new BaseAction() {
852 public void action() {
853 sidePaneManager.ensureVisible("search");
854 frame.searchToggle.setSelected(true);
855 frame.searchManager.startIncrementalSearch();
859 // The action for copying the selected entry's key.
860 actions.put("copyKey", new BaseAction() {
861 public void action() {
862 BibtexEntry[] bes = mainTable.getSelectedEntries();
863 if ((bes != null) && (bes.length > 0)) {
865 //String[] keys = new String[bes.length];
866 Vector keys = new Vector();
867 // Collect all non-null keys.
868 for (int i=0; i<bes.length; i++)
869 if (bes[i].getField(BibtexFields.KEY_FIELD) != null)
870 keys.add(bes[i].getField(BibtexFields.KEY_FIELD));
871 if (keys.size() == 0) {
872 output("None of the selected entries have BibTeX keys.");
875 StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
876 for (int i=1; i<keys.size(); i++) {
878 sb.append((String)keys.elementAt(i));
881 StringSelection ss = new StringSelection(sb.toString());
882 Toolkit.getDefaultToolkit().getSystemClipboard()
883 .setContents(ss, BasePanel.this);
885 if (keys.size() == bes.length)
886 // All entries had keys.
887 output(Globals.lang((bes.length > 1) ? "Copied keys"
888 : "Copied key")+".");
890 output(Globals.lang("Warning")+": "+(bes.length-keys.size())
891 +" "+Globals.lang("out of")+" "+bes.length+" "+
892 Globals.lang("entries have undefined BibTeX key")+".");
897 // The action for copying a cite for the selected entry.
898 actions.put("copyCiteKey", new BaseAction() {
899 public void action() {
900 BibtexEntry[] bes = mainTable.getSelectedEntries();
901 if ((bes != null) && (bes.length > 0)) {
903 //String[] keys = new String[bes.length];
904 Vector keys = new Vector();
905 // Collect all non-null keys.
906 for (int i=0; i<bes.length; i++)
907 if (bes[i].getField(BibtexFields.KEY_FIELD) != null)
908 keys.add(bes[i].getField(BibtexFields.KEY_FIELD));
909 if (keys.size() == 0) {
910 output("None of the selected entries have BibTeX keys.");
913 StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
914 for (int i=1; i<keys.size(); i++) {
916 sb.append((String)keys.elementAt(i));
919 StringSelection ss = new StringSelection
920 ("\\cite{"+sb.toString()+"}");
921 Toolkit.getDefaultToolkit().getSystemClipboard()
922 .setContents(ss, BasePanel.this);
924 if (keys.size() == bes.length)
925 // All entries had keys.
926 output(Globals.lang((bes.length > 1) ? "Copied keys"
927 : "Copied key")+".");
929 output(Globals.lang("Warning")+": "+(bes.length-keys.size())
930 +" "+Globals.lang("out of")+" "+bes.length+" "+
931 Globals.lang("entries have undefined BibTeX key")+".");
936 actions.put("mergeDatabase", new AppendDatabaseAction(frame, this));
938 actions.put("openFile", new BaseAction() {
939 public void action() {
942 BibtexEntry[] bes = mainTable.getSelectedEntries();
944 if ( (bes != null) && (bes.length == 1)) {
945 Object link = bes[0].getField("ps");
946 if (bes[0].getField("pdf") != null) {
947 link = bes[0].getField("pdf");
950 String filepath = null;
952 filepath = link.toString();
956 // see if we can fall back to a filename based on the bibtex key
958 Object key = bes[0].getField(BibtexFields.KEY_FIELD);
960 basefile = key.toString();
961 final String[] types = new String[] {"pdf", "ps"};
962 final String sep = System.getProperty("file.separator");
963 for (int i = 0; i < types.length; i++) {
964 String dir = Globals.prefs.get(types[i]+"Directory");
965 if (dir.endsWith(sep)) {
966 dir = dir.substring(0, dir.length()-sep.length());
968 String found = Util.findPdf(basefile, types[i], dir, new OpenFileFilter("."+types[i]));
970 filepath = dir+sep+found;
978 if (filepath != null) {
979 //output(Globals.lang("Calling external viewer..."));
981 Util.openExternalViewer(metaData(), filepath, field);
982 output(Globals.lang("External viewer called") + ".");
984 catch (IOException ex) {
985 output(Globals.lang("Error") + ": " + ex.getMessage());
990 "No pdf or ps defined, and no file matching Bibtex key found") +
994 output(Globals.lang("No entries or multiple entries selected."));
1001 actions.put("openUrl", new BaseAction() {
1002 public void action() {
1003 BibtexEntry[] bes = mainTable.getSelectedEntries();
1004 String field = "doi";
1005 if ((bes != null) && (bes.length == 1)) {
1006 Object link = bes[0].getField("doi");
1007 if (bes[0].getField("url") != null) {
1008 link = bes[0].getField("url");
1012 //output(Globals.lang("Calling external viewer..."));
1014 Util.openExternalViewer(metaData(), link.toString(), field);
1015 output(Globals.lang("External viewer called")+".");
1016 } catch (IOException ex) {
1017 output(Globals.lang("Error") + ": " + ex.getMessage());
1021 output(Globals.lang("No url defined")+".");
1023 output(Globals.lang("No entries or multiple entries selected."));
1027 actions.put("replaceAll", new BaseAction() {
1028 public void action() {
1029 ReplaceStringDialog rsd = new ReplaceStringDialog(frame);
1030 rsd.setVisible(true);
1031 if (!rsd.okPressed())
1034 NamedCompound ce = new NamedCompound(Globals.lang("Replace string"));
1035 if (!rsd.selOnly()) {
1036 for (Iterator i=database.getKeySet().iterator();
1038 counter += rsd.replace(database.getEntryById((String)i.next()), ce);
1040 BibtexEntry[] bes = mainTable.getSelectedEntries();
1041 for (int i=0; i<bes.length; i++)
1042 counter += rsd.replace(bes[i], ce);
1045 output(Globals.lang("Replaced")+" "+counter+" "+
1046 Globals.lang(counter==1?"occurence":"occurences")+".");
1049 undoManager.addEdit(ce);
1055 actions.put("dupliCheck", new BaseAction() {
1056 public void action() {
1057 DuplicateSearch ds = new DuplicateSearch(BasePanel.this);
1062 actions.put("strictDupliCheck", new BaseAction() {
1063 public void action() {
1064 StrictDuplicateSearch ds = new StrictDuplicateSearch(BasePanel.this);
1069 actions.put("plainTextImport", new BaseAction() {
1070 public void action()
1072 // get Type of new entry
1073 EntryTypeDialog etd = new EntryTypeDialog(frame);
1074 Util.placeDialog(etd, BasePanel.this);
1075 etd.setVisible(true);
1076 BibtexEntryType tp = etd.getChoice();
1080 String id = Util.createNeutralId();
1081 BibtexEntry bibEntry = new BibtexEntry(id, tp) ;
1082 TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1085 Util.placeDialog(tidialog, BasePanel.this);
1086 tidialog.setVisible(true);
1088 if (tidialog.okPressed())
1090 Util.setAutomaticFields(Arrays.asList(new BibtexEntry[] {bibEntry}));
1091 insertEntry(bibEntry) ;
1096 // The action starts the "import from plain text" dialog
1097 /*actions.put("importPlainText", new BaseAction() {
1098 public void action()
1100 BibtexEntry bibEntry = null ;
1101 // try to get the first marked entry
1102 BibtexEntry[] bes = entryTable.getSelectedEntries();
1103 if ((bes != null) && (bes.length > 0))
1106 if (bibEntry != null)
1108 // Create an UndoableInsertEntry object.
1109 undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1111 TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1114 Util.placeDialog(tidialog, BasePanel.this);
1115 tidialog.setVisible(true);
1117 if (tidialog.okPressed())
1119 output(Globals.lang("changed ")+" '"
1120 +bibEntry.getType().getName().toLowerCase()+"' "
1121 +Globals.lang("entry")+".");
1123 int row = tableModel.getNumberFromName(bibEntry.getId());
1125 entryTable.clearSelection();
1126 entryTable.scrollTo(row);
1127 markBaseChanged(); // The database just changed.
1128 if (Globals.prefs.getBoolean("autoOpenForm"))
1130 showEntry(bibEntry);
1137 actions.put("markEntries", new AbstractWorker() {
1138 private int besLength = -1;
1141 NamedCompound ce = new NamedCompound(Globals.lang("Mark entries"));
1142 BibtexEntry[] bes = mainTable.getSelectedEntries();
1143 besLength = bes.length;
1146 for (int i=0; i<bes.length; i++) {
1147 Util.markEntry(bes[i], ce);
1150 undoManager.addEdit(ce);
1153 public void update() {
1155 output(Globals.lang("Marked selected")+" "+Globals.lang(besLength>0?"entry":"entries"));
1160 actions.put("unmarkEntries", new BaseAction() {
1161 public void action() {
1163 NamedCompound ce = new NamedCompound(Globals.lang("Unmark entries"));
1164 BibtexEntry[] bes = mainTable.getSelectedEntries();
1167 for (int i=0; i<bes.length; i++) {
1168 Util.unmarkEntry(bes[i], database, ce);
1171 undoManager.addEdit(ce);
1173 output(Globals.lang("Unmarked selected")+" "+Globals.lang(bes.length>0?"entry":"entries"));
1174 } catch (Throwable ex) { ex.printStackTrace(); }
1178 actions.put("unmarkAll", new BaseAction() {
1179 public void action() {
1180 NamedCompound ce = new NamedCompound(Globals.lang("Unmark all"));
1181 Set keySet = database.getKeySet();
1182 for (Iterator i = keySet.iterator(); i.hasNext(); ) {
1183 BibtexEntry be = database.getEntryById( (String) i.next());
1184 Util.unmarkEntry(be, database, ce);
1188 undoManager.addEdit(ce);
1193 actions.put("togglePreview", new BaseAction() {
1194 public void action() {
1195 boolean enabled = !Globals.prefs.getBoolean("previewEnabled");
1196 Globals.prefs.putBoolean("previewEnabled", enabled);
1197 frame.setPreviewActive(enabled);
1198 frame.previewToggle.setSelected(enabled);
1202 actions.put("toggleHighlightGroupsMatchingAny", new BaseAction() {
1203 public void action() {
1204 boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAny");
1205 Globals.prefs.putBoolean("highlightGroupsMatchingAny", enabled);
1206 frame.highlightAny.setSelected(enabled);
1208 frame.highlightAll.setSelected(false);
1209 Globals.prefs.putBoolean("highlightGroupsMatchingAll", false);
1211 // ping the listener so it updates:
1212 groupsHighlightListener.listChanged(null);
1216 actions.put("toggleHighlightGroupsMatchingAll", new BaseAction() {
1217 public void action() {
1218 boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAll");
1219 Globals.prefs.putBoolean("highlightGroupsMatchingAll", enabled);
1220 frame.highlightAll.setSelected(enabled);
1222 frame.highlightAny.setSelected(false);
1223 Globals.prefs.putBoolean("highlightGroupsMatchingAny", false);
1225 // ping the listener so it updates:
1226 groupsHighlightListener.listChanged(null);
1230 actions.put("switchPreview", new BaseAction() {
1231 public void action() {
1232 selectionListener.switchPreview();
1236 actions.put("manageSelectors", new BaseAction() {
1237 public void action() {
1238 ContentSelectorDialog2 csd = new ContentSelectorDialog2
1239 (frame, frame, BasePanel.this, false, metaData, null);
1240 Util.placeDialog(csd, frame);
1241 csd.setVisible(true);
1246 actions.put("exportToClipboard", new AbstractWorker() {
1247 String message = null;
1249 if (mainTable.getSelected().size() == 0) {
1250 message = Globals.lang("No entries selected")+".";
1251 getCallBack().update();
1255 // Make a list of possible formats:
1256 Map formats = new HashMap();
1257 formats.put("BibTeXML", "bibtexml");
1258 formats.put("DocBook", "docbook");
1259 formats.put("HTML", "html");
1260 formats.put("RTF (Harvard)", "harvard/harvard");
1261 formats.put("Simple HTML", "simplehtml");
1262 for (int i = 0; i < Globals.prefs.customExports.size(); i++) {
1263 Object o = (Globals.prefs.customExports.getElementAt(i))[0];
1266 Object[] array = formats.keySet().toArray();
1268 JList list = new JList(array);
1269 list.setBorder(BorderFactory.createEtchedBorder());
1270 list.setSelectionInterval(0,0);
1271 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
1273 JOptionPane.showOptionDialog(frame, list, Globals.lang("Select format"),
1274 JOptionPane.YES_NO_OPTION,
1275 JOptionPane.QUESTION_MESSAGE, null,
1276 new String[] {Globals.lang("Ok"), Globals.lang("Cancel")},
1277 Globals.lang("Ok"));
1279 if (answer == JOptionPane.NO_OPTION)
1282 String lfName = (String)(formats.get(list.getSelectedValue()));
1283 final boolean custom = (list.getSelectedIndex() >= Globals.STANDARD_EXPORT_COUNT);
1286 int index = list.getSelectedIndex()-Globals.STANDARD_EXPORT_COUNT;
1287 dir = (String)(Globals.prefs.customExports.getElementAt(index)[1]);
1288 File f = new File(dir);
1289 lfName = f.getName();
1290 lfName = lfName.substring(0, lfName.indexOf("."));
1291 // Remove file name - we want the directory only.
1292 dir = f.getParent()+System.getProperty("file.separator");
1294 final String format = lfName,
1298 BibtexEntry[] bes = mainTable.getSelectedEntries();
1299 StringWriter sw = new StringWriter();
1300 FileActions.exportEntries(database, bes, format, custom, directory, sw);
1301 ClipboardOwner owner = new ClipboardOwner() {
1302 public void lostOwnership(Clipboard clipboard, Transferable content) {}
1304 //StringSelection ss = new StringSelection(sw.toString());
1305 RtfSelection rs = new RtfSelection(sw.toString());
1306 Toolkit.getDefaultToolkit().getSystemClipboard()
1307 .setContents(rs, owner);
1308 message = Globals.lang("Entries exported to clipboard")+": "+bes.length;
1309 } catch (Exception ex) {
1310 ex.printStackTrace();
1314 public void update() {
1322 actions.put("abbreviateIso", new AbbreviateAction(this, true));
1323 actions.put("abbreviateMedline", new AbbreviateAction(this, false));
1324 actions.put("unabbreviate", new UnabbreviateAction(this));
1325 actions.put("autoSetPdf", new AutoSetExternalFileForEntries(this, "pdf"));
1326 actions.put("autoSetPs", new AutoSetExternalFileForEntries(this, "ps"));
1331 * This method is called from JabRefFrame is a database specific
1332 * action is requested by the user. Runs the command if it is
1333 * defined, or prints an error message to the standard error
1336 * @param _command The name of the command to run.
1338 public void runCommand(String _command) {
1339 final String command = _command;
1341 // public void run() {
1342 if (actions.get(command) == null)
1343 Util.pr("No action defined for'" + command + "'");
1345 Object o = actions.get(command);
1347 if (o instanceof BaseAction)
1348 ((BaseAction)o).action();
1350 // This part uses Spin's features:
1351 Worker wrk = ((AbstractWorker)o).getWorker();
1352 // The Worker returned by getWorker() has been wrapped
1353 // by Spin.off(), which makes its methods be run in
1354 // a different thread from the EDT.
1355 CallBack clb = ((AbstractWorker)o).getCallBack();
1357 ((AbstractWorker)o).init(); // This method runs in this same thread, the EDT.
1358 // Useful for initial GUI actions, like printing a message.
1360 // The CallBack returned by getCallBack() has been wrapped
1361 // by Spin.over(), which makes its methods be run on
1363 wrk.run(); // Runs the potentially time-consuming action
1364 // without freezing the GUI. The magic is that THIS line
1365 // of execution will not continue until run() is finished.
1366 clb.update(); // Runs the update() method on the EDT.
1368 } catch (Throwable ex) {
1369 // If the action has blocked the JabRefFrame before crashing, we need to unblock it.
1370 // The call to unblock will simply hide the glasspane, so there is no harm in calling
1371 // it even if the frame hasn't been blocked.
1373 ex.printStackTrace();
1380 private boolean saveDatabase(File file, boolean selectedOnly, String encoding) throws SaveException {
1381 SaveSession session;
1385 session = FileActions.saveDatabase(database, metaData, file,
1386 Globals.prefs, false, false, encoding);
1388 session = FileActions.savePartOfDatabase(database, metaData, file,
1389 Globals.prefs, mainTable.getSelectedEntries(), encoding);
1391 } catch (UnsupportedCharsetException ex2) {
1392 JOptionPane.showMessageDialog(frame, Globals.lang("Could not save file. "
1393 +"Character encoding '%0' is not supported.", encoding),
1394 Globals.lang("Save database"), JOptionPane.ERROR_MESSAGE);
1395 throw new SaveException("rt");
1396 } catch (SaveException ex) {
1397 if (ex.specificEntry()) {
1398 // Error occured during processing of
1399 // be. Highlight it:
1400 int row = mainTable.findEntry(ex.getEntry()),
1401 topShow = Math.max(0, row-3);
1402 mainTable.setRowSelectionInterval(row, row);
1403 mainTable.scrollTo(topShow);
1404 showEntry(ex.getEntry());
1406 else ex.printStackTrace();
1408 JOptionPane.showMessageDialog
1409 (frame, Globals.lang("Could not save file")
1410 +".\n"+ex.getMessage(),
1411 Globals.lang("Save database"),
1412 JOptionPane.ERROR_MESSAGE);
1413 throw new SaveException("rt");
1419 boolean commit = true;
1420 if (!session.getWriter().couldEncodeAll()) {
1421 DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("left:pref, 4dlu, fill:pref", ""));
1422 JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters());
1423 ta.setEditable(false);
1424 builder.append(Globals.lang("The chosen encoding '%0' could not encode the following characters: ",
1425 session.getEncoding()));
1427 builder.append(Globals.lang("What do you want to do?"));
1428 String tryDiff = Globals.lang("Try different encoding");
1429 int answer = JOptionPane.showOptionDialog(frame, builder.getPanel(), Globals.lang("Save database"),
1430 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
1431 new String[] {Globals.lang("Save"), tryDiff, Globals.lang("Cancel")}, tryDiff);
1433 if (answer == JOptionPane.NO_OPTION) {
1434 // The user wants to use another encoding.
1435 Object choice = JOptionPane.showInputDialog(frame, Globals.lang("Select encoding"), Globals.lang("Save database"),
1436 JOptionPane.QUESTION_MESSAGE, null, Globals.ENCODINGS, encoding);
1437 if (choice != null) {
1438 String newEncoding = (String)choice;
1439 return saveDatabase(file, selectedOnly, newEncoding);
1442 } else if (answer == JOptionPane.CANCEL_OPTION)
1451 this.encoding = encoding; // Make sure to remember which encoding we used.
1455 } catch (IOException e) {
1456 e.printStackTrace();
1464 * This method is called from JabRefFrame when the user wants to
1465 * create a new entry. If the argument is null, the user is
1466 * prompted for an entry type.
1468 * @param type The type of the entry to create.
1470 public void newEntry(BibtexEntryType type) {
1472 // Find out what type is wanted.
1473 EntryTypeDialog etd = new EntryTypeDialog(frame);
1474 // We want to center the dialog, to make it look nicer.
1475 Util.placeDialog(etd, frame);
1476 etd.setVisible(true);
1477 type = etd.getChoice();
1479 if (type != null) { // Only if the dialog was not cancelled.
1480 String id = Util.createNeutralId();
1481 final BibtexEntry be = new BibtexEntry(id, type);
1483 database.insertEntry(be);
1485 // Set owner/timestamp if options are enabled:
1486 ArrayList list = new ArrayList();
1488 Util.setAutomaticFields(list);
1490 // Create an UndoableInsertEntry object.
1491 undoManager.addEdit(new UndoableInsertEntry(database, be, BasePanel.this));
1492 output(Globals.lang("Added new")+" '"+type.getName().toLowerCase()+"' "
1493 +Globals.lang("entry")+".");
1494 final int row = mainTable.findEntry(be);
1496 // We are going to select the new entry. Before that, make sure that we are in
1497 // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR
1498 // mode which makes sure the selection will trigger display of the entry editor
1499 // and adjustment of the splitter.
1500 if (mode != SHOWING_EDITOR) {
1501 mode = WILL_SHOW_EDITOR;
1504 highlightEntry(be); // Selects the entry. The selection listener will open the editor.
1506 markBaseChanged(); // The database just changed.
1507 new FocusRequester(getEntryEditor(be));
1508 } catch (KeyCollisionException ex) {
1509 Util.pr(ex.getMessage());
1517 * This method is called from JabRefFrame when the user wants to
1518 * create a new entry.
1519 * @param bibEntry The new entry.
1521 public void insertEntry(BibtexEntry bibEntry)
1523 if (bibEntry != null)
1527 database.insertEntry(bibEntry) ;
1528 if (Globals.prefs.getBoolean("useOwner"))
1529 // Set owner field to default value
1530 bibEntry.setField(BibtexFields.OWNER, Globals.prefs.get("defaultOwner") );
1531 // Create an UndoableInsertEntry object.
1532 undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1533 output(Globals.lang("Added new")+" '"
1534 +bibEntry.getType().getName().toLowerCase()+"' "
1535 +Globals.lang("entry")+".");
1536 int row = mainTable.findEntry(bibEntry);
1538 mainTable.clearSelection();
1539 mainTable.scrollTo(row);
1540 markBaseChanged(); // The database just changed.
1541 if (Globals.prefs.getBoolean("autoOpenForm"))
1543 showEntry(bibEntry);
1545 } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); }
1549 public void createMainTable() {
1550 //Comparator comp = new FieldComparator("author");
1552 GlazedEntrySorter eventList = new GlazedEntrySorter(database.getEntryMap(), null);
1553 // Must initialize sort columns somehow:
1555 database.addDatabaseChangeListener(eventList);
1556 groupFilterList = new FilterList(eventList.getTheList(), NoSearchMatcher.INSTANCE);
1557 searchFilterList = new FilterList(groupFilterList, NoSearchMatcher.INSTANCE);
1558 //final SortedList sortedList = new SortedList(searchFilterList, null);
1559 MainTableFormat tableFormat = new MainTableFormat(this);
1560 tableFormat.updateTableFormat();
1561 //EventTableModel tableModel = new EventTableModel(sortedList, tableFormat);
1562 mainTable = new MainTable(/*tableModel, */tableFormat, searchFilterList, frame);
1564 selectionListener = new MainTableSelectionListener(this, mainTable);
1565 mainTable.updateFont();
1566 mainTable.addSelectionListener(selectionListener);
1567 mainTable.addMouseListener(selectionListener);
1569 // Add the listener that will take care of highlighting groups as the selection changes:
1570 groupsHighlightListener = new ListEventListener() {
1571 public void listChanged(ListEvent listEvent) {
1572 if (Globals.prefs.getBoolean("highlightGroupsMatchingAny"))
1573 getGroupSelector().showMatchingGroups(
1574 mainTable.getSelectedEntries(), false);
1575 else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll"))
1576 getGroupSelector().showMatchingGroups(
1577 mainTable.getSelectedEntries(), true);
1578 else // no highlight
1579 getGroupSelector().showMatchingGroups(null, true);
1582 mainTable.addSelectionListener(groupsHighlightListener);
1584 mainTable.getActionMap().put("cut", new AbstractAction() {
1585 public void actionPerformed(ActionEvent e) {
1586 try { runCommand("cut");
1587 } catch (Throwable ex) {
1588 ex.printStackTrace();
1592 mainTable.getActionMap().put("copy", new AbstractAction() {
1593 public void actionPerformed(ActionEvent e) {
1594 try { runCommand("copy");
1595 } catch (Throwable ex) {
1596 ex.printStackTrace();
1600 mainTable.getActionMap().put("paste", new AbstractAction() {
1601 public void actionPerformed(ActionEvent e) {
1602 try { runCommand("paste");
1603 } catch (Throwable ex) {
1604 ex.printStackTrace();
1609 mainTable.addKeyListener(new KeyAdapter() {
1611 public void keyPressed(KeyEvent e) {
1612 final int keyCode = e.getKeyCode();
1613 final TreePath path = frame.groupSelector.getSelectionPath();
1614 final GroupTreeNode node = path == null ? null : (GroupTreeNode) path.getLastPathComponent();
1616 if (e.isControlDown()) {
1618 // The up/down/left/rightkeystrokes are displayed in the
1619 // GroupSelector's popup menu, so if they are to be changed,
1620 // edit GroupSelector.java accordingly!
1621 case KeyEvent.VK_UP:
1624 frame.groupSelector.moveNodeUp(node, true);
1626 case KeyEvent.VK_DOWN:
1629 frame.groupSelector.moveNodeDown(node, true);
1631 case KeyEvent.VK_LEFT:
1634 frame.groupSelector.moveNodeLeft(node, true);
1636 case KeyEvent.VK_RIGHT:
1639 frame.groupSelector.moveNodeRight(node, true);
1641 case KeyEvent.VK_PAGE_DOWN:
1642 frame.nextTab.actionPerformed(null);
1645 case KeyEvent.VK_PAGE_UP:
1646 frame.prevTab.actionPerformed(null);
1650 } else if (keyCode == KeyEvent.VK_ENTER){
1652 try { runCommand("edit");
1653 } catch (Throwable ex) {
1654 ex.printStackTrace();
1661 public void setupMainPanel() {
1662 //System.out.println("setupMainPanel");
1663 //splitPane = new com.jgoodies.uif_lite.component.UIFSplitPane(JSplitPane.VERTICAL_SPLIT);
1664 splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
1665 splitPane.setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
1666 // We replace the default FocusTraversalPolicy with a subclass
1667 // that only allows FieldEditor components to gain keyboard focus,
1668 // if there is an entry editor open.
1669 /*splitPane.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
1670 protected boolean accept(Component c) {
1672 if (showing == null)
1673 return super.accept(c);
1675 return (super.accept(c) &&
1676 (c instanceof FieldEditor));
1682 splitPane.setTopComponent(mainTable.getPane());
1685 // If an entry is currently being shown, make sure it stays shown,
1686 // otherwise set the bottom component to null.
1687 if (mode == SHOWING_PREVIEW) {
1688 mode = SHOWING_NOTHING;
1689 int row = mainTable.findEntry(currentPreview.entry);
1691 mainTable.setRowSelectionInterval(row, row);
1694 else if (mode == SHOWING_EDITOR) {
1695 mode = SHOWING_NOTHING;
1696 /*int row = mainTable.findEntry(currentEditor.entry);
1698 mainTable.setRowSelectionInterval(row, row);
1700 //showEntryEditor(currentEditor);
1702 splitPane.setBottomComponent(null);
1705 setLayout(new BorderLayout());
1707 add(splitPane, BorderLayout.CENTER);
1708 //add(contentPane, BorderLayout.CENTER);
1710 //add(sidePaneManager.getPanel(), BorderLayout.WEST);
1711 //add(splitPane, BorderLayout.CENTER);
1714 //con.fill = GridBagConstraints.BOTH;
1717 //gbl.setConstraints(sidePaneManager.getPanel(), con);
1719 //gbl.setConstraints(splitPane, con);
1720 //mainPanel.setDividerLocation(GUIGlobals.SPLIT_PANE_DIVIDER_LOCATION);
1721 //setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
1722 //setResizeWeight(0);
1723 splitPane.revalidate();
1730 * This method is called after a database has been parsed. The
1731 * hashmap contains the contents of all comments in the .bib file
1732 * that started with the meta flag (GUIGlobals.META_FLAG).
1733 * In this method, the meta data are input to their respective
1736 * @param meta Metadata to input.
1738 public void parseMetaData(HashMap meta) {
1739 metaData = new MetaData(meta,database());
1744 public void refreshTable() {
1745 //System.out.println("hiding="+hidingNonHits+"\tlastHits="+lastSearchHits);
1746 // This method is called by EntryTypeForm when a field value is
1747 // stored. The table is scheduled for repaint.
1748 entryTable.assureNotEditing();
1749 //entryTable.invalidate();
1750 BibtexEntry[] bes = entryTable.getSelectedEntries();
1752 tableModel.update(lastSearchHits);
1754 tableModel.update();
1755 //tableModel.remap();
1756 if ((bes != null) && (bes.length > 0))
1757 selectEntries(bes, 0);
1759 //long toc = System.currentTimeMillis();
1760 // Util.pr("Refresh took: "+(toc-tic)+" ms");
1763 public void updatePreamble() {
1764 if (preambleEditor != null)
1765 preambleEditor.updatePreamble();
1768 public void assureStringDialogNotEditing() {
1769 if (stringDialog != null)
1770 stringDialog.assureNotEditing();
1773 public void updateStringDialog() {
1774 if (stringDialog != null)
1775 stringDialog.refreshTable();
1778 public void updateEntryPreviewToRow(BibtexEntry e) {
1782 public void adjustSplitter() {
1783 int mode = getMode();
1784 if (mode == SHOWING_PREVIEW) {
1785 splitPane.setDividerLocation(splitPane.getHeight()-GUIGlobals.PREVIEW_PANEL_HEIGHT);
1787 splitPane.setDividerLocation(GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1795 * Stores the source view in the entry editor, if one is open, has the source view
1796 * selected and the source has been edited.
1797 * @return boolean false if there is a validation error in the source panel, true otherwise.
1799 public boolean entryEditorAllowsChange() {
1800 Component c = splitPane.getBottomComponent();
1801 if ((c != null) && (c instanceof EntryEditor)) {
1802 return ((EntryEditor)c).lastSourceAccepted();
1808 public void moveFocusToEntryEditor() {
1809 Component c = splitPane.getBottomComponent();
1810 if ((c != null) && (c instanceof EntryEditor)) {
1811 new FocusRequester(c);
1816 * Ensure that no preview is shown. Called when preview is turned off. Must chech if
1817 * a preview is in fact visible before doing anything rash.
1819 public void hidePreview() {
1820 Globals.prefs.putBoolean("previewEnabled", false);
1822 Component c = splitPane.getBottomComponent();
1823 if ((c != null) && !(c instanceof EntryEditor))
1824 splitPane.setBottomComponent(null);
1827 public boolean isShowingEditor() {
1828 return ((splitPane.getBottomComponent() != null)
1829 && (splitPane.getBottomComponent() instanceof EntryEditor));
1832 public void showEntry(final BibtexEntry be) {
1833 if (showing == be) {
1834 if (splitPane.getBottomComponent() == null) {
1835 // This is the special occasion when showing is set to an
1836 // entry, but no entry editor is in fact shown. This happens
1837 // after Preferences dialog is closed, and it means that we
1838 // must make sure the same entry is shown again. We do this by
1839 // setting showing to null, and recursively calling this method.
1843 // The correct entry is already being shown. Make sure the editor
1845 ((EntryEditor)splitPane.getBottomComponent()).updateAllFields();
1854 String visName = null;
1855 if (showing != null) {
1856 visName = ((EntryEditor)splitPane.getBottomComponent()).
1857 getVisiblePanelName();
1859 if (showing != null)
1860 divLoc = splitPane.getDividerLocation();
1862 if (entryEditors.containsKey(be.getType().getName())) {
1863 // We already have an editor for this entry type.
1864 form = (EntryEditor)entryEditors.get
1865 ((be.getType().getName()));
1867 if (visName != null)
1868 form.setVisiblePanel(visName);
1869 splitPane.setBottomComponent(form);
1870 //highlightEntry(be);
1872 // We must instantiate a new editor for this type.
1873 form = new EntryEditor(frame, BasePanel.this, be);
1874 if (visName != null)
1875 form.setVisiblePanel(visName);
1876 splitPane.setBottomComponent(form);
1878 //highlightEntry(be);
1879 entryEditors.put(be.getType().getName(), form);
1883 splitPane.setDividerLocation(divLoc);
1886 splitPane.setDividerLocation
1887 (GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1888 //new FocusRequester(form);
1889 //form.requestFocus();
1892 setEntryEditorEnabled(true); // Make sure it is enabled.
1896 * Get an entry editor ready to edit the given entry. If an appropriate editor is already
1897 * cached, it will be updated and returned.
1898 * @param entry The entry to be edited.
1899 * @return A suitable entry editor.
1901 public EntryEditor getEntryEditor(BibtexEntry entry) {
1903 if (entryEditors.containsKey(entry.getType().getName())) {
1904 EntryEditor visibleNow = currentEditor;
1905 // We already have an editor for this entry type.
1906 form = (EntryEditor)entryEditors.get
1907 ((entry.getType().getName()));
1909 form.switchTo(entry);
1910 //if (visName != null)
1911 // form.setVisiblePanel(visName);
1913 // We must instantiate a new editor for this type.
1914 form = new EntryEditor(frame, BasePanel.this, entry);
1915 //if (visName != null)
1916 // form.setVisiblePanel(visName);
1918 entryEditors.put(entry.getType().getName(), form);
1923 public EntryEditor getCurrentEditor() {
1924 return currentEditor;
1928 * Sets the given entry editor as the bottom component in the split pane. If an entry editor already
1929 * was shown, makes sure that the divider doesn't move.
1930 * Updates the mode to SHOWING_EDITOR.
1931 * @param editor The entry editor to add.
1933 public void showEntryEditor(EntryEditor editor) {
1934 int oldSplitterLocation = -1;
1935 if (mode == SHOWING_EDITOR)
1936 oldSplitterLocation = splitPane.getDividerLocation();
1937 boolean adjustSplitter = (mode == WILL_SHOW_EDITOR);
1938 mode = SHOWING_EDITOR;
1939 currentEditor = editor;
1940 splitPane.setBottomComponent(editor);
1941 if (oldSplitterLocation > 0)
1942 splitPane.setDividerLocation(oldSplitterLocation);
1943 if (adjustSplitter) {
1945 //new FocusRequester(editor);
1950 * Sets the given preview panel as the bottom component in the split panel.
1951 * Updates the mode to SHOWING_PREVIEW.
1952 * @param preview The preview to show.
1954 public void showPreview(PreviewPanel preview) {
1955 mode = SHOWING_PREVIEW;
1956 currentPreview = preview;
1957 splitPane.setBottomComponent(preview.getPane());
1961 * Removes the bottom component.
1963 public void hideBottomComponent() {
1964 mode = SHOWING_NOTHING;
1965 splitPane.setBottomComponent(null);
1969 * This method selects the given entry, and scrolls it into view in the table.
1970 * If an entryEditor is shown, it is given focus afterwards.
1972 public void highlightEntry(final BibtexEntry be) {
1973 //SwingUtilities.invokeLater(new Thread() {
1974 // public void run() {
1975 final int row = mainTable.findEntry(be);
1977 mainTable.setRowSelectionInterval(row, row);
1978 //entryTable.setActiveRow(row);
1979 mainTable.ensureVisible(row);
1987 * This method is called from an EntryEditor when it should be closed. We relay
1988 * to the selection listener, which takes care of the rest.
1989 * @param editor The entry editor to close.
1991 public void entryEditorClosing(EntryEditor editor) {
1992 selectionListener.entryEditorClosing(editor);
1996 * This method selects the given enties.
1997 * If an entryEditor is shown, it is given focus afterwards.
1999 /*public void selectEntries(final BibtexEntry[] bes, final int toScrollTo) {
2001 SwingUtilities.invokeLater(new Thread() {
2003 int rowToScrollTo = 0;
2004 entryTable.revalidate();
2005 entryTable.clearSelection();
2006 loop: for (int i=0; i<bes.length; i++) {
2009 int row = tableModel.getNumberFromName(bes[i].getId());
2011 rowToScrollTo = row;
2013 entryTable.addRowSelectionIntervalQuietly(row, row);
2015 entryTable.ensureVisible(rowToScrollTo);
2016 Component comp = splitPane.getBottomComponent();
2017 //if (comp instanceof EntryEditor)
2018 // comp.requestFocus();
2024 * Closes the entry editor if it is showing the given entry.
2026 * @param be a <code>BibtexEntry</code> value
2028 public void ensureNotShowing(BibtexEntry be) {
2029 if ((mode == SHOWING_EDITOR) && (currentEditor.getEntry() == be)) {
2030 selectionListener.entryEditorClosing(currentEditor);
2034 public void updateEntryEditorIfShowing() {
2035 if (mode == SHOWING_EDITOR) {
2036 if (currentEditor.getType() != currentEditor.getEntry().getType()) {
2037 // The entry has changed type, so we must get a new editor.
2039 EntryEditor newEditor = getEntryEditor(currentEditor.getEntry());
2040 showEntryEditor(newEditor);
2042 currentEditor.updateAllFields();
2043 currentEditor.updateSource();
2049 * If an entry editor is showing, make sure its currently focused field
2050 * stores its changes, if any.
2052 public void storeCurrentEdit() {
2053 if (isShowingEditor()) {
2054 EntryEditor editor = (EntryEditor)splitPane.getBottomComponent();
2055 editor.storeCurrentEdit();
2061 * This method iterates through all existing entry editors in this
2062 * BasePanel, telling each to update all its instances of
2063 * FieldContentSelector. This is done to ensure that the list of words
2064 * in each selector is up-to-date after the user has made changes in
2065 * the Manage dialog.
2067 public void updateAllContentSelectors() {
2068 for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2069 EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2070 ed.updateAllContentSelectors();
2074 public void rebuildAllEntryEditors() {
2075 for (Iterator i=entryEditors.keySet().iterator(); i.hasNext();) {
2076 EntryEditor ed = (EntryEditor)entryEditors.get(i.next());
2082 public void markBaseChanged() {
2085 // Put an asterix behind the file name to indicate the
2086 // database has changed.
2087 String oldTitle = frame.getTabTitle(this);
2088 if (!oldTitle.endsWith("*"))
2089 frame.setTabTitle(this, oldTitle+"*");
2091 // If the status line states that the base has been saved, we
2092 // remove this message, since it is no longer relevant. If a
2093 // different message is shown, we leave it.
2094 if (frame.statusLine.getText().startsWith("Saved database"))
2098 public void markNonUndoableBaseChanged() {
2099 nonUndoableChange = true;
2103 public synchronized void markChangedOrUnChanged() {
2104 if (undoManager.hasChanged()) {
2108 else if (baseChanged && !nonUndoableChange) {
2109 baseChanged = false;
2111 frame.setTabTitle(BasePanel.this, file.getName());
2113 frame.setTabTitle(BasePanel.this, Globals.lang("untitled"));
2118 * Selects a single entry, and scrolls the table to center it.
2120 * @param pos Current position of entry to select.
2123 public void selectSingleEntry(int pos) {
2124 mainTable.clearSelection();
2125 mainTable.addRowSelectionInterval(pos, pos);
2126 mainTable.scrollToCenter(pos, 0);
2130 * Selects all entries with a non-zero value in the field
2131 * @param field <code>String</code> field name.
2133 /* public void selectResults(String field) {
2134 LinkedList intervals = new LinkedList();
2135 int prevStart = -1, prevToSel = 0;
2136 // First we build a list of intervals to select, without touching the table.
2137 for (int i = 0; i < entryTable.getRowCount(); i++) {
2138 String value = (String) (database.getEntryById
2139 (tableModel.getIdForRow(i)))
2141 if ( (value != null) && !value.equals("0")) {
2146 else if (prevStart >= 0) {
2147 intervals.add(new int[] {prevStart, prevToSel});
2151 // Then select those intervals, if any.
2152 if (intervals.size() > 0) {
2153 entryTable.setSelectionListenerEnabled(false);
2154 entryTable.clearSelection();
2155 for (Iterator i=intervals.iterator(); i.hasNext();) {
2156 int[] interval = (int[])i.next();
2157 entryTable.addRowSelectionInterval(interval[0], interval[1]);
2159 entryTable.setSelectionListenerEnabled(true);
2163 public void setSearchMatcher(SearchMatcher matcher) {
2164 searchFilterList.setMatcher(matcher);
2167 public void setGroupMatcher(Matcher matcher) {
2168 groupFilterList.setMatcher(matcher);
2171 public void stopShowingSearchResults() {
2172 searchFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2175 public void stopShowingGroup() {
2176 groupFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2180 public BibtexDatabase getDatabase(){
2184 public void preambleEditorClosing() {
2185 preambleEditor = null;
2188 public void stringsClosing() {
2189 stringDialog = null;
2192 public void changeType(BibtexEntry entry, BibtexEntryType type) {
2193 changeType(new BibtexEntry[] {entry}, type);
2196 public void changeType(BibtexEntryType type) {
2197 BibtexEntry[] bes = mainTable.getSelectedEntries();
2198 changeType(bes, type);
2201 public void changeType(BibtexEntry[] bes, BibtexEntryType type) {
2203 if ((bes == null) || (bes.length == 0)) {
2204 output("First select the entries you wish to change type "+
2208 if (bes.length > 1) {
2209 int choice = JOptionPane.showConfirmDialog
2210 (this, "Multiple entries selected. Do you want to change"
2211 +"\nthe type of all these to '"+type.getName()+"'?",
2212 "Change type", JOptionPane.YES_NO_OPTION,
2213 JOptionPane.WARNING_MESSAGE);
2214 if (choice == JOptionPane.NO_OPTION)
2218 NamedCompound ce = new NamedCompound(Globals.lang("change type"));
2219 for (int i=0; i<bes.length; i++) {
2220 ce.addEdit(new UndoableChangeType(bes[i],
2223 bes[i].setType(type);
2226 output(Globals.lang("Changed type to")+" '"+type.getName()+"' "
2227 +Globals.lang("for")+" "+bes.length
2228 +" "+Globals.lang("entries")+".");
2230 undoManager.addEdit(ce);
2232 updateEntryEditorIfShowing();
2235 public boolean showDeleteConfirmationDialog(int numberOfEntries) {
2236 if (Globals.prefs.getBoolean("confirmDelete")) {
2237 String msg = Globals.lang("Really delete the selected")
2238 + " " + Globals.lang("entry") + "?",
2239 title = Globals.lang("Delete entry");
2240 if (numberOfEntries > 1) {
2241 msg = Globals.lang("Really delete the selected")
2242 + " " + numberOfEntries + " " + Globals.lang("entries") + "?";
2243 title = Globals.lang("Delete multiple entries");
2246 CheckBoxMessage cb = new CheckBoxMessage
2247 (msg, Globals.lang("Disable this confirmation dialog"), false);
2249 int answer = JOptionPane.showConfirmDialog(frame, cb, title,
2250 JOptionPane.YES_NO_OPTION,
2251 JOptionPane.QUESTION_MESSAGE);
2252 if (cb.isSelected())
2253 Globals.prefs.putBoolean("confirmDelete", false);
2254 return (answer == JOptionPane.YES_OPTION);
2260 * Activates or deactivates the entry preview, depending on the argument.
2261 * When deactivating, makes sure that any visible preview is hidden.
2264 public void setPreviewActive(boolean enabled) {
2265 selectionListener.setPreviewActive(enabled);
2269 class UndoAction extends BaseAction {
2270 public void action() {
2272 String name = undoManager.getUndoPresentationName();
2276 } catch (CannotUndoException ex) {
2277 frame.output(Globals.lang("Nothing to undo")+".");
2279 // After everything, enable/disable the undo/redo actions
2281 //updateUndoState();
2282 //redoAction.updateRedoState();
2283 markChangedOrUnChanged();
2287 class RedoAction extends BaseAction {
2288 public void action() {
2290 String name = undoManager.getRedoPresentationName();
2294 } catch (CannotRedoException ex) {
2295 frame.output(Globals.lang("Nothing to redo")+".");
2297 // After everything, enable/disable the undo/redo actions
2299 //updateRedoState();
2300 //undoAction.updateUndoState();
2301 markChangedOrUnChanged();
2305 // Method pertaining to the ClipboardOwner interface.
2306 public void lostOwnership(Clipboard clipboard, Transferable contents) {}
2309 public void setEntryEditorEnabled(boolean enabled) {
2310 if ((showing != null) && (splitPane.getBottomComponent() instanceof EntryEditor)) {
2311 EntryEditor ed = (EntryEditor)splitPane.getBottomComponent();
2312 if (ed.isEnabled() != enabled)
2313 ed.setEnabled(enabled);
2317 public String fileMonitorHandle() { return fileMonitorHandle; }
2319 public void fileUpdated() {
2321 return; // We are just saving the file, so this message is most likely due
2322 // to bad timing. If not, we'll handle it on the next polling.
2323 //Util.pr("File '"+file.getPath()+"' has been modified.");
2324 updatedExternally = true;
2326 // Adding the sidepane component is Swing work, so we must do this in the Swing
2328 Thread t = new Thread() {
2330 // Test: running scan automatically in background
2331 ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this);
2332 scanner.changeScan(BasePanel.this.file());
2335 } catch (InterruptedException e) {
2336 e.printStackTrace();
2339 if (scanner.changesFound()) {
2340 FileUpdatePanel pan = new FileUpdatePanel(frame, BasePanel.this, sidePaneManager, file, scanner);
2341 sidePaneManager.add("fileUpdate", pan);
2342 setUpdatedExternally(false);
2343 //scanner.displayResult();
2345 setUpdatedExternally(false);
2346 //System.out.println("No changes found.");
2351 SwingUtilities.invokeLater(t);
2355 public void fileRemoved() {
2356 Util.pr("File '"+file.getPath()+"' has been deleted.");
2360 public void cleanUp() {
2361 if (fileMonitorHandle != null)
2362 Globals.fileUpdateMonitor.removeUpdateListener(fileMonitorHandle);
2365 public void setUpdatedExternally(boolean b) {
2366 updatedExternally = b;
2370 * Get an array containing the currently selected entries.
2372 * @return An array containing the selected entries.
2374 public BibtexEntry[] getSelectedEntries() {
2375 return mainTable.getSelectedEntries();
2379 * Get a String containing a comma-separated list of the bibtex keys
2380 * of the selected entries.
2382 * @return A comma-separated list of the keys of the selected entries.
2384 public String getKeysForSelection() {
2385 List entries = mainTable.getSelected();
2386 StringBuffer result = new StringBuffer();
2387 String citeKey = "";//, message = "";
2388 boolean first = true;
2389 for (Iterator i = entries.iterator(); i.hasNext();) {
2390 BibtexEntry bes = (BibtexEntry) i.next();
2391 citeKey = (String) bes.getField(BibtexFields.KEY_FIELD);
2392 // if the key is empty we give a warning and ignore this entry
2393 if (citeKey == null || citeKey.equals(""))
2396 result.append(citeKey);
2399 result.append(",").append(citeKey);
2402 return result.toString();
2405 public GroupSelector getGroupSelector() {
2406 return frame.groupSelector;