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 ca.odell.glazedlists.FilterList;
32 import ca.odell.glazedlists.event.ListEvent;
33 import ca.odell.glazedlists.event.ListEventListener;
34 import ca.odell.glazedlists.matchers.Matcher;
35 import com.jgoodies.forms.builder.DefaultFormBuilder;
36 import com.jgoodies.forms.layout.FormLayout;
37 import com.jgoodies.uif_lite.component.UIFSplitPane;
38 import net.sf.jabref.collab.ChangeScanner;
39 import net.sf.jabref.collab.FileUpdateListener;
40 import net.sf.jabref.collab.FileUpdatePanel;
41 import net.sf.jabref.export.*;
42 import net.sf.jabref.external.*;
43 import net.sf.jabref.groups.GroupSelector;
44 import net.sf.jabref.groups.GroupTreeNode;
45 import net.sf.jabref.gui.*;
46 import net.sf.jabref.imports.AppendDatabaseAction;
47 import net.sf.jabref.imports.BibtexParser;
48 import net.sf.jabref.imports.SPIRESFetcher;
49 import net.sf.jabref.journals.AbbreviateAction;
50 import net.sf.jabref.journals.UnabbreviateAction;
51 import net.sf.jabref.labelPattern.LabelPatternUtil;
52 import net.sf.jabref.search.NoSearchMatcher;
53 import net.sf.jabref.search.SearchMatcher;
54 import net.sf.jabref.sql.DBConnectDialog;
55 import net.sf.jabref.sql.DBStrings;
56 import net.sf.jabref.sql.SQLutil;
57 import net.sf.jabref.undo.*;
58 import net.sf.jabref.wizard.text.gui.TextInputDialog;
61 import javax.swing.tree.TreePath;
62 import javax.swing.undo.CannotRedoException;
63 import javax.swing.undo.CannotUndoException;
65 import java.awt.datatransfer.*;
66 import java.awt.event.ActionEvent;
67 import java.awt.event.KeyAdapter;
68 import java.awt.event.KeyEvent;
70 import java.io.IOException;
71 import java.nio.charset.UnsupportedCharsetException;
72 import java.sql.Connection;
74 import java.util.List;
76 public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener {
78 public final static int SHOWING_NOTHING=0, SHOWING_PREVIEW=1, SHOWING_EDITOR=2, WILL_SHOW_EDITOR=3;
81 * The database shown in this panel.
83 BibtexDatabase database;
86 private EntryEditor currentEditor = null;
87 private PreviewPanel currentPreview = null;
91 private MainTableSelectionListener selectionListener = null;
92 private ListEventListener<BibtexEntry> groupsHighlightListener;
93 UIFSplitPane contentPane = new UIFSplitPane();
99 String fileMonitorHandle = null;
100 boolean saving = false, updatedExternally = false;
101 private String encoding;
103 GridBagLayout gbl = new GridBagLayout();
104 GridBagConstraints con = new GridBagConstraints();
106 HashMap<String, AutoCompleter> autoCompleters = new HashMap<String, AutoCompleter>();
107 // Hashtable that holds as keys the names of the fields where
108 // autocomplete is active, and references to the autocompleter objects.
111 public CountingUndoManager undoManager = new CountingUndoManager(this);
112 UndoAction undoAction = new UndoAction();
113 RedoAction redoAction = new RedoAction();
115 //ExampleFileFilter fileFilter;
116 // File filter for .bib files.
118 boolean baseChanged = false, nonUndoableChange = false;
119 // Used to track whether the base has changed since last save.
121 //EntryTableModel tableModel = null;
122 //public EntryTable entryTable = null;
123 public MainTable mainTable = null;
124 public FilterList<BibtexEntry> searchFilterList = null, groupFilterList = null;
126 public RightClickMenu rcm;
128 BibtexEntry showing = null;
129 // To indicate which entry is currently shown.
130 public HashMap<String, EntryEditor> entryEditors = new HashMap<String, EntryEditor>();
131 // To contain instantiated entry editors. This is to save time
132 // in switching between entries.
134 //HashMap entryTypeForms = new HashMap();
135 // Hashmap to keep track of which entries currently have open
136 // EntryTypeForm dialogs.
138 PreambleEditor preambleEditor = null;
139 // Keeps track of the preamble dialog if it is open.
141 StringDialog stringDialog = null;
142 // Keeps track of the string dialog if it is open.
144 SaveDatabaseAction saveAction;
147 * The group selector component for this database. Instantiated by the
148 * SidePaneManager if necessary, or from this class if merging groups from a
149 * different database.
151 //GroupSelector groupSelector;
154 showingSearch = false,
155 showingGroup = false,
156 sortingBySearchResults = false,
157 coloringBySearchResults = false,
158 hidingNonHits = false,
159 sortingByGroup = false,
160 sortingByCiteSeerResults = false,
161 coloringByGroup = false;
163 int lastSearchHits = -1; // The number of hits in the latest search.
164 // Potential use in hiding non-hits completely.
166 // MetaData parses, keeps and writes meta data.
169 private boolean suppressOutput = false;
171 private HashMap<String, Object> actions = new HashMap<String, Object>();
172 private SidePaneManager sidePaneManager;
175 * Create a new BasePanel with an empty database.
176 * @param frame The application window.
178 public BasePanel(JabRefFrame frame) {
179 this.sidePaneManager = Globals.sidePaneManager;
180 database = new BibtexDatabase();
181 metaData = new MetaData();
182 metaData.initializeNewDatabase();
186 encoding = Globals.prefs.get("defaultEncoding");
187 //System.out.println("Default: "+encoding);
190 public BasePanel(JabRefFrame frame, BibtexDatabase db, File file,
191 HashMap<String, String> meta, String encoding) {
193 this.encoding = encoding;
194 // System.out.println(encoding);
195 //super(JSplitPane.HORIZONTAL_SPLIT, true);
196 this.sidePaneManager = Globals.sidePaneManager;
202 metaData = new MetaData();
203 metaData.initializeNewDatabase();
208 metaData.setFile(file);
210 // Register so we get notifications about outside changes to the file.
213 fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(this,
215 } catch (IOException ex) {
219 public boolean isBaseChanged(){
223 public int getMode() {
227 public BibtexDatabase database() {
231 public MetaData metaData() {
235 public JabRefFrame frame() {
239 public JabRefPreferences prefs() {
240 return Globals.prefs;
243 public String getEncoding() {
247 public void setEncoding(String encoding) {
248 this.encoding = encoding;
251 public void output(String s) {
252 //Util.pr("\""+s+"\""+(SwingUtilities.isEventDispatchThread()));
257 private void setupActions() {
258 saveAction = new SaveDatabaseAction(this);
260 actions.put("undo", undoAction);
261 actions.put("redo", redoAction);
263 // The action for opening an entry editor.
264 actions.put("edit", new BaseAction() {
265 public void action() {
266 selectionListener.editSignalled();
269 if (isShowingEditor()) {
270 new FocusRequester(splitPane.getBottomComponent());
276 //public void run() {
278 // We demand that one and only one row is selected.
279 if (entryTable.getSelectedRowCount() == 1) {
280 clickedOn = entryTable.getSelectedRow();
282 if (clickedOn >= 0) {
283 String id = tableModel.getIdForRow(clickedOn);
284 BibtexEntry be = database.getEntryById(id);
287 if (splitPane.getBottomComponent() != null) {
288 new FocusRequester(splitPane.getBottomComponent());
298 actions.put("test", new BaseAction () {
299 public void action() throws Throwable {
301 SearchResultsDialog diag = new SearchResultsDialog(frame, "Test");
302 diag.setVisible(true);
303 List<BibtexEntry> list = new ArrayList<BibtexEntry>();
304 BibtexEntry[] entries = getSelectedEntries();
305 for (int i = 0; i < entries.length; i++) {
306 BibtexEntry entry = entries[i];
309 diag.addEntries(list, BasePanel.this);
312 // Z3950Connection conn = new Z3950Connection();
315 ArrayList<BibtexEntry> entries = new ArrayList<BibtexEntry>();
316 BibtexEntry[] sel = getSelectedEntries();
317 for (int i = 0; i < sel.length; i++) {
318 BibtexEntry bibtexEntry = sel[i];
319 entries.add(bibtexEntry);
321 final List<FileListEntry> links =
322 AccessLinksForEntries.getExternalLinksForEntries(entries);
323 for (Iterator<FileListEntry> iterator = links.iterator(); iterator.hasNext();) {
324 FileListEntry entry = iterator.next();
325 System.out.println("Link: "+entry.getLink());
328 final JProgressBar prog = new JProgressBar();
329 prog.setIndeterminate(true);
330 final JDialog diag = new JDialog(frame, false);
331 diag.getContentPane().add(prog, BorderLayout.CENTER);
333 diag.setLocationRelativeTo(frame);
334 diag.setVisible(true);
335 Thread t = new Thread(new Runnable() {
337 AccessLinksForEntries.copyExternalLinksToDirectory(links,
338 new File("/home/alver/tmp"), metaData, prog, false,
339 new ActionListener() {
340 public void actionPerformed(ActionEvent actionEvent) {
348 //CheckBoxFileChooser cb = new CheckBoxFileChooser(new File(""), "Selected only");
349 //cb.showSaveDialog(frame);
351 //ExternalFileTypeEditor efte = new ExternalFileTypeEditor(frame);
352 //efte.setVisible(true);
359 // The action for saving a database.
360 actions.put("save", saveAction);
362 actions.put("saveAs", new BaseAction() {
363 public void action() throws Throwable {
368 actions.put("saveSelectedAs", new BaseAction () {
369 public void action() throws Throwable {
371 String chosenFile = Globals.getNewFile(frame, new File(Globals.prefs.get("workingDirectory")), ".bib",
372 JFileChooser.SAVE_DIALOG, false);
373 if (chosenFile != null) {
374 File expFile = new File(chosenFile);
375 if (!expFile.exists() ||
376 (JOptionPane.showConfirmDialog
377 (frame, "'"+expFile.getName()+"' "+
378 Globals.lang("exists. Overwrite file?"),
379 Globals.lang("Save database"), JOptionPane.OK_CANCEL_OPTION)
380 == JOptionPane.OK_OPTION)) {
382 saveDatabase(expFile, true, Globals.prefs.get("defaultEncoding"));
383 //runCommand("save");
384 frame.getFileHistory().newFile(expFile.getPath());
385 frame.output(Globals.lang("Saved selected to")+" '"
386 +expFile.getPath()+"'.");
392 // The action for copying selected entries.
393 actions.put("copy", new BaseAction() {
394 public void action() {
395 BibtexEntry[] bes = mainTable.getSelectedEntries();
397 if ((bes != null) && (bes.length > 0)) {
398 TransferableBibtexEntry trbe
399 = new TransferableBibtexEntry(bes);
400 // ! look at ClipBoardManager
401 Toolkit.getDefaultToolkit().getSystemClipboard()
402 .setContents(trbe, BasePanel.this);
403 output(Globals.lang("Copied")+" "+(bes.length>1 ? bes.length+" "
404 +Globals.lang("entries")
405 : "1 "+Globals.lang("entry")+"."));
407 // The user maybe selected a single cell.
408 int[] rows = mainTable.getSelectedRows(),
409 cols = mainTable.getSelectedColumns();
410 if ((cols.length == 1) && (rows.length == 1)) {
411 // Copy single value.
412 Object o = mainTable.getValueAt(rows[0], cols[0]);
414 StringSelection ss = new StringSelection(o.toString());
415 Toolkit.getDefaultToolkit().getSystemClipboard()
416 .setContents(ss, BasePanel.this);
418 output(Globals.lang("Copied cell contents")+".");
425 actions.put("cut", new BaseAction() {
426 public void action() throws Throwable {
428 BibtexEntry[] bes = mainTable.getSelectedEntries();
429 //int row0 = mainTable.getSelectedRow();
430 if ((bes != null) && (bes.length > 0)) {
431 // Create a CompoundEdit to make the action undoable.
432 NamedCompound ce = new NamedCompound
433 (Globals.lang(bes.length > 1 ? "cut entries" : "cut entry"));
434 // Loop through the array of entries, and delete them.
435 for (int i=0; i<bes.length; i++) {
436 database.removeEntry(bes[i].getId());
437 ensureNotShowing(bes[i]);
438 ce.addEdit(new UndoableRemoveEntry
439 (database, bes[i], BasePanel.this));
441 //entryTable.clearSelection();
442 frame.output(Globals.lang("Cut_pr")+" "+
443 (bes.length>1 ? bes.length
444 +" "+ Globals.lang("entries")
445 : Globals.lang("entry"))+".");
447 undoManager.addEdit(ce);
450 // Reselect the entry in the first prev. selected position:
451 /*if (row0 >= entryTable.getRowCount())
452 row0 = entryTable.getRowCount()-1;
454 entryTable.addRowSelectionInterval(row0, row0);*/
459 actions.put("delete", new BaseAction() {
460 public void action() {
461 BibtexEntry[] bes = mainTable.getSelectedEntries();
462 if ((bes != null) && (bes.length > 0)) {
464 boolean goOn = showDeleteConfirmationDialog(bes.length);
469 // Create a CompoundEdit to make the action undoable.
470 NamedCompound ce = new NamedCompound
471 (Globals.lang(bes.length > 1 ? "delete entries" : "delete entry"));
472 // Loop through the array of entries, and delete them.
473 for (int i = 0; i < bes.length; i++) {
474 database.removeEntry(bes[i].getId());
475 ensureNotShowing(bes[i]);
476 ce.addEdit(new UndoableRemoveEntry(database, bes[i], BasePanel.this));
479 frame.output(Globals.lang("Deleted") + " " +
480 (bes.length > 1 ? bes.length
481 + " " + Globals.lang("entries")
482 : Globals.lang("entry")) + ".");
484 undoManager.addEdit(ce);
485 //entryTable.clearSelection();
489 // Reselect the entry in the first prev. selected position:
490 /*if (row0 >= entryTable.getRowCount())
491 row0 = entryTable.getRowCount()-1;
493 final int toSel = row0;
495 SwingUtilities.invokeLater(new Runnable() {
497 entryTable.addRowSelectionInterval(toSel, toSel);
498 //entryTable.ensureVisible(toSel);
508 // The action for pasting entries or cell contents.
509 // Edited by Seb Wills <saw27@mrao.cam.ac.uk> on 14-Apr-04:
510 // - more robust detection of available content flavors (doesn't only look at first one offered)
511 // - support for parsing string-flavor clipboard contents which are bibtex entries.
512 // This allows you to (a) paste entire bibtex entries from a text editor, web browser, etc
513 // (b) copy and paste entries between multiple instances of JabRef (since
514 // only the text representation seems to get as far as the X clipboard, at least on my system)
515 actions.put("paste", new BaseAction() {
516 public void action() {
517 // Get clipboard contents, and see if TransferableBibtexEntry is among the content flavors offered
518 Transferable content = Toolkit.getDefaultToolkit()
519 .getSystemClipboard().getContents(null);
520 if (content != null) {
521 BibtexEntry[] bes = null;
522 if (content.isDataFlavorSupported(TransferableBibtexEntry.entryFlavor)) {
523 // We have determined that the clipboard data is a set of entries.
525 bes = (BibtexEntry[])(content.getTransferData(TransferableBibtexEntry.entryFlavor));
527 } catch (UnsupportedFlavorException ex) {
528 ex.printStackTrace();
529 } catch (IOException ex) {
530 ex.printStackTrace();
532 } else if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
534 BibtexParser bp = new BibtexParser
535 (new java.io.StringReader( (String) (content.getTransferData(
536 DataFlavor.stringFlavor))));
537 BibtexDatabase db = bp.parse().getDatabase();
538 Util.pr("Parsed " + db.getEntryCount() + " entries from clipboard text");
539 if(db.getEntryCount()>0) {
540 bes = db.getEntries().toArray(new BibtexEntry[db.getEntryCount()]);
542 } catch (UnsupportedFlavorException ex) {
543 ex.printStackTrace();
544 } catch (Throwable ex) {
545 ex.printStackTrace();
550 // finally we paste in the entries (if any), which either came from TransferableBibtexEntries
551 // or were parsed from a string
552 if ((bes != null) && (bes.length > 0)) {
554 NamedCompound ce = new NamedCompound
555 (Globals.lang(bes.length > 1 ? "paste entries" : "paste entry"));
556 for (int i=0; i<bes.length; i++) {
558 BibtexEntry be = (BibtexEntry)(bes[i].clone());
559 Util.setAutomaticFields(be,
560 Globals.prefs.getBoolean("overwriteOwner"),
561 Globals.prefs.getBoolean("overwriteTimeStamp"));
563 // We have to clone the
564 // entries, since the pasted
565 // entries must exist
566 // independently of the copied
568 be.setId(Util.createNeutralId());
569 database.insertEntry(be);
570 ce.addEdit(new UndoableInsertEntry
571 (database, be, BasePanel.this));
572 } catch (KeyCollisionException ex) {
573 Util.pr("KeyCollisionException... this shouldn't happen.");
577 undoManager.addEdit(ce);
578 //entryTable.clearSelection();
579 //entryTable.revalidate();
580 output(Globals.lang("Pasted")+" "+
581 (bes.length>1 ? bes.length+" "+
582 Globals.lang("entries") : "1 "+Globals.lang("entry"))
592 actions.put("selectAll", new BaseAction() {
593 public void action() {
594 mainTable.selectAll();
598 // The action for opening the preamble editor
599 actions.put("editPreamble", new BaseAction() {
600 public void action() {
601 if (preambleEditor == null) {
602 PreambleEditor form = new PreambleEditor
603 (frame, BasePanel.this, database, Globals.prefs);
604 Util.placeDialog(form, frame);
605 form.setVisible(true);
606 preambleEditor = form;
608 preambleEditor.setVisible(true);
614 // The action for opening the string editor
615 actions.put("editStrings", new BaseAction() {
616 public void action() {
617 if (stringDialog == null) {
618 StringDialog form = new StringDialog
619 (frame, BasePanel.this, database, Globals.prefs);
620 Util.placeDialog(form, frame);
621 form.setVisible(true);
624 stringDialog.setVisible(true);
630 // The action for toggling the groups interface
631 actions.put("toggleGroups", new BaseAction() {
632 public void action() {
633 sidePaneManager.toggle("groups");
634 frame.groupToggle.setSelected(sidePaneManager.isComponentVisible("groups"));
639 // action for collecting database strings from user
640 actions.put("dbConnect", new BaseAction() {
642 public void action () {
644 DBStrings dbs = metaData.getDBStrings();
646 // init DB strings if necessary
647 if (! dbs.isInitialized()) {
651 // show connection dialog
652 DBConnectDialog dbd = new DBConnectDialog(frame(), dbs);
653 Util.placeDialog(dbd, BasePanel.this );
654 dbd.setVisible(true);
656 // connnect to database to test DBStrings
657 if (dbd.getConnectToDB()) {
659 dbs = dbd.getDBStrings();
663 frame.output(Globals.lang("Establishing SQL connection..."));
664 Connection conn = SQLutil.connectToDB(dbs);
666 dbs.isConfigValid(true);
667 frame.output(Globals.lang("SQL connection established."));
669 } catch (Exception ex) {
671 String errorMessage = SQLutil.getExceptionMessage(ex,SQLutil.DBTYPE.MYSQL);
672 dbs.isConfigValid(false);
674 String preamble = "Could not connect to SQL database for the following reason:";
675 frame.output(Globals.lang(preamble)
676 + " " + errorMessage);
678 JOptionPane.showMessageDialog(frame, Globals.lang(preamble)
679 + "\n" + errorMessage, Globals.lang("Connect to SQL database"),
680 JOptionPane.ERROR_MESSAGE);
684 metaData.setDBStrings(dbs);
696 // action for exporting database to external SQL database
697 actions.put("dbExport", new AbstractWorker () {
699 String errorMessage = null;
700 boolean connectToDB = false;
702 // run first, in EDT:
705 DBStrings dbs = metaData.getDBStrings();
707 // get DBStrings from user if necessary
708 if (!dbs.isConfigValid()) {
710 // init DB strings if necessary
711 if (! dbs.isInitialized()) {
715 // show connection dialog
716 DBConnectDialog dbd = new DBConnectDialog(frame(), dbs);
717 Util.placeDialog(dbd, BasePanel.this );
718 dbd.setVisible(true);
720 connectToDB = dbd.getConnectToDB();
722 // store database strings
724 dbs = dbd.getDBStrings();
725 metaData.setDBStrings(dbs);
737 // run second, on a different thread:
742 DBStrings dbs = metaData.getDBStrings();
746 frame.output(Globals.lang("Attempting SQL export..."));
747 SQLutil.exportDatabase(database, metaData, null, dbs);
748 dbs.isConfigValid(true);
750 } catch (Exception ex) {
752 errorMessage = SQLutil.getExceptionMessage(ex,SQLutil.DBTYPE.MYSQL);
753 dbs.isConfigValid(false);
757 metaData.setDBStrings(dbs);
763 // run third, on EDT:
764 public void update() {
766 String url = SQLutil.createJDBCurl(metaData.getDBStrings());
768 // if no error, report success
769 if (errorMessage == null) {
771 frame.output(Globals.lang("%0 export successful", url));
775 // show an error dialog if an error occurred
778 String preamble = "Could not export to SQL database for the following reason:";
779 frame.output(Globals.lang(preamble)
780 + " " + errorMessage);
782 JOptionPane.showMessageDialog(frame, Globals.lang(preamble)
783 + "\n" + errorMessage, Globals.lang("Export to SQL database"),
784 JOptionPane.ERROR_MESSAGE);
793 // The action for auto-generating keys.
794 actions.put("makeKey", new AbstractWorker() {
796 List<BibtexEntry> entries;
798 boolean cancelled = false;
800 // Run first, in EDT:
803 entries = new ArrayList<BibtexEntry>(Arrays.asList(getSelectedEntries()));
804 //rows = entryTable.getSelectedRows() ;
805 numSelected = entries.size();
807 if (entries.size() == 0) { // None selected. Inform the user to select entries first.
808 JOptionPane.showMessageDialog(frame, Globals.lang("First select the entries you want keys to be generated for."),
809 Globals.lang("Autogenerate BibTeX key"), JOptionPane.INFORMATION_MESSAGE);
813 output(Globals.lang("Generating BibTeX key for")+" "+
814 numSelected+" "+(numSelected>1 ? Globals.lang("entries")
815 : Globals.lang("entry"))+"...");
818 // Run second, on a different thread:
820 BibtexEntry bes = null ;
821 NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
823 // First check if any entries have keys set already. If so, possibly remove
824 // them from consideration, or warn about overwriting keys.
825 loop: for (Iterator<BibtexEntry> i=entries.iterator(); i.hasNext();) {
827 if (bes.getField(BibtexFields.KEY_FIELD) != null) {
828 if (Globals.prefs.getBoolean("avoidOverwritingKey"))
829 // Rmove the entry, because its key is already set:
831 else if (Globals.prefs.getBoolean("warnBeforeOverwritingKey")) {
832 // Ask if the user wants to cancel the operation:
833 CheckBoxMessage cbm = new CheckBoxMessage(Globals.lang("One or more keys will be overwritten. Continue?"),
834 Globals.lang("Disable this confirmation dialog"), false);
835 int answer = JOptionPane.showConfirmDialog(frame, cbm, Globals.lang("Overwrite keys"),
836 JOptionPane.YES_NO_OPTION);
837 if (cbm.isSelected())
838 Globals.prefs.putBoolean("warnBeforeOverwritingKey", false);
839 if (answer == JOptionPane.NO_OPTION) {
840 // Ok, break off the operation.
844 // No need to check more entries, because the user has already confirmed
845 // that it's ok to overwrite keys:
851 HashMap<BibtexEntry, Object> oldvals = new HashMap<BibtexEntry, Object>();
852 // Iterate again, removing already set keys. This is skipped if overwriting
853 // is disabled, since all entries with keys set will have been removed.
854 if (!Globals.prefs.getBoolean("avoidOverwritingKey")) for (Iterator<BibtexEntry> i=entries.iterator(); i.hasNext();) {
856 // Store the old value:
857 oldvals.put(bes, bes.getField(BibtexFields.KEY_FIELD));
858 database.setCiteKeyForEntry(bes.getId(), null);
861 // Finally, set the new keys:
862 for (Iterator<BibtexEntry> i=entries.iterator(); i.hasNext();) {
864 bes = LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, bes);
865 ce.addEdit(new UndoableKeyChange
866 (database, bes.getId(), (String)oldvals.get(bes),
867 bes.getField(BibtexFields.KEY_FIELD)));
870 undoManager.addEdit(ce);
873 // Run third, on EDT:
874 public void update() {
880 numSelected = entries.size();
881 output(Globals.lang("Generated BibTeX key for")+" "+
882 numSelected+" "+(numSelected!=1 ? Globals.lang("entries")
883 : Globals.lang("entry")));
888 actions.put("search", new BaseAction() {
889 public void action() {
890 //sidePaneManager.togglePanel("search");
891 sidePaneManager.show("search");
892 //boolean on = sidePaneManager.isPanelVisible("search");
893 frame.searchToggle.setSelected(true);
895 frame.searchManager.startSearch();
899 actions.put("toggleSearch", new BaseAction() {
900 public void action() {
901 //sidePaneManager.togglePanel("search");
902 sidePaneManager.toggle("search");
903 boolean on = sidePaneManager.isComponentVisible("search");
904 frame.searchToggle.setSelected(on);
906 frame.searchManager.startSearch();
910 actions.put("incSearch", new BaseAction() {
911 public void action() {
912 sidePaneManager.show("search");
913 frame.searchToggle.setSelected(true);
914 frame.searchManager.startIncrementalSearch();
918 // The action for copying the selected entry's key.
919 actions.put("copyKey", new BaseAction() {
920 public void action() {
921 BibtexEntry[] bes = mainTable.getSelectedEntries();
922 if ((bes != null) && (bes.length > 0)) {
924 //String[] keys = new String[bes.length];
925 Vector<Object> keys = new Vector<Object>();
926 // Collect all non-null keys.
927 for (int i=0; i<bes.length; i++)
928 if (bes[i].getField(BibtexFields.KEY_FIELD) != null)
929 keys.add(bes[i].getField(BibtexFields.KEY_FIELD));
930 if (keys.size() == 0) {
931 output("None of the selected entries have BibTeX keys.");
934 StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
935 for (int i=1; i<keys.size(); i++) {
937 sb.append((String)keys.elementAt(i));
940 StringSelection ss = new StringSelection(sb.toString());
941 Toolkit.getDefaultToolkit().getSystemClipboard()
942 .setContents(ss, BasePanel.this);
944 if (keys.size() == bes.length)
945 // All entries had keys.
946 output(Globals.lang((bes.length > 1) ? "Copied keys"
947 : "Copied key")+".");
949 output(Globals.lang("Warning")+": "+(bes.length-keys.size())
950 +" "+Globals.lang("out of")+" "+bes.length+" "+
951 Globals.lang("entries have undefined BibTeX key")+".");
956 // The action for copying a cite for the selected entry.
957 actions.put("copyCiteKey", new BaseAction() {
958 public void action() {
959 BibtexEntry[] bes = mainTable.getSelectedEntries();
960 if ((bes != null) && (bes.length > 0)) {
962 //String[] keys = new String[bes.length];
963 Vector<Object> keys = new Vector<Object>();
964 // Collect all non-null keys.
965 for (int i=0; i<bes.length; i++)
966 if (bes[i].getField(BibtexFields.KEY_FIELD) != null)
967 keys.add(bes[i].getField(BibtexFields.KEY_FIELD));
968 if (keys.size() == 0) {
969 output("None of the selected entries have BibTeX keys.");
972 StringBuffer sb = new StringBuffer((String)keys.elementAt(0));
973 for (int i=1; i<keys.size(); i++) {
975 sb.append((String)keys.elementAt(i));
978 StringSelection ss = new StringSelection
979 ("\\cite{"+sb.toString()+"}");
980 Toolkit.getDefaultToolkit().getSystemClipboard()
981 .setContents(ss, BasePanel.this);
983 if (keys.size() == bes.length)
984 // All entries had keys.
985 output(Globals.lang((bes.length > 1) ? "Copied keys"
986 : "Copied key")+".");
988 output(Globals.lang("Warning")+": "+(bes.length-keys.size())
989 +" "+Globals.lang("out of")+" "+bes.length+" "+
990 Globals.lang("entries have undefined BibTeX key")+".");
995 actions.put("mergeDatabase", new AppendDatabaseAction(frame, this));
998 actions.put("openFile", new BaseAction() {
999 public void action() {
1002 BibtexEntry[] bes = mainTable.getSelectedEntries();
1003 String field = "ps";
1004 if ((bes != null) && (bes.length == 1)) {
1005 Object link = bes[0].getField("ps");
1006 if (bes[0].getField("pdf") != null) {
1007 link = bes[0].getField("pdf");
1010 String filepath = null;
1012 filepath = link.toString();
1015 // see if we can fall back to a filename based on the bibtex key
1017 Object key = bes[0].getField(BibtexFields.KEY_FIELD);
1019 basefile = key.toString();
1020 final ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection();
1021 final String sep = System.getProperty("file.separator");
1022 String dir = metaData.getFileDirectory(GUIGlobals.FILE_FIELD);
1023 if ((dir != null) && (dir.length() > 0)) {
1024 if (dir.endsWith(sep)) {
1025 dir = dir.substring(0, dir.length() - sep.length());
1027 for (int i = 0; i < types.length; i++) {
1028 String found = Util.findPdf(basefile, types[i].getExtension(),
1029 dir, new OpenFileFilter("." + types[i].getExtension()));
1030 if (found != null) {
1031 filepath = dir + sep + found;
1040 if (filepath != null) {
1041 //output(Globals.lang("Calling external viewer..."));
1043 Util.openExternalViewer(metaData(), filepath, field);
1044 output(Globals.lang("External viewer called") + ".");
1046 catch (IOException ex) {
1047 output(Globals.lang("Error") + ": " + ex.getMessage());
1050 output(Globals.lang(
1051 "No pdf or ps defined, and no file matching Bibtex key found") +
1054 output(Globals.lang("No entries or multiple entries selected."));
1060 actions.put("openExternalFile", new BaseAction() {
1061 public void action() {
1064 BibtexEntry[] bes = mainTable.getSelectedEntries();
1065 String field = GUIGlobals.FILE_FIELD;
1066 if ((bes != null) && (bes.length == 1)) {
1067 Object link = bes[0].getField(field);
1069 runCommand("openFile"); // Fall back on PDF/PS fields???
1072 FileListTableModel tableModel = new FileListTableModel();
1073 tableModel.setContent((String)link);
1074 if (tableModel.getRowCount() == 0) {
1075 runCommand("openFile"); // Fall back on PDF/PS fields???
1078 FileListEntry flEntry = tableModel.getEntry(0);
1079 ExternalFileMenuItem item = new ExternalFileMenuItem
1080 (frame(), bes[0], "",
1081 flEntry.getLink(), flEntry.getType().getIcon(),
1082 metaData(), flEntry.getType());
1083 item.actionPerformed(null);
1085 output(Globals.lang("No entries or multiple entries selected."));
1092 actions.put("openUrl", new BaseAction() {
1093 public void action() {
1094 BibtexEntry[] bes = mainTable.getSelectedEntries();
1095 String field = "doi";
1096 if ((bes != null) && (bes.length == 1)) {
1097 Object link = bes[0].getField("doi");
1098 if (bes[0].getField("url") != null) {
1099 link = bes[0].getField("url");
1103 //output(Globals.lang("Calling external viewer..."));
1105 Util.openExternalViewer(metaData(), link.toString(), field);
1106 output(Globals.lang("External viewer called")+".");
1107 } catch (IOException ex) {
1108 output(Globals.lang("Error") + ": " + ex.getMessage());
1112 output(Globals.lang("No url defined")+".");
1114 output(Globals.lang("No entries or multiple entries selected."));
1118 actions.put("openSpires", new BaseAction() {
1119 public void action() {
1120 BibtexEntry[] bes = mainTable.getSelectedEntries();
1121 if ((bes != null) && (bes.length == 1)) {
1123 if (bes[0].getField("eprint") != null)
1124 link = SPIRESFetcher.constructUrlFromEprint(bes[0].getField("eprint").toString());
1125 else if (bes[0].getField("slaccitation") != null)
1126 link = SPIRESFetcher.constructUrlFromSlaccitation(bes[0].getField("slaccitation").toString());
1128 //output(Globals.lang("Calling external viewer..."));
1130 Util.openExternalViewer(metaData(), link.toString(), "url");
1131 output(Globals.lang("External viewer called")+".");
1132 } catch (IOException ex) {
1133 output(Globals.lang("Error") + ": " + ex.getMessage());
1137 output(Globals.lang("No url defined")+".");
1139 output(Globals.lang("No entries or multiple entries selected."));
1144 actions.put("replaceAll", new BaseAction() {
1145 public void action() {
1146 ReplaceStringDialog rsd = new ReplaceStringDialog(frame);
1147 rsd.setVisible(true);
1148 if (!rsd.okPressed())
1151 NamedCompound ce = new NamedCompound(Globals.lang("Replace string"));
1152 if (!rsd.selOnly()) {
1153 for (BibtexEntry entry : database.getEntries()){
1154 counter += rsd.replace(entry, ce);
1157 BibtexEntry[] bes = mainTable.getSelectedEntries();
1158 for (int i=0; i<bes.length; i++)
1159 counter += rsd.replace(bes[i], ce);
1162 output(Globals.lang("Replaced")+" "+counter+" "+
1163 Globals.lang(counter==1?"occurence":"occurences")+".");
1166 undoManager.addEdit(ce);
1172 actions.put("dupliCheck", new BaseAction() {
1173 public void action() {
1174 DuplicateSearch ds = new DuplicateSearch(BasePanel.this);
1179 /*actions.put("strictDupliCheck", new BaseAction() {
1180 public void action() {
1181 StrictDuplicateSearch ds = new StrictDuplicateSearch(BasePanel.this);
1186 actions.put("plainTextImport", new BaseAction() {
1187 public void action()
1189 // get Type of new entry
1190 EntryTypeDialog etd = new EntryTypeDialog(frame);
1191 Util.placeDialog(etd, BasePanel.this);
1192 etd.setVisible(true);
1193 BibtexEntryType tp = etd.getChoice();
1197 String id = Util.createNeutralId();
1198 BibtexEntry bibEntry = new BibtexEntry(id, tp) ;
1199 TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1202 Util.placeDialog(tidialog, BasePanel.this);
1203 tidialog.setVisible(true);
1205 if (tidialog.okPressed())
1207 Util.setAutomaticFields(Arrays.asList(new BibtexEntry[] {bibEntry}),
1209 insertEntry(bibEntry) ;
1214 // The action starts the "import from plain text" dialog
1215 /*actions.put("importPlainText", new BaseAction() {
1216 public void action()
1218 BibtexEntry bibEntry = null ;
1219 // try to get the first marked entry
1220 BibtexEntry[] bes = entryTable.getSelectedEntries();
1221 if ((bes != null) && (bes.length > 0))
1224 if (bibEntry != null)
1226 // Create an UndoableInsertEntry object.
1227 undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1229 TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this,
1232 Util.placeDialog(tidialog, BasePanel.this);
1233 tidialog.setVisible(true);
1235 if (tidialog.okPressed())
1237 output(Globals.lang("changed ")+" '"
1238 +bibEntry.getType().getName().toLowerCase()+"' "
1239 +Globals.lang("entry")+".");
1241 int row = tableModel.getNumberFromName(bibEntry.getId());
1243 entryTable.clearSelection();
1244 entryTable.scrollTo(row);
1245 markBaseChanged(); // The database just changed.
1246 if (Globals.prefs.getBoolean("autoOpenForm"))
1248 showEntry(bibEntry);
1255 actions.put("markEntries", new AbstractWorker() {
1256 private int besLength = -1;
1259 NamedCompound ce = new NamedCompound(Globals.lang("Mark entries"));
1260 BibtexEntry[] bes = mainTable.getSelectedEntries();
1261 besLength = bes.length;
1263 for (int i=0; i<bes.length; i++) {
1264 Util.markEntry(bes[i], ce);
1267 undoManager.addEdit(ce);
1270 public void update() {
1272 output(Globals.lang("Marked selected")+" "+Globals.lang(besLength>0?"entry":"entries"));
1277 actions.put("unmarkEntries", new BaseAction() {
1278 public void action() {
1280 NamedCompound ce = new NamedCompound(Globals.lang("Unmark entries"));
1281 BibtexEntry[] bes = mainTable.getSelectedEntries();
1284 for (int i=0; i<bes.length; i++) {
1285 Util.unmarkEntry(bes[i], database, ce);
1288 undoManager.addEdit(ce);
1290 output(Globals.lang("Unmarked selected")+" "+Globals.lang(bes.length>0?"entry":"entries"));
1291 } catch (Throwable ex) { ex.printStackTrace(); }
1295 actions.put("unmarkAll", new BaseAction() {
1296 public void action() {
1297 NamedCompound ce = new NamedCompound(Globals.lang("Unmark all"));
1299 for (BibtexEntry be : database.getEntries()){
1300 Util.unmarkEntry(be, database, ce);
1303 undoManager.addEdit(ce);
1308 actions.put("togglePreview", new BaseAction() {
1309 public void action() {
1310 boolean enabled = !Globals.prefs.getBoolean("previewEnabled");
1311 Globals.prefs.putBoolean("previewEnabled", enabled);
1312 frame.setPreviewActive(enabled);
1313 frame.previewToggle.setSelected(enabled);
1317 actions.put("toggleHighlightGroupsMatchingAny", new BaseAction() {
1318 public void action() {
1319 boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAny");
1320 Globals.prefs.putBoolean("highlightGroupsMatchingAny", enabled);
1321 frame.highlightAny.setSelected(enabled);
1323 frame.highlightAll.setSelected(false);
1324 Globals.prefs.putBoolean("highlightGroupsMatchingAll", false);
1326 // ping the listener so it updates:
1327 groupsHighlightListener.listChanged(null);
1331 actions.put("toggleHighlightGroupsMatchingAll", new BaseAction() {
1332 public void action() {
1333 boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAll");
1334 Globals.prefs.putBoolean("highlightGroupsMatchingAll", enabled);
1335 frame.highlightAll.setSelected(enabled);
1337 frame.highlightAny.setSelected(false);
1338 Globals.prefs.putBoolean("highlightGroupsMatchingAny", false);
1340 // ping the listener so it updates:
1341 groupsHighlightListener.listChanged(null);
1345 actions.put("switchPreview", new BaseAction() {
1346 public void action() {
1347 selectionListener.switchPreview();
1351 actions.put("manageSelectors", new BaseAction() {
1352 public void action() {
1353 ContentSelectorDialog2 csd = new ContentSelectorDialog2
1354 (frame, frame, BasePanel.this, false, metaData, null);
1355 Util.placeDialog(csd, frame);
1356 csd.setVisible(true);
1360 actions.put("exportToClipboard", new ExportToClipboardAction(frame, database()));
1362 actions.put("writeXMP", new WriteXMPAction(this));
1364 actions.put("abbreviateIso", new AbbreviateAction(this, true));
1365 actions.put("abbreviateMedline", new AbbreviateAction(this, false));
1366 actions.put("unabbreviate", new UnabbreviateAction(this));
1367 actions.put("autoSetPdf", new AutoSetExternalFileForEntries(this, "pdf"));
1368 actions.put("autoSetPs", new AutoSetExternalFileForEntries(this, "ps"));
1369 actions.put("autoSetFile", new SynchronizeFileField(this));
1370 actions.put("upgradeLinks", new UpgradeExternalLinks(this));
1375 * This method is called from JabRefFrame is a database specific
1376 * action is requested by the user. Runs the command if it is
1377 * defined, or prints an error message to the standard error
1380 * @param _command The name of the command to run.
1382 public void runCommand(String _command) {
1383 final String command = _command;
1385 // public void run() {
1386 if (actions.get(command) == null)
1387 Util.pr("No action defined for'" + command + "'");
1389 Object o = actions.get(command);
1391 if (o instanceof BaseAction)
1392 ((BaseAction)o).action();
1394 // This part uses Spin's features:
1395 Worker wrk = ((AbstractWorker)o).getWorker();
1396 // The Worker returned by getWorker() has been wrapped
1397 // by Spin.off(), which makes its methods be run in
1398 // a different thread from the EDT.
1399 CallBack clb = ((AbstractWorker)o).getCallBack();
1401 ((AbstractWorker)o).init(); // This method runs in this same thread, the EDT.
1402 // Useful for initial GUI actions, like printing a message.
1404 // The CallBack returned by getCallBack() has been wrapped
1405 // by Spin.over(), which makes its methods be run on
1407 wrk.run(); // Runs the potentially time-consuming action
1408 // without freezing the GUI. The magic is that THIS line
1409 // of execution will not continue until run() is finished.
1410 clb.update(); // Runs the update() method on the EDT.
1412 } catch (Throwable ex) {
1413 // If the action has blocked the JabRefFrame before crashing, we need to unblock it.
1414 // The call to unblock will simply hide the glasspane, so there is no harm in calling
1415 // it even if the frame hasn't been blocked.
1417 ex.printStackTrace();
1424 private boolean saveDatabase(File file, boolean selectedOnly, String encoding) throws SaveException {
1425 SaveSession session;
1429 session = FileActions.saveDatabase(database, metaData, file,
1430 Globals.prefs, false, false, encoding);
1432 session = FileActions.savePartOfDatabase(database, metaData, file,
1433 Globals.prefs, mainTable.getSelectedEntries(), encoding);
1435 } catch (UnsupportedCharsetException ex2) {
1436 JOptionPane.showMessageDialog(frame, Globals.lang("Could not save file. "
1437 +"Character encoding '%0' is not supported.", encoding),
1438 Globals.lang("Save database"), JOptionPane.ERROR_MESSAGE);
1439 throw new SaveException("rt");
1440 } catch (SaveException ex) {
1441 if (ex.specificEntry()) {
1442 // Error occured during processing of
1443 // be. Highlight it:
1444 int row = mainTable.findEntry(ex.getEntry()),
1445 topShow = Math.max(0, row-3);
1446 mainTable.setRowSelectionInterval(row, row);
1447 mainTable.scrollTo(topShow);
1448 showEntry(ex.getEntry());
1450 else ex.printStackTrace();
1452 JOptionPane.showMessageDialog
1453 (frame, Globals.lang("Could not save file")
1454 +".\n"+ex.getMessage(),
1455 Globals.lang("Save database"),
1456 JOptionPane.ERROR_MESSAGE);
1457 throw new SaveException("rt");
1463 boolean commit = true;
1464 if (!session.getWriter().couldEncodeAll()) {
1465 DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("left:pref, 4dlu, fill:pref", ""));
1466 JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters());
1467 ta.setEditable(false);
1468 builder.append(Globals.lang("The chosen encoding '%0' could not encode the following characters: ",
1469 session.getEncoding()));
1471 builder.append(Globals.lang("What do you want to do?"));
1472 String tryDiff = Globals.lang("Try different encoding");
1473 int answer = JOptionPane.showOptionDialog(frame, builder.getPanel(), Globals.lang("Save database"),
1474 JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null,
1475 new String[] {Globals.lang("Save"), tryDiff, Globals.lang("Cancel")}, tryDiff);
1477 if (answer == JOptionPane.NO_OPTION) {
1478 // The user wants to use another encoding.
1479 Object choice = JOptionPane.showInputDialog(frame, Globals.lang("Select encoding"), Globals.lang("Save database"),
1480 JOptionPane.QUESTION_MESSAGE, null, Globals.ENCODINGS, encoding);
1481 if (choice != null) {
1482 String newEncoding = (String)choice;
1483 return saveDatabase(file, selectedOnly, newEncoding);
1486 } else if (answer == JOptionPane.CANCEL_OPTION)
1495 this.encoding = encoding; // Make sure to remember which encoding we used.
1499 } catch (IOException e) {
1500 e.printStackTrace();
1508 * This method is called from JabRefFrame when the user wants to
1509 * create a new entry. If the argument is null, the user is
1510 * prompted for an entry type.
1512 * @param type The type of the entry to create.
1513 * @return The newly created BibtexEntry or null the operation was canceled by the user.
1515 public BibtexEntry newEntry(BibtexEntryType type) {
1517 // Find out what type is wanted.
1518 EntryTypeDialog etd = new EntryTypeDialog(frame);
1519 // We want to center the dialog, to make it look nicer.
1520 Util.placeDialog(etd, frame);
1521 etd.setVisible(true);
1522 type = etd.getChoice();
1524 if (type != null) { // Only if the dialog was not cancelled.
1525 String id = Util.createNeutralId();
1526 final BibtexEntry be = new BibtexEntry(id, type);
1528 database.insertEntry(be);
1530 // Set owner/timestamp if options are enabled:
1531 ArrayList<BibtexEntry> list = new ArrayList<BibtexEntry>();
1533 Util.setAutomaticFields(list, true, true);
1535 // Create an UndoableInsertEntry object.
1536 undoManager.addEdit(new UndoableInsertEntry(database, be, BasePanel.this));
1537 output(Globals.lang("Added new")+" '"+type.getName().toLowerCase()+"' "
1538 +Globals.lang("entry")+".");
1539 mainTable.findEntry(be);
1541 // We are going to select the new entry. Before that, make sure that we are in
1542 // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR
1543 // mode which makes sure the selection will trigger display of the entry editor
1544 // and adjustment of the splitter.
1545 if (mode != SHOWING_EDITOR) {
1546 mode = WILL_SHOW_EDITOR;
1549 highlightEntry(be); // Selects the entry. The selection listener will open the editor.
1551 markBaseChanged(); // The database just changed.
1552 new FocusRequester(getEntryEditor(be));
1554 } catch (KeyCollisionException ex) {
1555 Util.pr(ex.getMessage());
1564 * This method is called from JabRefFrame when the user wants to
1565 * create a new entry.
1566 * @param bibEntry The new entry.
1568 public void insertEntry(BibtexEntry bibEntry)
1570 if (bibEntry != null)
1574 database.insertEntry(bibEntry) ;
1575 if (Globals.prefs.getBoolean("useOwner"))
1576 // Set owner field to default value
1577 bibEntry.setField(BibtexFields.OWNER, Globals.prefs.get("defaultOwner") );
1578 // Create an UndoableInsertEntry object.
1579 undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this));
1580 output(Globals.lang("Added new")+" '"
1581 +bibEntry.getType().getName().toLowerCase()+"' "
1582 +Globals.lang("entry")+".");
1583 int row = mainTable.findEntry(bibEntry);
1585 mainTable.clearSelection();
1586 mainTable.scrollTo(row);
1587 markBaseChanged(); // The database just changed.
1588 if (Globals.prefs.getBoolean("autoOpenForm"))
1590 showEntry(bibEntry);
1592 } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); }
1596 public void createMainTable() {
1597 //Comparator comp = new FieldComparator("author");
1599 GlazedEntrySorter eventList = new GlazedEntrySorter(database.getEntryMap());
1600 // Must initialize sort columns somehow:
1602 database.addDatabaseChangeListener(eventList);
1603 groupFilterList = new FilterList<BibtexEntry>(eventList.getTheList(), NoSearchMatcher.INSTANCE);
1604 searchFilterList = new FilterList<BibtexEntry>(groupFilterList, NoSearchMatcher.INSTANCE);
1605 //final SortedList sortedList = new SortedList(searchFilterList, null);
1606 MainTableFormat tableFormat = new MainTableFormat(this);
1607 tableFormat.updateTableFormat();
1608 //EventTableModel tableModel = new EventTableModel(sortedList, tableFormat);
1609 mainTable = new MainTable(tableFormat, searchFilterList, frame, this);
1611 selectionListener = new MainTableSelectionListener(this, mainTable);
1612 mainTable.updateFont();
1613 mainTable.addSelectionListener(selectionListener);
1614 mainTable.addMouseListener(selectionListener);
1615 mainTable.addKeyListener(selectionListener);
1616 mainTable.addFocusListener(selectionListener);
1618 // Add the listener that will take care of highlighting groups as the selection changes:
1619 groupsHighlightListener = new ListEventListener<BibtexEntry>() {
1620 public void listChanged(ListEvent<BibtexEntry> listEvent) {
1621 if (Globals.prefs.getBoolean("highlightGroupsMatchingAny"))
1622 getGroupSelector().showMatchingGroups(
1623 mainTable.getSelectedEntries(), false);
1624 else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll"))
1625 getGroupSelector().showMatchingGroups(
1626 mainTable.getSelectedEntries(), true);
1627 else // no highlight
1628 getGroupSelector().showMatchingGroups(null, true);
1631 mainTable.addSelectionListener(groupsHighlightListener);
1633 mainTable.getActionMap().put("cut", new AbstractAction() {
1634 public void actionPerformed(ActionEvent e) {
1635 try { runCommand("cut");
1636 } catch (Throwable ex) {
1637 ex.printStackTrace();
1641 mainTable.getActionMap().put("copy", new AbstractAction() {
1642 public void actionPerformed(ActionEvent e) {
1643 try { runCommand("copy");
1644 } catch (Throwable ex) {
1645 ex.printStackTrace();
1649 mainTable.getActionMap().put("paste", new AbstractAction() {
1650 public void actionPerformed(ActionEvent e) {
1651 try { runCommand("paste");
1652 } catch (Throwable ex) {
1653 ex.printStackTrace();
1658 mainTable.addKeyListener(new KeyAdapter() {
1660 public void keyPressed(KeyEvent e) {
1661 final int keyCode = e.getKeyCode();
1662 final TreePath path = frame.groupSelector.getSelectionPath();
1663 final GroupTreeNode node = path == null ? null : (GroupTreeNode) path.getLastPathComponent();
1665 if (e.isControlDown()) {
1667 // The up/down/left/rightkeystrokes are displayed in the
1668 // GroupSelector's popup menu, so if they are to be changed,
1669 // edit GroupSelector.java accordingly!
1670 case KeyEvent.VK_UP:
1673 frame.groupSelector.moveNodeUp(node, true);
1675 case KeyEvent.VK_DOWN:
1678 frame.groupSelector.moveNodeDown(node, true);
1680 case KeyEvent.VK_LEFT:
1683 frame.groupSelector.moveNodeLeft(node, true);
1685 case KeyEvent.VK_RIGHT:
1688 frame.groupSelector.moveNodeRight(node, true);
1690 case KeyEvent.VK_PAGE_DOWN:
1691 frame.nextTab.actionPerformed(null);
1694 case KeyEvent.VK_PAGE_UP:
1695 frame.prevTab.actionPerformed(null);
1699 } else if (keyCode == KeyEvent.VK_ENTER){
1701 try { runCommand("edit");
1702 } catch (Throwable ex) {
1703 ex.printStackTrace();
1710 public void setupMainPanel() {
1711 //System.out.println("setupMainPanel");
1712 //splitPane = new com.jgoodies.uif_lite.component.UIFSplitPane(JSplitPane.VERTICAL_SPLIT);
1713 splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
1714 splitPane.setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE);
1715 // We replace the default FocusTraversalPolicy with a subclass
1716 // that only allows FieldEditor components to gain keyboard focus,
1717 // if there is an entry editor open.
1718 /*splitPane.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() {
1719 protected boolean accept(Component c) {
1721 if (showing == null)
1722 return super.accept(c);
1724 return (super.accept(c) &&
1725 (c instanceof FieldEditor));
1731 splitPane.setTopComponent(mainTable.getPane());
1734 // If an entry is currently being shown, make sure it stays shown,
1735 // otherwise set the bottom component to null.
1736 if (mode == SHOWING_PREVIEW) {
1737 mode = SHOWING_NOTHING;
1738 int row = mainTable.findEntry(currentPreview.entry);
1740 mainTable.setRowSelectionInterval(row, row);
1743 else if (mode == SHOWING_EDITOR) {
1744 mode = SHOWING_NOTHING;
1745 /*int row = mainTable.findEntry(currentEditor.entry);
1747 mainTable.setRowSelectionInterval(row, row);
1749 //showEntryEditor(currentEditor);
1751 splitPane.setBottomComponent(null);
1754 setLayout(new BorderLayout());
1756 add(splitPane, BorderLayout.CENTER);
1758 // Set up AutoCompleters for this panel:
1759 if (Globals.prefs.getBoolean("autoComplete")) {
1760 instantiateAutoCompleters();
1763 splitPane.revalidate();
1768 public HashMap<String, AutoCompleter> getAutoCompleters() {
1769 return autoCompleters;
1772 public AutoCompleter getAutoCompleter(String fieldName) {
1773 return autoCompleters.get(fieldName);
1776 private void instantiateAutoCompleters() {
1777 autoCompleters.clear();
1778 String[] completeFields = Globals.prefs.getStringArray("autoCompleteFields");
1779 for (int i = 0; i < completeFields.length; i++) {
1780 String field = completeFields[i];
1781 autoCompleters.put(field, new AutoCompleter(field));
1783 for (BibtexEntry entry : database.getEntries()){
1784 Util.updateCompletersForEntry(autoCompleters, entry);
1790 * This method is called after a database has been parsed. The
1791 * hashmap contains the contents of all comments in the .bib file
1792 * that started with the meta flag (GUIGlobals.META_FLAG).
1793 * In this method, the meta data are input to their respective
1796 * @param meta Metadata to input.
1798 public void parseMetaData(HashMap<String, String> meta) {
1799 metaData = new MetaData(meta,database());
1804 public void refreshTable() {
1805 //System.out.println("hiding="+hidingNonHits+"\tlastHits="+lastSearchHits);
1806 // This method is called by EntryTypeForm when a field value is
1807 // stored. The table is scheduled for repaint.
1808 entryTable.assureNotEditing();
1809 //entryTable.invalidate();
1810 BibtexEntry[] bes = entryTable.getSelectedEntries();
1812 tableModel.update(lastSearchHits);
1814 tableModel.update();
1815 //tableModel.remap();
1816 if ((bes != null) && (bes.length > 0))
1817 selectEntries(bes, 0);
1819 //long toc = System.currentTimeMillis();
1820 // Util.pr("Refresh took: "+(toc-tic)+" ms");
1823 public void updatePreamble() {
1824 if (preambleEditor != null)
1825 preambleEditor.updatePreamble();
1828 public void assureStringDialogNotEditing() {
1829 if (stringDialog != null)
1830 stringDialog.assureNotEditing();
1833 public void updateStringDialog() {
1834 if (stringDialog != null)
1835 stringDialog.refreshTable();
1838 public void updateEntryPreviewToRow(BibtexEntry e) {
1842 public void adjustSplitter() {
1843 int mode = getMode();
1844 if (mode == SHOWING_PREVIEW) {
1845 splitPane.setDividerLocation(splitPane.getHeight()-GUIGlobals.PREVIEW_PANEL_HEIGHT);
1847 splitPane.setDividerLocation(GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1855 * Stores the source view in the entry editor, if one is open, has the source view
1856 * selected and the source has been edited.
1857 * @return boolean false if there is a validation error in the source panel, true otherwise.
1859 public boolean entryEditorAllowsChange() {
1860 Component c = splitPane.getBottomComponent();
1861 if ((c != null) && (c instanceof EntryEditor)) {
1862 return ((EntryEditor)c).lastSourceAccepted();
1868 public void moveFocusToEntryEditor() {
1869 Component c = splitPane.getBottomComponent();
1870 if ((c != null) && (c instanceof EntryEditor)) {
1871 new FocusRequester(c);
1875 public boolean isShowingEditor() {
1876 return ((splitPane.getBottomComponent() != null)
1877 && (splitPane.getBottomComponent() instanceof EntryEditor));
1880 public void showEntry(final BibtexEntry be) {
1881 if (showing == be) {
1882 if (splitPane.getBottomComponent() == null) {
1883 // This is the special occasion when showing is set to an
1884 // entry, but no entry editor is in fact shown. This happens
1885 // after Preferences dialog is closed, and it means that we
1886 // must make sure the same entry is shown again. We do this by
1887 // setting showing to null, and recursively calling this method.
1891 // The correct entry is already being shown. Make sure the editor
1893 ((EntryEditor)splitPane.getBottomComponent()).updateAllFields();
1902 String visName = null;
1903 if (showing != null) {
1904 visName = ((EntryEditor)splitPane.getBottomComponent()).
1905 getVisiblePanelName();
1907 if (showing != null)
1908 divLoc = splitPane.getDividerLocation();
1910 if (entryEditors.containsKey(be.getType().getName())) {
1911 // We already have an editor for this entry type.
1912 form = entryEditors.get
1913 ((be.getType().getName()));
1915 if (visName != null)
1916 form.setVisiblePanel(visName);
1917 splitPane.setBottomComponent(form);
1918 //highlightEntry(be);
1920 // We must instantiate a new editor for this type.
1921 form = new EntryEditor(frame, BasePanel.this, be);
1922 if (visName != null)
1923 form.setVisiblePanel(visName);
1924 splitPane.setBottomComponent(form);
1926 //highlightEntry(be);
1927 entryEditors.put(be.getType().getName(), form);
1931 splitPane.setDividerLocation(divLoc);
1934 splitPane.setDividerLocation
1935 (GUIGlobals.VERTICAL_DIVIDER_LOCATION);
1936 //new FocusRequester(form);
1937 //form.requestFocus();
1940 setEntryEditorEnabled(true); // Make sure it is enabled.
1944 * Get an entry editor ready to edit the given entry. If an appropriate editor is already
1945 * cached, it will be updated and returned.
1946 * @param entry The entry to be edited.
1947 * @return A suitable entry editor.
1949 public EntryEditor getEntryEditor(BibtexEntry entry) {
1951 if (entryEditors.containsKey(entry.getType().getName())) {
1952 EntryEditor visibleNow = currentEditor;
1954 // We already have an editor for this entry type.
1955 form = entryEditors.get
1956 ((entry.getType().getName()));
1958 // If the cached editor is not the same as the currently shown one,
1959 // make sure the current one stores its current edit:
1960 if ((visibleNow != null) && (form != visibleNow)) {
1961 visibleNow.storeCurrentEdit();
1964 form.switchTo(entry);
1965 //if (visName != null)
1966 // form.setVisiblePanel(visName);
1968 // We must instantiate a new editor for this type. First make sure the old one
1969 // stores its last edit:
1971 // Then start the new one:
1972 form = new EntryEditor(frame, BasePanel.this, entry);
1973 //if (visName != null)
1974 // form.setVisiblePanel(visName);
1976 entryEditors.put(entry.getType().getName(), form);
1981 public EntryEditor getCurrentEditor() {
1982 return currentEditor;
1986 * Sets the given entry editor as the bottom component in the split pane. If an entry editor already
1987 * was shown, makes sure that the divider doesn't move.
1988 * Updates the mode to SHOWING_EDITOR.
1989 * @param editor The entry editor to add.
1991 public void showEntryEditor(EntryEditor editor) {
1992 int oldSplitterLocation = -1;
1993 if (mode == SHOWING_EDITOR)
1994 oldSplitterLocation = splitPane.getDividerLocation();
1995 boolean adjustSplitter = (mode == WILL_SHOW_EDITOR);
1996 mode = SHOWING_EDITOR;
1997 currentEditor = editor;
1998 splitPane.setBottomComponent(editor);
1999 if (oldSplitterLocation > 0)
2000 splitPane.setDividerLocation(oldSplitterLocation);
2001 if (adjustSplitter) {
2003 //new FocusRequester(editor);
2008 * Sets the given preview panel as the bottom component in the split panel.
2009 * Updates the mode to SHOWING_PREVIEW.
2010 * @param preview The preview to show.
2012 public void showPreview(PreviewPanel preview) {
2013 mode = SHOWING_PREVIEW;
2014 currentPreview = preview;
2015 splitPane.setBottomComponent(preview);
2019 * Removes the bottom component.
2021 public void hideBottomComponent() {
2022 mode = SHOWING_NOTHING;
2023 splitPane.setBottomComponent(null);
2027 * This method selects the given entry, and scrolls it into view in the table.
2028 * If an entryEditor is shown, it is given focus afterwards.
2030 public void highlightEntry(final BibtexEntry be) {
2031 //SwingUtilities.invokeLater(new Thread() {
2032 // public void run() {
2033 final int row = mainTable.findEntry(be);
2035 mainTable.setRowSelectionInterval(row, row);
2036 //entryTable.setActiveRow(row);
2037 mainTable.ensureVisible(row);
2045 * This method is called from an EntryEditor when it should be closed. We relay
2046 * to the selection listener, which takes care of the rest.
2047 * @param editor The entry editor to close.
2049 public void entryEditorClosing(EntryEditor editor) {
2050 selectionListener.entryEditorClosing(editor);
2054 * This method selects the given enties.
2055 * If an entryEditor is shown, it is given focus afterwards.
2057 /*public void selectEntries(final BibtexEntry[] bes, final int toScrollTo) {
2059 SwingUtilities.invokeLater(new Thread() {
2061 int rowToScrollTo = 0;
2062 entryTable.revalidate();
2063 entryTable.clearSelection();
2064 loop: for (int i=0; i<bes.length; i++) {
2067 int row = tableModel.getNumberFromName(bes[i].getId());
2069 rowToScrollTo = row;
2071 entryTable.addRowSelectionIntervalQuietly(row, row);
2073 entryTable.ensureVisible(rowToScrollTo);
2074 Component comp = splitPane.getBottomComponent();
2075 //if (comp instanceof EntryEditor)
2076 // comp.requestFocus();
2082 * Closes the entry editor if it is showing the given entry.
2084 * @param be a <code>BibtexEntry</code> value
2086 public void ensureNotShowing(BibtexEntry be) {
2087 if ((mode == SHOWING_EDITOR) && (currentEditor.getEntry() == be)) {
2088 selectionListener.entryEditorClosing(currentEditor);
2092 public void updateEntryEditorIfShowing() {
2093 if (mode == SHOWING_EDITOR) {
2094 if (currentEditor.getType() != currentEditor.getEntry().getType()) {
2095 // The entry has changed type, so we must get a new editor.
2097 EntryEditor newEditor = getEntryEditor(currentEditor.getEntry());
2098 showEntryEditor(newEditor);
2100 currentEditor.updateAllFields();
2101 currentEditor.updateSource();
2107 * If an entry editor is showing, make sure its currently focused field
2108 * stores its changes, if any.
2110 public void storeCurrentEdit() {
2111 if (isShowingEditor()) {
2112 EntryEditor editor = (EntryEditor)splitPane.getBottomComponent();
2113 editor.storeCurrentEdit();
2119 * This method iterates through all existing entry editors in this
2120 * BasePanel, telling each to update all its instances of
2121 * FieldContentSelector. This is done to ensure that the list of words
2122 * in each selector is up-to-date after the user has made changes in
2123 * the Manage dialog.
2125 public void updateAllContentSelectors() {
2126 for (Iterator<String> i=entryEditors.keySet().iterator(); i.hasNext();) {
2127 EntryEditor ed = entryEditors.get(i.next());
2128 ed.updateAllContentSelectors();
2132 public void rebuildAllEntryEditors() {
2133 for (Iterator<String> i=entryEditors.keySet().iterator(); i.hasNext();) {
2134 EntryEditor ed = entryEditors.get(i.next());
2140 public void markBaseChanged() {
2143 // Put an asterix behind the file name to indicate the
2144 // database has changed.
2145 String oldTitle = frame.getTabTitle(this);
2146 if (!oldTitle.endsWith("*"))
2147 frame.setTabTitle(this, oldTitle+"*", frame.getTabTooltip(this));
2149 // If the status line states that the base has been saved, we
2150 // remove this message, since it is no longer relevant. If a
2151 // different message is shown, we leave it.
2152 if (frame.statusLine.getText().startsWith("Saved database"))
2156 public void markNonUndoableBaseChanged() {
2157 nonUndoableChange = true;
2161 public synchronized void markChangedOrUnChanged() {
2162 if (undoManager.hasChanged()) {
2166 else if (baseChanged && !nonUndoableChange) {
2167 baseChanged = false;
2168 if (getFile() != null)
2169 frame.setTabTitle(BasePanel.this, getFile().getName(),
2170 getFile().getAbsolutePath());
2172 frame.setTabTitle(BasePanel.this, Globals.lang("untitled"), null);
2177 * Selects a single entry, and scrolls the table to center it.
2179 * @param pos Current position of entry to select.
2182 public void selectSingleEntry(int pos) {
2183 mainTable.clearSelection();
2184 mainTable.addRowSelectionInterval(pos, pos);
2185 mainTable.scrollToCenter(pos, 0);
2189 * Selects all entries with a non-zero value in the field
2190 * @param field <code>String</code> field name.
2192 /* public void selectResults(String field) {
2193 LinkedList intervals = new LinkedList();
2194 int prevStart = -1, prevToSel = 0;
2195 // First we build a list of intervals to select, without touching the table.
2196 for (int i = 0; i < entryTable.getRowCount(); i++) {
2197 String value = (String) (database.getEntryById
2198 (tableModel.getIdForRow(i)))
2200 if ( (value != null) && !value.equals("0")) {
2205 else if (prevStart >= 0) {
2206 intervals.add(new int[] {prevStart, prevToSel});
2210 // Then select those intervals, if any.
2211 if (intervals.size() > 0) {
2212 entryTable.setSelectionListenerEnabled(false);
2213 entryTable.clearSelection();
2214 for (Iterator i=intervals.iterator(); i.hasNext();) {
2215 int[] interval = (int[])i.next();
2216 entryTable.addRowSelectionInterval(interval[0], interval[1]);
2218 entryTable.setSelectionListenerEnabled(true);
2222 public void setSearchMatcher(SearchMatcher matcher) {
2223 searchFilterList.setMatcher(matcher);
2224 showingSearch = true;
2227 public void setGroupMatcher(Matcher<BibtexEntry> matcher) {
2228 groupFilterList.setMatcher(matcher);
2229 showingGroup = true;
2232 public void stopShowingSearchResults() {
2233 searchFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2234 showingSearch = false;
2237 public void stopShowingGroup() {
2238 groupFilterList.setMatcher(NoSearchMatcher.INSTANCE);
2239 showingGroup = false;
2243 * Query whether this BasePanel is in the mode where a float search result is shown.
2244 * @return true if showing float search, false otherwise.
2246 public boolean isShowingFloatSearch() {
2247 return mainTable.isShowingFloatSearch();
2251 * Query whether this BasePanel is in the mode where a filter search result is shown.
2252 * @return true if showing filter search, false otherwise.
2254 public boolean isShowingFilterSearch() {
2255 return showingSearch;
2258 public BibtexDatabase getDatabase(){
2262 public void preambleEditorClosing() {
2263 preambleEditor = null;
2266 public void stringsClosing() {
2267 stringDialog = null;
2270 public void changeType(BibtexEntry entry, BibtexEntryType type) {
2271 changeType(new BibtexEntry[] {entry}, type);
2274 public void changeType(BibtexEntryType type) {
2275 BibtexEntry[] bes = mainTable.getSelectedEntries();
2276 changeType(bes, type);
2279 public void changeType(BibtexEntry[] bes, BibtexEntryType type) {
2281 if ((bes == null) || (bes.length == 0)) {
2282 output("First select the entries you wish to change type "+
2286 if (bes.length > 1) {
2287 int choice = JOptionPane.showConfirmDialog
2288 (this, "Multiple entries selected. Do you want to change"
2289 +"\nthe type of all these to '"+type.getName()+"'?",
2290 "Change type", JOptionPane.YES_NO_OPTION,
2291 JOptionPane.WARNING_MESSAGE);
2292 if (choice == JOptionPane.NO_OPTION)
2296 NamedCompound ce = new NamedCompound(Globals.lang("change type"));
2297 for (int i=0; i<bes.length; i++) {
2298 ce.addEdit(new UndoableChangeType(bes[i],
2301 bes[i].setType(type);
2304 output(Globals.lang("Changed type to")+" '"+type.getName()+"' "
2305 +Globals.lang("for")+" "+bes.length
2306 +" "+Globals.lang("entries")+".");
2308 undoManager.addEdit(ce);
2310 updateEntryEditorIfShowing();
2313 public boolean showDeleteConfirmationDialog(int numberOfEntries) {
2314 if (Globals.prefs.getBoolean("confirmDelete")) {
2315 String msg = Globals.lang("Really delete the selected")
2316 + " " + Globals.lang("entry") + "?",
2317 title = Globals.lang("Delete entry");
2318 if (numberOfEntries > 1) {
2319 msg = Globals.lang("Really delete the selected")
2320 + " " + numberOfEntries + " " + Globals.lang("entries") + "?";
2321 title = Globals.lang("Delete multiple entries");
2324 CheckBoxMessage cb = new CheckBoxMessage
2325 (msg, Globals.lang("Disable this confirmation dialog"), false);
2327 int answer = JOptionPane.showConfirmDialog(frame, cb, title,
2328 JOptionPane.YES_NO_OPTION,
2329 JOptionPane.QUESTION_MESSAGE);
2330 if (cb.isSelected())
2331 Globals.prefs.putBoolean("confirmDelete", false);
2332 return (answer == JOptionPane.YES_OPTION);
2338 * If the relevant option is set, autogenerate keys for all entries that are
2341 public void autoGenerateKeysBeforeSaving() {
2342 if (Globals.prefs.getBoolean("generateKeysBeforeSaving")) {
2343 NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
2344 boolean any = false;
2346 for (BibtexEntry bes : database.getEntries()){
2347 String oldKey = bes.getCiteKey();
2348 if ((oldKey == null) || (oldKey.equals(""))) {
2349 LabelPatternUtil.makeLabel(Globals.prefs.getKeyPattern(), database, bes);
2350 ce.addEdit(new UndoableKeyChange(database, bes.getId(), null,
2351 bes.getField(BibtexFields.KEY_FIELD)));
2355 // Store undo information, if any:
2358 undoManager.addEdit(ce);
2364 * Activates or deactivates the entry preview, depending on the argument.
2365 * When deactivating, makes sure that any visible preview is hidden.
2368 public void setPreviewActive(boolean enabled) {
2369 selectionListener.setPreviewActive(enabled);
2373 class UndoAction extends BaseAction {
2374 public void action() {
2376 String name = undoManager.getUndoPresentationName();
2380 } catch (CannotUndoException ex) {
2381 frame.output(Globals.lang("Nothing to undo")+".");
2383 // After everything, enable/disable the undo/redo actions
2385 //updateUndoState();
2386 //redoAction.updateRedoState();
2387 markChangedOrUnChanged();
2391 class RedoAction extends BaseAction {
2392 public void action() {
2394 String name = undoManager.getRedoPresentationName();
2398 } catch (CannotRedoException ex) {
2399 frame.output(Globals.lang("Nothing to redo")+".");
2401 // After everything, enable/disable the undo/redo actions
2403 //updateRedoState();
2404 //undoAction.updateUndoState();
2405 markChangedOrUnChanged();
2409 // Method pertaining to the ClipboardOwner interface.
2410 public void lostOwnership(Clipboard clipboard, Transferable contents) {}
2413 public void setEntryEditorEnabled(boolean enabled) {
2414 if ((showing != null) && (splitPane.getBottomComponent() instanceof EntryEditor)) {
2415 EntryEditor ed = (EntryEditor)splitPane.getBottomComponent();
2416 if (ed.isEnabled() != enabled)
2417 ed.setEnabled(enabled);
2421 public String fileMonitorHandle() { return fileMonitorHandle; }
2423 public void fileUpdated() {
2425 return; // We are just saving the file, so this message is most likely due
2426 // to bad timing. If not, we'll handle it on the next polling.
2427 //Util.pr("File '"+file.getPath()+"' has been modified.");
2428 updatedExternally = true;
2430 final ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this);
2432 // Adding the sidepane component is Swing work, so we must do this in the Swing
2434 Thread t = new Thread() {
2437 // Check if there is already a notification about external
2439 boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.NAME);
2441 sidePaneManager.hideComponent(FileUpdatePanel.NAME);
2442 sidePaneManager.unregisterComponent(FileUpdatePanel.NAME);
2444 FileUpdatePanel pan = new FileUpdatePanel(frame, BasePanel.this,
2445 sidePaneManager, getFile(), scanner);
2446 sidePaneManager.register(FileUpdatePanel.NAME, pan);
2447 sidePaneManager.show(FileUpdatePanel.NAME);
2448 //setUpdatedExternally(false);
2449 //scanner.displayResult();
2453 // Test: running scan automatically in background
2454 scanner.changeScan(BasePanel.this.getFile());
2457 } catch (InterruptedException e) {
2458 e.printStackTrace();
2461 if (scanner.changesFound()) {
2462 SwingUtilities.invokeLater(t);
2464 setUpdatedExternally(false);
2465 //System.out.println("No changes found.");
2469 public void fileRemoved() {
2470 Util.pr("File '"+getFile().getPath()+"' has been deleted.");
2475 * Perform necessary cleanup when this BasePanel is closed.
2477 public void cleanUp() {
2478 if (fileMonitorHandle != null)
2479 Globals.fileUpdateMonitor.removeUpdateListener(fileMonitorHandle);
2480 // Check if there is a FileUpdatePanel for this BasePanel being shown. If so,
2482 if (sidePaneManager.hasComponent("fileUpdate")) {
2483 FileUpdatePanel fup = (FileUpdatePanel)sidePaneManager.getComponent("fileUpdate");
2484 if (fup.getPanel() == this) {
2485 sidePaneManager.hideComponent("fileUpdate");
2490 public void setUpdatedExternally(boolean b) {
2491 updatedExternally = b;
2495 * Get an array containing the currently selected entries.
2497 * @return An array containing the selected entries.
2499 public BibtexEntry[] getSelectedEntries() {
2500 return mainTable.getSelectedEntries();
2504 * Get the file where this database was last saved to or loaded from, if any.
2506 * @return The relevant File, or null if none is defined.
2508 public File getFile() {
2509 return metaData.getFile();
2513 * Get a String containing a comma-separated list of the bibtex keys
2514 * of the selected entries.
2516 * @return A comma-separated list of the keys of the selected entries.
2518 public String getKeysForSelection() {
2519 StringBuffer result = new StringBuffer();
2520 String citeKey = "";//, message = "";
2521 boolean first = true;
2522 for (BibtexEntry bes : mainTable.getSelected()){
2523 citeKey = bes.getField(BibtexFields.KEY_FIELD);
2524 // if the key is empty we give a warning and ignore this entry
2525 if (citeKey == null || citeKey.equals(""))
2528 result.append(citeKey);
2531 result.append(",").append(citeKey);
2534 return result.toString();
2537 public GroupSelector getGroupSelector() {
2538 return frame.groupSelector;
2542 public boolean isUpdatedExternally() {
2543 return updatedExternally;
2547 public String getFileMonitorHandle() {
2548 return fileMonitorHandle;
2552 public void setFileMonitorHandle(String fileMonitorHandle) {
2553 this.fileMonitorHandle = fileMonitorHandle;
2556 public SidePaneManager getSidePaneManager() {
2557 return sidePaneManager;
2561 public void setNonUndoableChange(boolean nonUndoableChange) {
2562 this.nonUndoableChange = nonUndoableChange;
2565 public void setBaseChanged(boolean baseChanged) {
2566 this.baseChanged = baseChanged;
2570 public void setSaving(boolean saving) {
2571 this.saving = saving;