/* Copyright (C) 2003-2012 JabRef contributors. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package net.sf.jabref; import java.awt.BorderLayout; import java.awt.Component; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Toolkit; import java.awt.datatransfer.Clipboard; import java.awt.datatransfer.ClipboardOwner; import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.StringSelection; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.awt.event.ActionEvent; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.File; import java.io.IOException; import java.io.StringReader; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Vector; import javax.swing.AbstractAction; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSplitPane; import javax.swing.JTextArea; import javax.swing.SwingUtilities; import javax.swing.tree.TreePath; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import net.sf.jabref.autocompleter.AbstractAutoCompleter; import net.sf.jabref.autocompleter.AutoCompleterFactory; import net.sf.jabref.autocompleter.NameFieldAutoCompleter; import net.sf.jabref.collab.ChangeScanner; import net.sf.jabref.collab.FileUpdateListener; import net.sf.jabref.collab.FileUpdatePanel; import net.sf.jabref.export.ExportToClipboardAction; import net.sf.jabref.export.FileActions; import net.sf.jabref.export.SaveDatabaseAction; import net.sf.jabref.export.SaveException; import net.sf.jabref.export.SaveSession; import net.sf.jabref.export.layout.Layout; import net.sf.jabref.export.layout.LayoutHelper; import net.sf.jabref.external.*; import net.sf.jabref.groups.GroupSelector; import net.sf.jabref.groups.GroupTreeNode; import net.sf.jabref.gui.*; import net.sf.jabref.imports.AppendDatabaseAction; import net.sf.jabref.imports.BibtexParser; import net.sf.jabref.imports.SPIRESFetcher; import net.sf.jabref.journals.AbbreviateAction; import net.sf.jabref.journals.UnabbreviateAction; import net.sf.jabref.labelPattern.LabelPatternUtil; import net.sf.jabref.labelPattern.SearchFixDuplicateLabels; import net.sf.jabref.search.NoSearchMatcher; import net.sf.jabref.search.SearchMatcher; import net.sf.jabref.specialfields.SpecialFieldAction; import net.sf.jabref.specialfields.Priority; import net.sf.jabref.specialfields.Quality; import net.sf.jabref.specialfields.Rank; import net.sf.jabref.specialfields.Relevance; import net.sf.jabref.specialfields.SpecialFieldDatabaseChangeListener; import net.sf.jabref.specialfields.SpecialFieldValue; import net.sf.jabref.sql.DBConnectDialog; import net.sf.jabref.sql.DBStrings; import net.sf.jabref.sql.DbConnectAction; import net.sf.jabref.sql.DBExporterAndImporterFactory; import net.sf.jabref.sql.SQLUtil; import net.sf.jabref.sql.exporter.DBExporter; import net.sf.jabref.undo.CountingUndoManager; import net.sf.jabref.undo.NamedCompound; import net.sf.jabref.undo.UndoableChangeType; import net.sf.jabref.undo.UndoableInsertEntry; import net.sf.jabref.undo.UndoableKeyChange; import net.sf.jabref.undo.UndoableRemoveEntry; import net.sf.jabref.wizard.text.gui.TextInputDialog; import ca.odell.glazedlists.FilterList; import ca.odell.glazedlists.event.ListEvent; import ca.odell.glazedlists.event.ListEventListener; import ca.odell.glazedlists.matchers.Matcher; import com.jgoodies.forms.builder.DefaultFormBuilder; import com.jgoodies.forms.layout.FormLayout; import com.jgoodies.uif_lite.component.UIFSplitPane; public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener { public final static int SHOWING_NOTHING=0, SHOWING_PREVIEW=1, SHOWING_EDITOR=2, WILL_SHOW_EDITOR=3; /* * The database shown in this panel. */ BibtexDatabase database; private int mode=0; private EntryEditor currentEditor = null; private PreviewPanel currentPreview = null; boolean tmp = true; private MainTableSelectionListener selectionListener = null; private ListEventListener<BibtexEntry> groupsHighlightListener; UIFSplitPane contentPane = new UIFSplitPane(); JSplitPane splitPane; JabRefFrame frame; String fileMonitorHandle = null; boolean saving = false, updatedExternally = false; private String encoding; GridBagLayout gbl = new GridBagLayout(); GridBagConstraints con = new GridBagConstraints(); HashMap<String, AbstractAutoCompleter> autoCompleters = new HashMap<String, AbstractAutoCompleter>(); // Hashtable that holds as keys the names of the fields where // autocomplete is active, and references to the autocompleter objects. NameFieldAutoCompleter searchCompleter = null; AutoCompleteListener searchCompleteListener = null; // The undo manager. public CountingUndoManager undoManager = new CountingUndoManager(this); UndoAction undoAction = new UndoAction(); RedoAction redoAction = new RedoAction(); private List<BibtexEntry> previousEntries = new ArrayList<BibtexEntry>(), nextEntries = new ArrayList<BibtexEntry>(); //ExampleFileFilter fileFilter; // File filter for .bib files. private boolean baseChanged = false; private boolean nonUndoableChange = false; // Used to track whether the base has changed since last save. //EntryTableModel tableModel = null; //public EntryTable entryTable = null; public MainTable mainTable = null; public MainTableFormat tableFormat = null; public FilterList<BibtexEntry> searchFilterList = null, groupFilterList = null; public RightClickMenu rcm; BibtexEntry showing = null; // Variable to prevent erroneous update of back/forward histories at the time // when a Back or Forward operation is being processed: private boolean backOrForwardInProgress = false; // To indicate which entry is currently shown. public HashMap<String, EntryEditor> entryEditors = new HashMap<String, EntryEditor>(); // To contain instantiated entry editors. This is to save time // in switching between entries. //HashMap entryTypeForms = new HashMap(); // Hashmap to keep track of which entries currently have open // EntryTypeForm dialogs. PreambleEditor preambleEditor = null; // Keeps track of the preamble dialog if it is open. StringDialog stringDialog = null; // Keeps track of the string dialog if it is open. SaveDatabaseAction saveAction; CleanUpAction cleanUpAction; /** * The group selector component for this database. Instantiated by the * SidePaneManager if necessary, or from this class if merging groups from a * different database. */ //GroupSelector groupSelector; public boolean showingSearch = false, showingGroup = false, sortingBySearchResults = false, coloringBySearchResults = false, hidingNonHits = false, sortingByGroup = false, sortingByCiteSeerResults = false, coloringByGroup = false; int lastSearchHits = -1; // The number of hits in the latest search. // Potential use in hiding non-hits completely. // MetaData parses, keeps and writes meta data. MetaData metaData; private boolean suppressOutput = false; private HashMap<String, Object> actions = new HashMap<String, Object>(); private SidePaneManager sidePaneManager; /** * Create a new BasePanel with an empty database. * @param frame The application window. */ public BasePanel(JabRefFrame frame) { this.sidePaneManager = Globals.sidePaneManager; database = new BibtexDatabase(); metaData = new MetaData(); metaData.initializeNewDatabase(); this.frame = frame; setupActions(); setupMainPanel(); encoding = Globals.prefs.get("defaultEncoding"); //System.out.println("Default: "+encoding); } public BasePanel(JabRefFrame frame, BibtexDatabase db, File file, MetaData metaData, String encoding) { init(frame, db, file, metaData, encoding); } private void init(JabRefFrame frame, BibtexDatabase db, File file, MetaData metaData, String encoding) { assert(frame != null); assert(db != null); //file may be null assert(encoding != null); assert(metaData != null); this.encoding = encoding; this.metaData = metaData; // System.out.println(encoding); //super(JSplitPane.HORIZONTAL_SPLIT, true); this.sidePaneManager = Globals.sidePaneManager; this.frame = frame; database = db; setupActions(); setupMainPanel(); metaData.setFile(file); if (file == null) { if (!database.getEntries().isEmpty()) { // if the database is not empty and no file is assigned, // the database came from an import and has to be treated somehow // -> mark as changed this.baseChanged = true; } } else { // Register so we get notifications about outside changes to the file. try { fileMonitorHandle = Globals.fileUpdateMonitor.addUpdateListener(this, file); } catch (IOException ex) { System.err.println(ex); } } } public boolean isBaseChanged(){ return baseChanged; } public int getMode() { return mode; } //Done by MrDlib public void setMode(int mode) { this.mode = mode; } //Done by MrDlib public BibtexDatabase database() { return database; } public MetaData metaData() { return metaData; } public JabRefFrame frame() { return frame; } public JabRefPreferences prefs() { return Globals.prefs; } public String getEncoding() { return encoding; } public void setEncoding(String encoding) { this.encoding = encoding; } public void output(String s) { if (!suppressOutput) frame.output(s); } private void setupActions() { saveAction = new SaveDatabaseAction(this); cleanUpAction = new CleanUpAction(this); actions.put("undo", undoAction); actions.put("redo", redoAction); actions.put("focusTable", new BaseAction() { public void action() throws Throwable { new FocusRequester(mainTable); } }); // The action for opening an entry editor. actions.put("edit", new BaseAction() { public void action() { /*System.out.println(Globals.focusListener.getFocused().getClass().getName()); if (Globals.focusListener.getFocused() instanceof FieldEditor) new FocusRequester(mainTable); else*/ selectionListener.editSignalled(); } /* if (isShowingEditor()) { new FocusRequester(splitPane.getBottomComponent()); return; } frame.block(); //(new Thread() { //public void run() { int clickedOn = -1; // We demand that one and only one row is selected. if (entryTable.getSelectedRowCount() == 1) { clickedOn = entryTable.getSelectedRow(); } if (clickedOn >= 0) { String id = tableModel.getIdForRow(clickedOn); BibtexEntry be = database.getEntryById(id); showEntry(be); if (splitPane.getBottomComponent() != null) { new FocusRequester(splitPane.getBottomComponent()); } } frame.unblock(); } */ }); actions.put("test",// new AccessLinksForEntries.SaveWithLinkedFiles(this)); new FindFullTextAction(this)); // The action for saving a database. actions.put("save", saveAction); actions.put("saveAs", new BaseAction() { public void action() throws Throwable { saveAction.saveAs(); } }); actions.put("saveSelectedAs", new BaseAction () { public void action() throws Throwable { String chosenFile = FileDialogs.getNewFile(frame, new File(Globals.prefs.get("workingDirectory")), ".bib", JFileChooser.SAVE_DIALOG, false); if (chosenFile != null) { File expFile = new File(chosenFile); if (!expFile.exists() || (JOptionPane.showConfirmDialog (frame, "'"+expFile.getName()+"' "+ Globals.lang("exists. Overwrite file?"), Globals.lang("Save database"), JOptionPane.OK_CANCEL_OPTION) == JOptionPane.OK_OPTION)) { saveDatabase(expFile, true, Globals.prefs.get("defaultEncoding")); //runCommand("save"); frame.getFileHistory().newFile(expFile.getPath()); frame.output(Globals.lang("Saved selected to")+" '" +expFile.getPath()+"'."); } } } }); // The action for copying selected entries. actions.put("copy", new BaseAction() { public void action() { BibtexEntry[] bes = mainTable.getSelectedEntries(); if ((bes != null) && (bes.length > 0)) { TransferableBibtexEntry trbe = new TransferableBibtexEntry(bes); // ! look at ClipBoardManager Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(trbe, BasePanel.this); output(Globals.lang("Copied")+" "+(bes.length>1 ? bes.length+" " +Globals.lang("entries") : "1 "+Globals.lang("entry")+".")); } else { // The user maybe selected a single cell. int[] rows = mainTable.getSelectedRows(), cols = mainTable.getSelectedColumns(); if ((cols.length == 1) && (rows.length == 1)) { // Copy single value. Object o = mainTable.getValueAt(rows[0], cols[0]); if (o != null) { StringSelection ss = new StringSelection(o.toString()); Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(ss, BasePanel.this); output(Globals.lang("Copied cell contents")+"."); } } } } }); actions.put("cut", new BaseAction() { public void action() throws Throwable { runCommand("copy"); BibtexEntry[] bes = mainTable.getSelectedEntries(); //int row0 = mainTable.getSelectedRow(); if ((bes != null) && (bes.length > 0)) { // Create a CompoundEdit to make the action undoable. NamedCompound ce = new NamedCompound (Globals.lang(bes.length > 1 ? "cut entries" : "cut entry")); // Loop through the array of entries, and delete them. for (int i=0; i<bes.length; i++) { database.removeEntry(bes[i].getId()); ensureNotShowing(bes[i]); ce.addEdit(new UndoableRemoveEntry (database, bes[i], BasePanel.this)); } //entryTable.clearSelection(); frame.output(Globals.lang("Cut_pr")+" "+ (bes.length>1 ? bes.length +" "+ Globals.lang("entries") : Globals.lang("entry"))+"."); ce.end(); undoManager.addEdit(ce); markBaseChanged(); // Reselect the entry in the first prev. selected position: /*if (row0 >= entryTable.getRowCount()) row0 = entryTable.getRowCount()-1; if (row0 >= 0) entryTable.addRowSelectionInterval(row0, row0);*/ } } }); actions.put("delete", new BaseAction() { public void action() { BibtexEntry[] bes = mainTable.getSelectedEntries(); if ((bes != null) && (bes.length > 0)) { boolean goOn = showDeleteConfirmationDialog(bes.length); if (!goOn) { return; } else { // Create a CompoundEdit to make the action undoable. NamedCompound ce = new NamedCompound (Globals.lang(bes.length > 1 ? "delete entries" : "delete entry")); // Loop through the array of entries, and delete them. for (int i = 0; i < bes.length; i++) { database.removeEntry(bes[i].getId()); ensureNotShowing(bes[i]); ce.addEdit(new UndoableRemoveEntry(database, bes[i], BasePanel.this)); } markBaseChanged(); frame.output(Globals.lang("Deleted") + " " + (bes.length > 1 ? bes.length + " " + Globals.lang("entries") : Globals.lang("entry")) + "."); ce.end(); undoManager.addEdit(ce); //entryTable.clearSelection(); } // Reselect the entry in the first prev. selected position: /*if (row0 >= entryTable.getRowCount()) row0 = entryTable.getRowCount()-1; if (row0 >= 0) { final int toSel = row0; // SwingUtilities.invokeLater(new Runnable() { public void run() { entryTable.addRowSelectionInterval(toSel, toSel); //entryTable.ensureVisible(toSel); } }); */ } } }); // The action for pasting entries or cell contents. // Edited by Seb Wills <saw27@mrao.cam.ac.uk> on 14-Apr-04: // - more robust detection of available content flavors (doesn't only look at first one offered) // - support for parsing string-flavor clipboard contents which are bibtex entries. // This allows you to (a) paste entire bibtex entries from a text editor, web browser, etc // (b) copy and paste entries between multiple instances of JabRef (since // only the text representation seems to get as far as the X clipboard, at least on my system) actions.put("paste", new BaseAction() { public void action() { // Get clipboard contents, and see if TransferableBibtexEntry is among the content flavors offered Transferable content = Toolkit.getDefaultToolkit() .getSystemClipboard().getContents(null); if (content != null) { BibtexEntry[] bes = null; if (content.isDataFlavorSupported(TransferableBibtexEntry.entryFlavor)) { // We have determined that the clipboard data is a set of entries. try { bes = (BibtexEntry[])(content.getTransferData(TransferableBibtexEntry.entryFlavor)); } catch (UnsupportedFlavorException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } else if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) { try { BibtexParser bp = new BibtexParser (new java.io.StringReader( (String) (content.getTransferData( DataFlavor.stringFlavor)))); BibtexDatabase db = bp.parse().getDatabase(); Util.pr("Parsed " + db.getEntryCount() + " entries from clipboard text"); if(db.getEntryCount()>0) { bes = db.getEntries().toArray(new BibtexEntry[db.getEntryCount()]); } } catch (UnsupportedFlavorException ex) { ex.printStackTrace(); } catch (Throwable ex) { ex.printStackTrace(); } } // finally we paste in the entries (if any), which either came from TransferableBibtexEntries // or were parsed from a string if ((bes != null) && (bes.length > 0)) { NamedCompound ce = new NamedCompound (Globals.lang(bes.length > 1 ? "paste entries" : "paste entry")); // Store the first inserted bibtexentry. // bes[0] does not work as bes[0] is first clonded, // then inserted. // This entry is used to open up an entry editor // for the first inserted entry. BibtexEntry firstBE = null; for (int i=0; i<bes.length; i++) { try { BibtexEntry be = (BibtexEntry)(bes[i].clone()); if (firstBE == null) firstBE = be; Util.setAutomaticFields(be, Globals.prefs.getBoolean("overwriteOwner"), Globals.prefs.getBoolean("overwriteTimeStamp")); // We have to clone the // entries, since the pasted // entries must exist // independently of the copied // ones. be.setId(Util.createNeutralId()); database.insertEntry(be); addToSelectedGroup(be); ce.addEdit(new UndoableInsertEntry (database, be, BasePanel.this)); } catch (KeyCollisionException ex) { Util.pr("KeyCollisionException... this shouldn't happen."); } } ce.end(); undoManager.addEdit(ce); //entryTable.clearSelection(); //entryTable.revalidate(); output(Globals.lang("Pasted") + " " + (bes.length > 1 ? bes.length + " " + Globals.lang("entries") : "1 " + Globals.lang("entry")) + "."); markBaseChanged(); if (Globals.prefs.getBoolean("autoOpenForm")) { selectionListener.editSignalled(firstBE); } highlightEntry(firstBE); } } } }); actions.put("selectAll", new BaseAction() { public void action() { mainTable.selectAll(); } }); // The action for opening the preamble editor actions.put("editPreamble", new BaseAction() { public void action() { if (preambleEditor == null) { PreambleEditor form = new PreambleEditor (frame, BasePanel.this, database, Globals.prefs); Util.placeDialog(form, frame); form.setVisible(true); preambleEditor = form; } else { preambleEditor.setVisible(true); } } }); // The action for opening the string editor actions.put("editStrings", new BaseAction() { public void action() { if (stringDialog == null) { StringDialog form = new StringDialog (frame, BasePanel.this, database, Globals.prefs); Util.placeDialog(form, frame); form.setVisible(true); stringDialog = form; } else { stringDialog.setVisible(true); } } }); // The action for toggling the groups interface actions.put("toggleGroups", new BaseAction() { public void action() { sidePaneManager.toggle("groups"); frame.groupToggle.setSelected(sidePaneManager.isComponentVisible("groups")); } }); // action for collecting database strings from user actions.put("dbConnect", new DbConnectAction(this)); // action for exporting database to external SQL database actions.put("dbExport", new AbstractWorker () { String errorMessage = null; boolean connectToDB = false; // run first, in EDT: public void init() { DBStrings dbs = metaData.getDBStrings(); // get DBStrings from user if necessary if (!dbs.isConfigValid()) { // init DB strings if necessary if (! dbs.isInitialized()) { dbs.initialize(); } // show connection dialog DBConnectDialog dbd = new DBConnectDialog(frame(), dbs); Util.placeDialog(dbd, BasePanel.this ); dbd.setVisible(true); connectToDB = dbd.getConnectToDB(); // store database strings if (connectToDB) { dbs = dbd.getDBStrings(); metaData.setDBStrings(dbs); dbd.dispose(); } } else { connectToDB = true; } } // run second, on a different thread: public void run() { if (connectToDB) { DBStrings dbs = metaData.getDBStrings(); try { /*boolean okToExport = null!=metaData.getFile(); if (!okToExport) { okToExport = false; int response = JOptionPane.showConfirmDialog(null, "You need to save your database in the disk \n" + "before saving. Save it now?", "Database is not saved", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if(response == JOptionPane.YES_OPTION) { try { saveAction.saveAs(); okToExport = (null!=metaData.getFile()); } catch (Throwable e) { e.printStackTrace(); } } } if (okToExport) {*/ frame.output(Globals.lang("Attempting SQL export...")); DBExporterAndImporterFactory factory = new DBExporterAndImporterFactory(); DBExporter exporter = factory.getExporter(dbs.getServerType()); exporter.exportDatabaseToDBMS(database, metaData, null, dbs, frame); dbs.isConfigValid(true); //} //else // errorMessage = "Database was not exported. Your database must be saved \nbefore exporting to a SQL database"; } catch (Exception ex) { String preamble = "Could not export to SQL database for the following reason:"; errorMessage = SQLUtil.getExceptionMessage(ex); ex.printStackTrace(); dbs.isConfigValid(false); JOptionPane.showMessageDialog(frame, Globals.lang(preamble) + "\n" +errorMessage, Globals.lang("Export to SQL database"), JOptionPane.ERROR_MESSAGE); } metaData.setDBStrings(dbs); } } // run third, on EDT: public void update() { // if no error, report success if (errorMessage == null) { if (connectToDB) { frame.output(Globals.lang("%0 export successful")); } } // show an error dialog if an error occurred else { String preamble = "Could not export to SQL database for the following reason:"; frame.output(Globals.lang(preamble) + " " + errorMessage); JOptionPane.showMessageDialog(frame, Globals.lang(preamble) + "\n" + errorMessage, Globals.lang("Export to SQL database"), JOptionPane.ERROR_MESSAGE); errorMessage = null; } } }); actions.put(FindUnlinkedFilesDialog.ACTION_COMMAND, new BaseAction() { @Override public void action() throws Throwable { FindUnlinkedFilesDialog dialog = new FindUnlinkedFilesDialog(frame, frame, BasePanel.this); Util.placeDialog(dialog, frame); dialog.setVisible(true); } }); // The action for auto-generating keys. actions.put("makeKey", new AbstractWorker() { //int[] rows; List<BibtexEntry> entries; int numSelected; boolean cancelled = false; // Run first, in EDT: public void init() { entries = new ArrayList<BibtexEntry>(Arrays.asList(getSelectedEntries())); //rows = entryTable.getSelectedRows() ; numSelected = entries.size(); if (entries.size() == 0) { // None selected. Inform the user to select entries first. JOptionPane.showMessageDialog(frame, Globals.lang("First select the entries you want keys to be generated for."), Globals.lang("Autogenerate BibTeX key"), JOptionPane.INFORMATION_MESSAGE); return ; } frame.block(); output(Globals.lang("Generating BibTeX key for")+" "+ numSelected+" "+(numSelected>1 ? Globals.lang("entries") : Globals.lang("entry"))+"..."); } // Run second, on a different thread: public void run() { BibtexEntry bes = null ; NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys")); // First check if any entries have keys set already. If so, possibly remove // them from consideration, or warn about overwriting keys. loop: for (Iterator<BibtexEntry> i=entries.iterator(); i.hasNext();) { bes = i.next(); if (bes.getField(BibtexFields.KEY_FIELD) != null) { if (Globals.prefs.getBoolean("avoidOverwritingKey")) // Remove the entry, because its key is already set: i.remove(); else if (Globals.prefs.getBoolean("warnBeforeOverwritingKey")) { // Ask if the user wants to cancel the operation: CheckBoxMessage cbm = new CheckBoxMessage(Globals.lang("One or more keys will be overwritten. Continue?"), Globals.lang("Disable this confirmation dialog"), false); int answer = JOptionPane.showConfirmDialog(frame, cbm, Globals.lang("Overwrite keys"), JOptionPane.YES_NO_OPTION); if (cbm.isSelected()) Globals.prefs.putBoolean("warnBeforeOverwritingKey", false); if (answer == JOptionPane.NO_OPTION) { // Ok, break off the operation. cancelled = true; return; } // No need to check more entries, because the user has already confirmed // that it's ok to overwrite keys: break loop; } } } HashMap<BibtexEntry, Object> oldvals = new HashMap<BibtexEntry, Object>(); // Iterate again, removing already set keys. This is skipped if overwriting // is disabled, since all entries with keys set will have been removed. if (!Globals.prefs.getBoolean("avoidOverwritingKey")) for (Iterator<BibtexEntry> i=entries.iterator(); i.hasNext();) { bes = i.next(); // Store the old value: oldvals.put(bes, bes.getField(BibtexFields.KEY_FIELD)); database.setCiteKeyForEntry(bes.getId(), null); } // Finally, set the new keys: for (Iterator<BibtexEntry> i=entries.iterator(); i.hasNext();) { bes = i.next(); bes = LabelPatternUtil.makeLabel(metaData, database, bes); ce.addEdit(new UndoableKeyChange (database, bes.getId(), (String)oldvals.get(bes), bes.getField(BibtexFields.KEY_FIELD))); } ce.end(); undoManager.addEdit(ce); } // Run third, on EDT: public void update() { database.setFollowCrossrefs(true); if (cancelled) { frame.unblock(); return; } markBaseChanged() ; numSelected = entries.size(); //////////////////////////////////////////////////////////////////////////////// // Prevent selection loss for autogenerated BibTeX-Keys //////////////////////////////////////////////////////////////////////////////// for (Iterator<BibtexEntry> i=entries.iterator(); i.hasNext();) { final BibtexEntry bibEntry = i.next(); SwingUtilities.invokeLater(new Runnable() { public void run() { final int row = mainTable.findEntry( bibEntry ); if (row >= 0 && mainTable.getSelectedRowCount() < entries.size()) mainTable.addRowSelectionInterval(row, row); } }); } //////////////////////////////////////////////////////////////////////////////// output(Globals.lang("Generated BibTeX key for")+" "+ numSelected+" "+(numSelected!=1 ? Globals.lang("entries") : Globals.lang("entry"))); frame.unblock(); } }); // The action for cleaning up entry. actions.put("Cleanup", cleanUpAction); actions.put("search", new BaseAction() { public void action() { //sidePaneManager.togglePanel("search"); sidePaneManager.show("search"); //boolean on = sidePaneManager.isPanelVisible("search"); frame.searchToggle.setSelected(true); if (true) frame.getSearchManager().startSearch(); } }); actions.put("toggleSearch", new BaseAction() { public void action() { //sidePaneManager.togglePanel("search"); sidePaneManager.toggle("search"); boolean on = sidePaneManager.isComponentVisible("search"); frame.searchToggle.setSelected(on); if (on) frame.getSearchManager().startSearch(); } }); actions.put("incSearch", new BaseAction() { public void action() { sidePaneManager.show("search"); frame.searchToggle.setSelected(true); frame.getSearchManager().startIncrementalSearch(); } }); // The action for copying the selected entry's key. actions.put("copyKey", new BaseAction() { public void action() { BibtexEntry[] bes = mainTable.getSelectedEntries(); if ((bes != null) && (bes.length > 0)) { storeCurrentEdit(); //String[] keys = new String[bes.length]; Vector<Object> keys = new Vector<Object>(); // Collect all non-null keys. for (int i=0; i<bes.length; i++) if (bes[i].getField(BibtexFields.KEY_FIELD) != null) keys.add(bes[i].getField(BibtexFields.KEY_FIELD)); if (keys.size() == 0) { output("None of the selected entries have BibTeX keys."); return; } StringBuffer sb = new StringBuffer((String)keys.elementAt(0)); for (int i=1; i<keys.size(); i++) { sb.append(','); sb.append((String)keys.elementAt(i)); } StringSelection ss = new StringSelection(sb.toString()); Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(ss, BasePanel.this); if (keys.size() == bes.length) // All entries had keys. output(Globals.lang((bes.length > 1) ? "Copied keys" : "Copied key")+"."); else output(Globals.lang("Warning")+": "+(bes.length-keys.size()) +" "+Globals.lang("out of")+" "+bes.length+" "+ Globals.lang("entries have undefined BibTeX key")+"."); } } }); // The action for copying a cite for the selected entry. actions.put("copyCiteKey", new BaseAction() { public void action() { BibtexEntry[] bes = mainTable.getSelectedEntries(); if ((bes != null) && (bes.length > 0)) { storeCurrentEdit(); //String[] keys = new String[bes.length]; Vector<Object> keys = new Vector<Object>(); // Collect all non-null keys. for (int i=0; i<bes.length; i++) if (bes[i].getField(BibtexFields.KEY_FIELD) != null) keys.add(bes[i].getField(BibtexFields.KEY_FIELD)); if (keys.size() == 0) { output("None of the selected entries have BibTeX keys."); return; } StringBuffer sb = new StringBuffer((String)keys.elementAt(0)); for (int i=1; i<keys.size(); i++) { sb.append(','); sb.append((String)keys.elementAt(i)); } StringSelection ss = new StringSelection ("\\cite{"+sb.toString()+"}"); Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(ss, BasePanel.this); if (keys.size() == bes.length) // All entries had keys. output(bes.length > 1 ? Globals.lang("Copied keys") : Globals.lang("Copied key")+"."); else output(Globals.lang("Warning")+": "+(bes.length-keys.size()) +" "+Globals.lang("out of")+" "+bes.length+" "+ Globals.lang("entries have undefined BibTeX key")+"."); } } }); // The action for copying the BibTeX key and the title for the first selected entry actions.put("copyKeyAndTitle", new BaseAction() { public void action() { BibtexEntry[] bes = mainTable.getSelectedEntries(); if ((bes != null) && (bes.length > 0)) { storeCurrentEdit(); // OK: in a future version, this string should be configurable to allow arbitrary exports StringReader sr = new StringReader("\\bibtexkey - \\begin{title}\\format[RemoveBrackets]{\\title}\\end{title}\n"); Layout layout; try { layout = new LayoutHelper(sr).getLayoutFromText(Globals.FORMATTER_PACKAGE); } catch (Exception e) { e.printStackTrace(); return; } StringBuffer sb = new StringBuffer(); int copied = 0; // Collect all non-null keys. for (int i=0; i<bes.length; i++) if (bes[i].getField(BibtexFields.KEY_FIELD) != null) { copied++; sb.append(layout.doLayout(bes[i], database)); } if (copied==0) { output("None of the selected entries have BibTeX keys."); return; } StringSelection ss = new StringSelection(sb.toString()); Toolkit.getDefaultToolkit().getSystemClipboard() .setContents(ss, BasePanel.this); if (copied == bes.length) // All entries had keys. output(Globals.lang((bes.length > 1) ? "Copied keys" : "Copied key")+"."); else output(Globals.lang("Warning")+": "+(copied) +" "+Globals.lang("out of")+" "+bes.length+" "+ Globals.lang("entries have undefined BibTeX key")+"."); } } }); actions.put("mergeDatabase", new AppendDatabaseAction(frame, this)); actions.put("openFile", new BaseAction() { public void action() { (new Thread() { public void run() { BibtexEntry[] bes = mainTable.getSelectedEntries(); String field = "ps"; if ((bes != null) && (bes.length == 1)) { FileListEntry entry = null; FileListTableModel tm = new FileListTableModel(); tm.setContent(bes[0].getField("file")); for (int i=0; i< tm.getRowCount(); i++) { FileListEntry flEntry = tm.getEntry(i); if (flEntry.getType().getName().toLowerCase().equals("pdf") || flEntry.getType().getName().toLowerCase().equals("ps")) { entry = flEntry; break; } } if (entry != null) { try { Util.openExternalFileAnyFormat(metaData, entry.getLink(), entry.getType()); output(Globals.lang("External viewer called") + "."); } catch (IOException e) { output(Globals.lang("Could not open link")); e.printStackTrace(); } return; } // If we didn't find anything in the "file" field, check "ps" and "pdf" fields: Object link = bes[0].getField("ps"); if (bes[0].getField("pdf") != null) { link = bes[0].getField("pdf"); field = "pdf"; } String filepath = null; if (link != null) { filepath = link.toString(); } else { if (Globals.prefs.getBoolean("runAutomaticFileSearch")) { /* The search can lead to an unexpected 100% CPU usage which is perceived as a bug, if the search incidentally starts at a directory with lots of stuff below. It is now disabled by default. */ // see if we can fall back to a filename based on the bibtex key final Collection<BibtexEntry> entries = new ArrayList<BibtexEntry>(); entries.add(bes[0]); ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection(); ArrayList<File> dirs = new ArrayList<File>(); if (metaData.getFileDirectory(GUIGlobals.FILE_FIELD).length > 0) { String[] mdDirs = metaData.getFileDirectory(GUIGlobals.FILE_FIELD); for (int i = 0; i < mdDirs.length; i++) { dirs.add(new File(mdDirs[i])); } } Collection<String> extensions = new ArrayList<String>(); for (int i = 0; i < types.length; i++) { final ExternalFileType type = types[i]; extensions.add(type.getExtension()); } // Run the search operation: Map<BibtexEntry, List<File>> result; if (Globals.prefs.getBoolean(JabRefPreferences.USE_REG_EXP_SEARCH_KEY)) { String regExp = Globals.prefs.get(JabRefPreferences.REG_EXP_SEARCH_EXPRESSION_KEY); result = RegExpFileSearch.findFilesForSet(entries, extensions, dirs, regExp); } else result = Util.findAssociatedFiles(entries, extensions, dirs); if (result.get(bes[0]) != null) { List<File> res = result.get(bes[0]); if (res.size() > 0) { filepath = res.get(0).getPath(); int index = filepath.lastIndexOf('.'); if ((index >= 0) && (index < filepath.length()-1)) { String extension = filepath.substring(index+1); ExternalFileType type = Globals.prefs.getExternalFileTypeByExt(extension); if (type != null) { try { Util.openExternalFileAnyFormat(metaData, filepath, type); output(Globals.lang("External viewer called") + "."); return; } catch (IOException ex) { output(Globals.lang("Error") + ": " + ex.getMessage()); } } } // TODO: add code for opening the file } } /*String basefile; Object key = bes[0].getField(BibtexFields.KEY_FIELD); if (key != null) { basefile = key.toString(); final ExternalFileType[] types = Globals.prefs.getExternalFileTypeSelection(); final String sep = System.getProperty("file.separator"); String dir = metaData.getFileDirectory(GUIGlobals.FILE_FIELD); if ((dir != null) && (dir.length() > 0)) { if (dir.endsWith(sep)) { dir = dir.substring(0, dir.length() - sep.length()); } for (int i = 0; i < types.length; i++) { String found = Util.findPdf(basefile, types[i].getExtension(), dir, new OpenFileFilter("." + types[i].getExtension())); if (found != null) { filepath = dir + sep + found; break; } } } }*/ } } if (filepath != null) { //output(Globals.lang("Calling external viewer...")); try { Util.openExternalViewer(metaData(), filepath, field); output(Globals.lang("External viewer called") + "."); } catch (IOException ex) { output(Globals.lang("Error") + ": " + ex.getMessage()); } } else output(Globals.lang( "No pdf or ps defined, and no file matching Bibtex key found") + "."); } else output(Globals.lang("No entries or multiple entries selected.")); } }).start(); } }); actions.put("addFileLink", new AttachFileAction(this)); actions.put("openExternalFile", new BaseAction() { public void action() { (new Thread() { public void run() { BibtexEntry[] bes = mainTable.getSelectedEntries(); String field = GUIGlobals.FILE_FIELD; if ((bes != null) && (bes.length == 1)) { Object link = bes[0].getField(field); if (link == null) { runCommand("openFile"); // Fall back on PDF/PS fields??? return; } FileListTableModel tableModel = new FileListTableModel(); tableModel.setContent((String)link); if (tableModel.getRowCount() == 0) { runCommand("openFile"); // Fall back on PDF/PS fields??? return; } FileListEntry flEntry = tableModel.getEntry(0); ExternalFileMenuItem item = new ExternalFileMenuItem (frame(), bes[0], "", flEntry.getLink(), flEntry.getType().getIcon(), metaData(), flEntry.getType()); item.openLink(); } else output(Globals.lang("No entries or multiple entries selected.")); } }).start(); } }); actions.put("openUrl", new BaseAction() { public void action() { BibtexEntry[] bes = mainTable.getSelectedEntries(); String field = "doi"; if ((bes != null) && (bes.length == 1)) { Object link = bes[0].getField("doi"); if (bes[0].getField("url") != null) { link = bes[0].getField("url"); field = "url"; } if (link != null) { //output(Globals.lang("Calling external viewer...")); try { Util.openExternalViewer(metaData(), link.toString(), field); output(Globals.lang("External viewer called")+"."); } catch (IOException ex) { output(Globals.lang("Error") + ": " + ex.getMessage()); } } else { // No URL or DOI found in the "url" and "doi" fields. // Look for web links in the "file" field as a fallback: FileListEntry entry = null; FileListTableModel tm = new FileListTableModel(); tm.setContent(bes[0].getField("file")); for (int i=0; i< tm.getRowCount(); i++) { FileListEntry flEntry = tm.getEntry(i); if (flEntry.getType().getName().toLowerCase().equals("url") || flEntry.getType().getName().toLowerCase().equals("ps")) { entry = flEntry; break; } } if (entry != null) { try { Util.openExternalFileAnyFormat(metaData, entry.getLink(), entry.getType()); output(Globals.lang("External viewer called") + "."); } catch (IOException e) { output(Globals.lang("Could not open link")); e.printStackTrace(); } return; } else output(Globals.lang("No url defined")+"."); } } else output(Globals.lang("No entries or multiple entries selected.")); } }); actions.put("openSpires", new BaseAction() { public void action() { BibtexEntry[] bes = mainTable.getSelectedEntries(); if ((bes != null) && (bes.length == 1)) { Object link = null; if (bes[0].getField("eprint") != null) link = SPIRESFetcher.constructUrlFromEprint(bes[0].getField("eprint").toString()); else if (bes[0].getField("slaccitation") != null) link = SPIRESFetcher.constructUrlFromSlaccitation(bes[0].getField("slaccitation").toString()); if (link != null) { //output(Globals.lang("Calling external viewer...")); try { Util.openExternalViewer(metaData(), link.toString(), "url"); output(Globals.lang("External viewer called")+"."); } catch (IOException ex) { output(Globals.lang("Error") + ": " + ex.getMessage()); } } else output(Globals.lang("No url defined")+"."); } else output(Globals.lang("No entries or multiple entries selected.")); } }); /* * It looks like this action was not being supported for SPIRES anyway * so we don't bother to implement it. actions.put("openInspire", new BaseAction() { public void action() { BibtexEntry[] bes = mainTable.getSelectedEntries(); if ((bes != null) && (bes.length == 1)) { Object link = null; if (bes[0].getField("eprint") != null) link = INSPIREFetcher.constructUrlFromEprint(bes[0].getField("eprint").toString()); else if (bes[0].getField("slaccitation") != null) link = INSPIREFetcher.constructUrlFromSlaccitation(bes[0].getField("slaccitation").toString()); if (link != null) { //output(Globals.lang("Calling external viewer...")); try { Util.openExternalViewer(metaData(), link.toString(), "url"); output(Globals.lang("External viewer called")+"."); } catch (IOException ex) { output(Globals.lang("Error") + ": " + ex.getMessage()); } } else output(Globals.lang("No url defined")+"."); } else output(Globals.lang("No entries or multiple entries selected.")); } }); */ actions.put("replaceAll", new BaseAction() { public void action() { ReplaceStringDialog rsd = new ReplaceStringDialog(frame); rsd.setVisible(true); if (!rsd.okPressed()) return; int counter = 0; NamedCompound ce = new NamedCompound(Globals.lang("Replace string")); if (!rsd.selOnly()) { for (BibtexEntry entry : database.getEntries()){ counter += rsd.replace(entry, ce); } } else { BibtexEntry[] bes = mainTable.getSelectedEntries(); for (int i=0; i<bes.length; i++) counter += rsd.replace(bes[i], ce); } output(Globals.lang("Replaced")+" "+counter+" "+ Globals.lang(counter==1?"occurence":"occurences")+"."); if (counter > 0) { ce.end(); undoManager.addEdit(ce); markBaseChanged(); } } }); actions.put("dupliCheck", new BaseAction() { public void action() { DuplicateSearch ds = new DuplicateSearch(BasePanel.this); ds.start(); } }); /*actions.put("strictDupliCheck", new BaseAction() { public void action() { StrictDuplicateSearch ds = new StrictDuplicateSearch(BasePanel.this); ds.start(); } });*/ actions.put("plainTextImport", new BaseAction() { public void action() { // get Type of new entry EntryTypeDialog etd = new EntryTypeDialog(frame); Util.placeDialog(etd, BasePanel.this); etd.setVisible(true); BibtexEntryType tp = etd.getChoice(); if (tp == null) return; String id = Util.createNeutralId(); BibtexEntry bibEntry = new BibtexEntry(id, tp) ; TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this, "import", true, bibEntry) ; Util.placeDialog(tidialog, BasePanel.this); tidialog.setVisible(true); if (tidialog.okPressed()) { Util.setAutomaticFields(Arrays.asList(new BibtexEntry[] {bibEntry}), false, false, false); insertEntry(bibEntry) ; } } }); // The action starts the "import from plain text" dialog /*actions.put("importPlainText", new BaseAction() { public void action() { BibtexEntry bibEntry = null ; // try to get the first marked entry BibtexEntry[] bes = entryTable.getSelectedEntries(); if ((bes != null) && (bes.length > 0)) bibEntry = bes[0] ; if (bibEntry != null) { // Create an UndoableInsertEntry object. undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this)); TextInputDialog tidialog = new TextInputDialog(frame, BasePanel.this, "import", true, bibEntry) ; Util.placeDialog(tidialog, BasePanel.this); tidialog.setVisible(true); if (tidialog.okPressed()) { output(Globals.lang("changed ")+" '" +bibEntry.getType().getName().toLowerCase()+"' " +Globals.lang("entry")+"."); refreshTable(); int row = tableModel.getNumberFromName(bibEntry.getId()); entryTable.clearSelection(); entryTable.scrollTo(row); markBaseChanged(); // The database just changed. if (Globals.prefs.getBoolean("autoOpenForm")) { showEntry(bibEntry); } } } } }); */ actions.put("markEntries", new AbstractWorker() { private int besLength = -1; public void run() { NamedCompound ce = new NamedCompound(Globals.lang("Mark entries")); BibtexEntry[] bes = mainTable.getSelectedEntries(); besLength = bes.length; for (int i=0; i<bes.length; i++) { Util.markEntry(bes[i], 1, true, ce); } ce.end(); undoManager.addEdit(ce); } public void update() { markBaseChanged(); output(Globals.lang("Marked selected")+" "+Globals.lang(besLength>0?"entry":"entries")); } }); actions.put("unmarkEntries", new BaseAction() { public void action() { try { NamedCompound ce = new NamedCompound(Globals.lang("Unmark entries")); BibtexEntry[] bes = mainTable.getSelectedEntries(); if (bes == null) return; for (int i=0; i<bes.length; i++) { Util.unmarkEntry(bes[i], false, database, ce); } ce.end(); undoManager.addEdit(ce); markBaseChanged(); output(Globals.lang("Unmarked selected")+" "+Globals.lang(bes.length>0?"entry":"entries")); } catch (Throwable ex) { ex.printStackTrace(); } } }); actions.put("unmarkAll", new BaseAction() { public void action() { NamedCompound ce = new NamedCompound(Globals.lang("Unmark all")); for (BibtexEntry be : database.getEntries()){ Util.unmarkEntry(be, false, database, ce); } ce.end(); undoManager.addEdit(ce); markBaseChanged(); } }); actions.put(Relevance.getInstance().getValues().get(0).getActionName(), new SpecialFieldAction(frame, Relevance.getInstance(), Relevance.getInstance().getValues().get(0).getFieldValue(), true, Globals.lang("Marked entries as relevant"), "Marked %0 entries as relevant")); actions.put(Quality.getInstance().getValues().get(0).getActionName(), new SpecialFieldAction(frame, Quality.getInstance(), Quality.getInstance().getValues().get(0).getFieldValue(), true, Globals.lang("Marked entries' quality as good"), "Set quality of %0 entries to good")); for (SpecialFieldValue prio: Priority.getInstance().getValues()) { actions.put(prio.getActionName(), prio.getAction(this.frame)); } for (SpecialFieldValue prio: Rank.getInstance().getValues()) { actions.put(prio.getActionName(), prio.getAction(this.frame)); } actions.put("togglePreview", new BaseAction() { public void action() { boolean enabled = !Globals.prefs.getBoolean("previewEnabled"); Globals.prefs.putBoolean("previewEnabled", enabled); frame.setPreviewActive(enabled); frame.previewToggle.setSelected(enabled); } }); actions.put("toggleHighlightGroupsMatchingAny", new BaseAction() { public void action() { boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAny"); Globals.prefs.putBoolean("highlightGroupsMatchingAny", enabled); frame.highlightAny.setSelected(enabled); if (enabled) { frame.highlightAll.setSelected(false); Globals.prefs.putBoolean("highlightGroupsMatchingAll", false); } // ping the listener so it updates: groupsHighlightListener.listChanged(null); } }); actions.put("toggleHighlightGroupsMatchingAll", new BaseAction() { public void action() { boolean enabled = !Globals.prefs.getBoolean("highlightGroupsMatchingAll"); Globals.prefs.putBoolean("highlightGroupsMatchingAll", enabled); frame.highlightAll.setSelected(enabled); if (enabled) { frame.highlightAny.setSelected(false); Globals.prefs.putBoolean("highlightGroupsMatchingAny", false); } // ping the listener so it updates: groupsHighlightListener.listChanged(null); } }); actions.put("switchPreview", new BaseAction() { public void action() { selectionListener.switchPreview(); } }); actions.put("manageSelectors", new BaseAction() { public void action() { ContentSelectorDialog2 csd = new ContentSelectorDialog2 (frame, frame, BasePanel.this, false, metaData, null); Util.placeDialog(csd, frame); csd.setVisible(true); } }); actions.put("exportToClipboard", new ExportToClipboardAction(frame, database())); actions.put("sendAsEmail", new SendAsEMailAction(frame)); actions.put("writeXMP", new WriteXMPAction(this)); actions.put("abbreviateIso", new AbbreviateAction(this, true)); actions.put("abbreviateMedline", new AbbreviateAction(this, false)); actions.put("unabbreviate", new UnabbreviateAction(this)); actions.put("autoSetPdf", new AutoSetExternalFileForEntries(this, "pdf")); actions.put("autoSetPs", new AutoSetExternalFileForEntries(this, "ps")); actions.put("autoSetFile", new SynchronizeFileField(this)); actions.put("back", new BaseAction() { public void action() throws Throwable { back(); } }); actions.put("forward", new BaseAction() { public void action() throws Throwable { forward(); } }); actions.put("resolveDuplicateKeys", new SearchFixDuplicateLabels(this)); //actions.put("downloadFullText", new FindFullTextAction(this)); } /** * This method is called from JabRefFrame is a database specific * action is requested by the user. Runs the command if it is * defined, or prints an error message to the standard error * stream. * * @param _command The name of the command to run. */ public void runCommand(String _command) { final String command = _command; //(new Thread() { // public void run() { if (actions.get(command) == null) Util.pr("No action defined for'" + command + "'"); else { Object o = actions.get(command); try { if (o instanceof BaseAction) ((BaseAction)o).action(); else { // This part uses Spin's features: Worker wrk = ((AbstractWorker)o).getWorker(); // The Worker returned by getWorker() has been wrapped // by Spin.off(), which makes its methods be run in // a different thread from the EDT. CallBack clb = ((AbstractWorker)o).getCallBack(); ((AbstractWorker)o).init(); // This method runs in this same thread, the EDT. // Useful for initial GUI actions, like printing a message. // The CallBack returned by getCallBack() has been wrapped // by Spin.over(), which makes its methods be run on // the EDT. wrk.run(); // Runs the potentially time-consuming action // without freezing the GUI. The magic is that THIS line // of execution will not continue until run() is finished. clb.update(); // Runs the update() method on the EDT. } } catch (Throwable ex) { // If the action has blocked the JabRefFrame before crashing, we need to unblock it. // The call to unblock will simply hide the glasspane, so there is no harm in calling // it even if the frame hasn't been blocked. frame.unblock(); ex.printStackTrace(); } } // } //}).start(); } private boolean saveDatabase(File file, boolean selectedOnly, String encoding) throws SaveException { SaveSession session; frame.block(); try { if (!selectedOnly) session = FileActions.saveDatabase(database, metaData, file, Globals.prefs, false, false, encoding, false); else session = FileActions.savePartOfDatabase(database, metaData, file, Globals.prefs, mainTable.getSelectedEntries(), encoding); } catch (UnsupportedCharsetException ex2) { JOptionPane.showMessageDialog(frame, Globals.lang("Could not save file. " +"Character encoding '%0' is not supported.", encoding), Globals.lang("Save database"), JOptionPane.ERROR_MESSAGE); throw new SaveException("rt"); } catch (SaveException ex) { if (ex.specificEntry()) { // Error occured during processing of // be. Highlight it: int row = mainTable.findEntry(ex.getEntry()), topShow = Math.max(0, row-3); mainTable.setRowSelectionInterval(row, row); mainTable.scrollTo(topShow); showEntry(ex.getEntry()); } else ex.printStackTrace(); JOptionPane.showMessageDialog (frame, Globals.lang("Could not save file") +".\n"+ex.getMessage(), Globals.lang("Save database"), JOptionPane.ERROR_MESSAGE); throw new SaveException("rt"); } finally { frame.unblock(); } boolean commit = true; if (!session.getWriter().couldEncodeAll()) { DefaultFormBuilder builder = new DefaultFormBuilder(new FormLayout("left:pref, 4dlu, fill:pref", "")); JTextArea ta = new JTextArea(session.getWriter().getProblemCharacters()); ta.setEditable(false); builder.append(Globals.lang("The chosen encoding '%0' could not encode the following characters: ", session.getEncoding())); builder.append(ta); builder.append(Globals.lang("What do you want to do?")); String tryDiff = Globals.lang("Try different encoding"); int answer = JOptionPane.showOptionDialog(frame, builder.getPanel(), Globals.lang("Save database"), JOptionPane.YES_NO_CANCEL_OPTION, JOptionPane.WARNING_MESSAGE, null, new String[] {Globals.lang("Save"), tryDiff, Globals.lang("Cancel")}, tryDiff); if (answer == JOptionPane.NO_OPTION) { // The user wants to use another encoding. Object choice = JOptionPane.showInputDialog(frame, Globals.lang("Select encoding"), Globals.lang("Save database"), JOptionPane.QUESTION_MESSAGE, null, Globals.ENCODINGS, encoding); if (choice != null) { String newEncoding = (String)choice; return saveDatabase(file, selectedOnly, newEncoding); } else commit = false; } else if (answer == JOptionPane.CANCEL_OPTION) commit = false; } try { if (commit) { session.commit(); this.encoding = encoding; // Make sure to remember which encoding we used. } else session.cancel(); } catch (IOException e) { e.printStackTrace(); } return commit; } /** * This method is called from JabRefFrame when the user wants to * create a new entry. If the argument is null, the user is * prompted for an entry type. * * @param type The type of the entry to create. * @return The newly created BibtexEntry or null the operation was canceled by the user. */ public BibtexEntry newEntry(BibtexEntryType type) { if (type == null) { // Find out what type is wanted. EntryTypeDialog etd = new EntryTypeDialog(frame); // We want to center the dialog, to make it look nicer. Util.placeDialog(etd, frame); etd.setVisible(true); type = etd.getChoice(); } if (type != null) { // Only if the dialog was not cancelled. String id = Util.createNeutralId(); final BibtexEntry be = new BibtexEntry(id, type); try { database.insertEntry(be); // Set owner/timestamp if options are enabled: ArrayList<BibtexEntry> list = new ArrayList<BibtexEntry>(); list.add(be); Util.setAutomaticFields(list, true, true, false); // Create an UndoableInsertEntry object. undoManager.addEdit(new UndoableInsertEntry(database, be, BasePanel.this)); output(Globals.lang("Added new")+" '"+type.getName().toLowerCase()+"' " +Globals.lang("entry")+"."); // We are going to select the new entry. Before that, make sure that we are in // show-entry mode. If we aren't already in that mode, enter the WILL_SHOW_EDITOR // mode which makes sure the selection will trigger display of the entry editor // and adjustment of the splitter. if (mode != SHOWING_EDITOR) { mode = WILL_SHOW_EDITOR; } int row = mainTable.findEntry(be); if (row >= 0) highlightEntry(be); // Selects the entry. The selection listener will open the editor. else { // The entry is not visible in the table, perhaps due to a filtering search // or group selection. Show the entry editor anyway: showEntry(be); } markBaseChanged(); // The database just changed. new FocusRequester(getEntryEditor(be)); //Add the new entry to the group(s) selected in the Group Panel addToSelectedGroup(be); return be; } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); } } return null; } /** * This method is called to add a new entry to a group (or a set of groups) * in case the Group View is selected and one or more groups are marked * @param bibEntry The new entry. */ private void addToSelectedGroup(final BibtexEntry bibEntry) { if (Globals.prefs.getBoolean("autoAssignGroup")){ if (frame.groupToggle.isSelected()){ BibtexEntry[] entries = {bibEntry}; TreePath[] selection = frame.groupSelector.getGroupsTree().getSelectionPaths(); if (selection != null) { // it is possible that the user selected nothing. Therefore, checked for "!= null" for (TreePath tree : selection){ ((GroupTreeNode)(tree.getLastPathComponent())).addToGroup(entries); } } this.updateEntryEditorIfShowing(); this.getGroupSelector().valueChanged(null); } } } /** * This method is called from JabRefFrame when the user wants to * create a new entry. * @param bibEntry The new entry. */ public void insertEntry(BibtexEntry bibEntry) { if (bibEntry != null) { try { database.insertEntry(bibEntry) ; if (Globals.prefs.getBoolean("useOwner")) // Set owner field to default value Util.setAutomaticFields(bibEntry, true, true); // Create an UndoableInsertEntry object. undoManager.addEdit(new UndoableInsertEntry(database, bibEntry, BasePanel.this)); output(Globals.lang("Added new")+" '" +bibEntry.getType().getName().toLowerCase()+"' " +Globals.lang("entry")+"."); markBaseChanged(); // The database just changed. if (Globals.prefs.getBoolean("autoOpenForm")) { selectionListener.editSignalled(bibEntry); } highlightEntry(bibEntry); } catch (KeyCollisionException ex) { Util.pr(ex.getMessage()); } } } public void updateTableFont() { mainTable.updateFont(); } public void createMainTable() { //Comparator comp = new FieldComparator("author"); GlazedEntrySorter eventList = new GlazedEntrySorter(database.getEntryMap()); // Must initialize sort columns somehow: database.addDatabaseChangeListener(eventList); database.addDatabaseChangeListener(SpecialFieldDatabaseChangeListener.getInstance()); groupFilterList = new FilterList<BibtexEntry>(eventList.getTheList(), NoSearchMatcher.INSTANCE); searchFilterList = new FilterList<BibtexEntry>(groupFilterList, NoSearchMatcher.INSTANCE); //final SortedList sortedList = new SortedList(searchFilterList, null); tableFormat = new MainTableFormat(this); tableFormat.updateTableFormat(); //EventTableModel tableModel = new EventTableModel(sortedList, tableFormat); mainTable = new MainTable(tableFormat, searchFilterList, frame, this); selectionListener = new MainTableSelectionListener(this, mainTable); mainTable.updateFont(); mainTable.addSelectionListener(selectionListener); mainTable.addMouseListener(selectionListener); mainTable.addKeyListener(selectionListener); mainTable.addFocusListener(selectionListener); // Add the listener that will take care of highlighting groups as the selection changes: groupsHighlightListener = new ListEventListener<BibtexEntry>() { public void listChanged(ListEvent<BibtexEntry> listEvent) { if (Globals.prefs.getBoolean("highlightGroupsMatchingAny")) getGroupSelector().showMatchingGroups( mainTable.getSelectedEntries(), false); else if (Globals.prefs.getBoolean("highlightGroupsMatchingAll")) getGroupSelector().showMatchingGroups( mainTable.getSelectedEntries(), true); else // no highlight getGroupSelector().showMatchingGroups(null, true); } }; mainTable.addSelectionListener(groupsHighlightListener); mainTable.getActionMap().put("cut", new AbstractAction() { public void actionPerformed(ActionEvent e) { try { runCommand("cut"); } catch (Throwable ex) { ex.printStackTrace(); } } }); mainTable.getActionMap().put("copy", new AbstractAction() { public void actionPerformed(ActionEvent e) { try { runCommand("copy"); } catch (Throwable ex) { ex.printStackTrace(); } } }); mainTable.getActionMap().put("paste", new AbstractAction() { public void actionPerformed(ActionEvent e) { try { runCommand("paste"); } catch (Throwable ex) { ex.printStackTrace(); } } }); mainTable.addKeyListener(new KeyAdapter() { public void keyPressed(KeyEvent e) { final int keyCode = e.getKeyCode(); final TreePath path = frame.groupSelector.getSelectionPath(); final GroupTreeNode node = path == null ? null : (GroupTreeNode) path.getLastPathComponent(); if (e.isControlDown()) { switch (keyCode) { // The up/down/left/rightkeystrokes are displayed in the // GroupSelector's popup menu, so if they are to be changed, // edit GroupSelector.java accordingly! case KeyEvent.VK_UP: e.consume(); if (node != null) frame.groupSelector.moveNodeUp(node, true); break; case KeyEvent.VK_DOWN: e.consume(); if (node != null) frame.groupSelector.moveNodeDown(node, true); break; case KeyEvent.VK_LEFT: e.consume(); if (node != null) frame.groupSelector.moveNodeLeft(node, true); break; case KeyEvent.VK_RIGHT: e.consume(); if (node != null) frame.groupSelector.moveNodeRight(node, true); break; case KeyEvent.VK_PAGE_DOWN: frame.nextTab.actionPerformed(null); e.consume(); break; case KeyEvent.VK_PAGE_UP: frame.prevTab.actionPerformed(null); e.consume(); break; } } else if (keyCode == KeyEvent.VK_ENTER){ e.consume(); try { runCommand("edit"); } catch (Throwable ex) { ex.printStackTrace(); } } } }); } public void setupMainPanel() { //System.out.println("setupMainPanel"); //splitPane = new com.jgoodies.uif_lite.component.UIFSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); splitPane.setDividerSize(GUIGlobals.SPLIT_PANE_DIVIDER_SIZE); // We replace the default FocusTraversalPolicy with a subclass // that only allows FieldEditor components to gain keyboard focus, // if there is an entry editor open. /*splitPane.setFocusTraversalPolicy(new LayoutFocusTraversalPolicy() { protected boolean accept(Component c) { Util.pr("jaa"); if (showing == null) return super.accept(c); else return (super.accept(c) && (c instanceof FieldEditor)); } });*/ createMainTable(); for (EntryEditor ee : entryEditors.values()) { ee.validateAllFields(); } splitPane.setTopComponent(mainTable.getPane()); //setupTable(); // If an entry is currently being shown, make sure it stays shown, // otherwise set the bottom component to null. if (mode == SHOWING_PREVIEW) { mode = SHOWING_NOTHING; int row = mainTable.findEntry(currentPreview.entry); if (row >= 0) mainTable.setRowSelectionInterval(row, row); } else if (mode == SHOWING_EDITOR) { mode = SHOWING_NOTHING; /*int row = mainTable.findEntry(currentEditor.entry); if (row >= 0) mainTable.setRowSelectionInterval(row, row); */ //showEntryEditor(currentEditor); } else splitPane.setBottomComponent(null); setLayout(new BorderLayout()); removeAll(); add(splitPane, BorderLayout.CENTER); // Set up name autocompleter for search: instantiateSearchAutoCompleter(); // Set up AutoCompleters for this panel: if (Globals.prefs.getBoolean("autoComplete")) { instantiateAutoCompleters(); } splitPane.revalidate(); revalidate(); repaint(); } public void updateSearchManager() { frame.getSearchManager().setAutoCompleteListener(searchCompleteListener); } public HashMap<String, AbstractAutoCompleter> getAutoCompleters() { return autoCompleters; } public AbstractAutoCompleter getAutoCompleter(String fieldName) { return autoCompleters.get(fieldName); } private void instantiateSearchAutoCompleter() { //if (!Globals.prefs.getBoolean("searchAutoComplete")) // return; searchCompleter = new NameFieldAutoCompleter(new String[] {"author", "editor"}, true); HashMap<String, AbstractAutoCompleter> hm = new HashMap<String, AbstractAutoCompleter>(); hm.put("x", searchCompleter); for (BibtexEntry entry : database.getEntries()){ Util.updateCompletersForEntry(hm, entry); } searchCompleteListener = new AutoCompleteListener(searchCompleter); searchCompleteListener.setConsumeEnterKey(false); // So you don't have to press Enter twice } private void instantiateAutoCompleters() { autoCompleters.clear(); String[] completeFields = Globals.prefs.getStringArray("autoCompleteFields"); for (int i = 0; i < completeFields.length; i++) { String field = completeFields[i]; AbstractAutoCompleter autoCompleter = AutoCompleterFactory.getFor(field); autoCompleters.put(field, autoCompleter ); } for (BibtexEntry entry : database.getEntries()){ Util.updateCompletersForEntry(autoCompleters, entry); } addJournalListToAutoCompleter(); addContentSelectorValuesToAutoCompleters(); } /** * For all fields with both autocompletion and content selector, add content selector * values to the autocompleter list: */ public void addContentSelectorValuesToAutoCompleters() { for (String field : autoCompleters.keySet()) { AbstractAutoCompleter ac = autoCompleters.get(field); if (metaData.getData(Globals.SELECTOR_META_PREFIX + field) != null) { Vector<String> items = metaData.getData(Globals.SELECTOR_META_PREFIX + field); if (items != null) { Iterator<String> i = items.iterator(); while (i.hasNext()) ac.addWordToIndex(i.next()); } } } } /** * If an autocompleter exists for the "journal" field, add all * journal names in the journal abbreviation list to this autocompleter. */ public void addJournalListToAutoCompleter() { if (autoCompleters.containsKey("journal")) { AbstractAutoCompleter ac = autoCompleters.get("journal"); Set<String> journals = Globals.journalAbbrev.getJournals().keySet(); for (String journal : journals) ac.addWordToIndex(journal); } } /* public void refreshTable() { //System.out.println("hiding="+hidingNonHits+"\tlastHits="+lastSearchHits); // This method is called by EntryTypeForm when a field value is // stored. The table is scheduled for repaint. entryTable.assureNotEditing(); //entryTable.invalidate(); BibtexEntry[] bes = entryTable.getSelectedEntries(); if (hidingNonHits) tableModel.update(lastSearchHits); else tableModel.update(); //tableModel.remap(); if ((bes != null) && (bes.length > 0)) selectEntries(bes, 0); //long toc = System.currentTimeMillis(); // Util.pr("Refresh took: "+(toc-tic)+" ms"); } */ public void updatePreamble() { if (preambleEditor != null) preambleEditor.updatePreamble(); } public void assureStringDialogNotEditing() { if (stringDialog != null) stringDialog.assureNotEditing(); } public void updateStringDialog() { if (stringDialog != null) stringDialog.refreshTable(); } public void updateEntryPreviewToRow(BibtexEntry e) { } public void adjustSplitter() { int mode = getMode(); if (mode == SHOWING_PREVIEW) { splitPane.setDividerLocation(splitPane.getHeight()-Globals.prefs.getInt("previewPanelHeight")); } else { splitPane.setDividerLocation(splitPane.getHeight()-Globals.prefs.getInt("entryEditorHeight")); } } /** * Stores the source view in the entry editor, if one is open, has the source view * selected and the source has been edited. * @return boolean false if there is a validation error in the source panel, true otherwise. */ public boolean entryEditorAllowsChange() { Component c = splitPane.getBottomComponent(); if ((c != null) && (c instanceof EntryEditor)) { return ((EntryEditor)c).lastSourceAccepted(); } else return true; } public void moveFocusToEntryEditor() { Component c = splitPane.getBottomComponent(); if ((c != null) && (c instanceof EntryEditor)) { new FocusRequester(c); } } public boolean isShowingEditor() { return ((splitPane.getBottomComponent() != null) && (splitPane.getBottomComponent() instanceof EntryEditor)); } public void showEntry(final BibtexEntry be) { if (getShowing() == be) { if (splitPane.getBottomComponent() == null) { // This is the special occasion when showing is set to an // entry, but no entry editor is in fact shown. This happens // after Preferences dialog is closed, and it means that we // must make sure the same entry is shown again. We do this by // setting showing to null, and recursively calling this method. newEntryShowing(null); showEntry(be); } else { // The correct entry is already being shown. Make sure the editor // is updated. ((EntryEditor)splitPane.getBottomComponent()).updateAllFields(); } return; } EntryEditor form; int divLoc = -1; String visName = null; if (getShowing() != null) { if (isShowingEditor()) { visName = ((EntryEditor) splitPane.getBottomComponent()).getVisiblePanelName(); } } if (getShowing() != null) divLoc = splitPane.getDividerLocation(); if (entryEditors.containsKey(be.getType().getName())) { // We already have an editor for this entry type. form = entryEditors.get ((be.getType().getName())); form.switchTo(be); if (visName != null) form.setVisiblePanel(visName); splitPane.setBottomComponent(form); //highlightEntry(be); } else { // We must instantiate a new editor for this type. form = new EntryEditor(frame, BasePanel.this, be); if (visName != null) form.setVisiblePanel(visName); splitPane.setBottomComponent(form); //highlightEntry(be); entryEditors.put(be.getType().getName(), form); } if (divLoc > 0) { splitPane.setDividerLocation(divLoc); } else splitPane.setDividerLocation (splitPane.getHeight()-Globals.prefs.getInt("entryEditorHeight")); //new FocusRequester(form); //form.requestFocus(); newEntryShowing(be); setEntryEditorEnabled(true); // Make sure it is enabled. } /** * Get an entry editor ready to edit the given entry. If an appropriate editor is already * cached, it will be updated and returned. * @param entry The entry to be edited. * @return A suitable entry editor. */ public EntryEditor getEntryEditor(BibtexEntry entry) { EntryEditor form; if (entryEditors.containsKey(entry.getType().getName())) { EntryEditor visibleNow = currentEditor; // We already have an editor for this entry type. form = entryEditors.get ((entry.getType().getName())); // If the cached editor is not the same as the currently shown one, // make sure the current one stores its current edit: if ((visibleNow != null) && (form != visibleNow)) { visibleNow.storeCurrentEdit(); } form.switchTo(entry); //if (visName != null) // form.setVisiblePanel(visName); } else { // We must instantiate a new editor for this type. First make sure the old one // stores its last edit: storeCurrentEdit(); // Then start the new one: form = new EntryEditor(frame, BasePanel.this, entry); //if (visName != null) // form.setVisiblePanel(visName); entryEditors.put(entry.getType().getName(), form); } return form; } public EntryEditor getCurrentEditor() { return currentEditor; } /** * Sets the given entry editor as the bottom component in the split pane. If an entry editor already * was shown, makes sure that the divider doesn't move. * Updates the mode to SHOWING_EDITOR. * @param editor The entry editor to add. */ public void showEntryEditor(EntryEditor editor) { int oldSplitterLocation = -1; if (mode == SHOWING_EDITOR) Globals.prefs.putInt("entryEditorHeight", splitPane.getHeight() - splitPane.getDividerLocation()); else if (mode == SHOWING_PREVIEW) Globals.prefs.putInt("previewPanelHeight", splitPane.getHeight()-splitPane.getDividerLocation()); mode = SHOWING_EDITOR; currentEditor = editor; splitPane.setBottomComponent(editor); if (editor.getEntry() != getShowing()) newEntryShowing(editor.getEntry()); adjustSplitter(); } /** * Sets the given preview panel as the bottom component in the split panel. * Updates the mode to SHOWING_PREVIEW. * @param preview The preview to show. */ public void showPreview(PreviewPanel preview) { mode = SHOWING_PREVIEW; currentPreview = preview; splitPane.setBottomComponent(preview); } /** * Removes the bottom component. */ public void hideBottomComponent() { mode = SHOWING_NOTHING; splitPane.setBottomComponent(null); } /** * This method selects the given entry, and scrolls it into view in the table. * If an entryEditor is shown, it is given focus afterwards. */ public void highlightEntry(final BibtexEntry be) { //SwingUtilities.invokeLater(new Thread() { // public void run() { final int row = mainTable.findEntry(be); if (row >= 0) { mainTable.setRowSelectionInterval(row, row); //entryTable.setActiveRow(row); mainTable.ensureVisible(row); } // } //}); } /** * This method is called from an EntryEditor when it should be closed. We relay * to the selection listener, which takes care of the rest. * @param editor The entry editor to close. */ public void entryEditorClosing(EntryEditor editor) { // Store divider location for next time: Globals.prefs.putInt("entryEditorHeight", splitPane.getHeight()-splitPane.getDividerLocation()); selectionListener.entryEditorClosing(editor); } /** * This method selects the given enties. * If an entryEditor is shown, it is given focus afterwards. */ /*public void selectEntries(final BibtexEntry[] bes, final int toScrollTo) { SwingUtilities.invokeLater(new Thread() { public void run() { int rowToScrollTo = 0; entryTable.revalidate(); entryTable.clearSelection(); loop: for (int i=0; i<bes.length; i++) { if (bes[i] == null) continue loop; int row = tableModel.getNumberFromName(bes[i].getId()); if (i==toScrollTo) rowToScrollTo = row; if (row >= 0) entryTable.addRowSelectionIntervalQuietly(row, row); } entryTable.ensureVisible(rowToScrollTo); Component comp = splitPane.getBottomComponent(); //if (comp instanceof EntryEditor) // comp.requestFocus(); } }); } */ /** * Closes the entry editor if it is showing the given entry. * * @param be a <code>BibtexEntry</code> value */ public void ensureNotShowing(BibtexEntry be) { if ((mode == SHOWING_EDITOR) && (currentEditor.getEntry() == be)) { selectionListener.entryEditorClosing(currentEditor); } } public void updateEntryEditorIfShowing() { if (mode == SHOWING_EDITOR) { if (currentEditor.getType() != currentEditor.getEntry().getType()) { // The entry has changed type, so we must get a new editor. newEntryShowing(null); EntryEditor newEditor = getEntryEditor(currentEditor.getEntry()); showEntryEditor(newEditor); } else { currentEditor.updateAllFields(); currentEditor.updateSource(); } } } /** * If an entry editor is showing, make sure its currently focused field * stores its changes, if any. */ public void storeCurrentEdit() { if (isShowingEditor()) { EntryEditor editor = (EntryEditor)splitPane.getBottomComponent(); editor.storeCurrentEdit(); } } /** * This method iterates through all existing entry editors in this * BasePanel, telling each to update all its instances of * FieldContentSelector. This is done to ensure that the list of words * in each selector is up-to-date after the user has made changes in * the Manage dialog. */ public void updateAllContentSelectors() { for (Iterator<String> i=entryEditors.keySet().iterator(); i.hasNext();) { EntryEditor ed = entryEditors.get(i.next()); ed.updateAllContentSelectors(); } } public void rebuildAllEntryEditors() { for (Iterator<String> i=entryEditors.keySet().iterator(); i.hasNext();) { EntryEditor ed = entryEditors.get(i.next()); ed.rebuildPanels(); } } public void markBaseChanged() { baseChanged = true; // Put an asterix behind the file name to indicate the // database has changed. String oldTitle = frame.getTabTitle(this); if (!oldTitle.endsWith("*")) { frame.setTabTitle(this, oldTitle+"*", frame.getTabTooltip(this)); frame.setWindowTitle(); } // If the status line states that the base has been saved, we // remove this message, since it is no longer relevant. If a // different message is shown, we leave it. if (frame.statusLine.getText().startsWith(Globals.lang("Saved database"))); frame.output(" "); } public void markNonUndoableBaseChanged() { nonUndoableChange = true; markBaseChanged(); } public synchronized void markChangedOrUnChanged() { if (undoManager.hasChanged()) { if (!baseChanged) { markBaseChanged(); } } else if (baseChanged && !nonUndoableChange) { baseChanged = false; if (getFile() != null) frame.setTabTitle(BasePanel.this, getFile().getName(), getFile().getAbsolutePath()); else frame.setTabTitle(BasePanel.this, Globals.lang("untitled"), null); } frame.setWindowTitle(); } /** * Selects a single entry, and scrolls the table to center it. * * @param pos Current position of entry to select. * */ public void selectSingleEntry(int pos) { mainTable.clearSelection(); mainTable.addRowSelectionInterval(pos, pos); mainTable.scrollToCenter(pos, 0); } /* * * Selects all entries with a non-zero value in the field * @param field <code>String</code> field name. */ /* public void selectResults(String field) { LinkedList intervals = new LinkedList(); int prevStart = -1, prevToSel = 0; // First we build a list of intervals to select, without touching the table. for (int i = 0; i < entryTable.getRowCount(); i++) { String value = (String) (database.getEntryById (tableModel.getIdForRow(i))) .getField(field); if ( (value != null) && !value.equals("0")) { if (prevStart < 0) prevStart = i; prevToSel = i; } else if (prevStart >= 0) { intervals.add(new int[] {prevStart, prevToSel}); prevStart = -1; } } // Then select those intervals, if any. if (intervals.size() > 0) { entryTable.setSelectionListenerEnabled(false); entryTable.clearSelection(); for (Iterator i=intervals.iterator(); i.hasNext();) { int[] interval = (int[])i.next(); entryTable.addRowSelectionInterval(interval[0], interval[1]); } entryTable.setSelectionListenerEnabled(true); } */ public void setSearchMatcher(SearchMatcher matcher) { searchFilterList.setMatcher(matcher); showingSearch = true; } public void setGroupMatcher(Matcher<BibtexEntry> matcher) { groupFilterList.setMatcher(matcher); showingGroup = true; } public void stopShowingSearchResults() { searchFilterList.setMatcher(NoSearchMatcher.INSTANCE); showingSearch = false; } public void stopShowingGroup() { groupFilterList.setMatcher(NoSearchMatcher.INSTANCE); showingGroup = false; } /** * Query whether this BasePanel is in the mode where a float search result is shown. * @return true if showing float search, false otherwise. */ public boolean isShowingFloatSearch() { return mainTable.isShowingFloatSearch(); } /** * Query whether this BasePanel is in the mode where a filter search result is shown. * @return true if showing filter search, false otherwise. */ public boolean isShowingFilterSearch() { return showingSearch; } public BibtexDatabase getDatabase(){ return database ; } public void preambleEditorClosing() { preambleEditor = null; } public void stringsClosing() { stringDialog = null; } public void changeType(BibtexEntry entry, BibtexEntryType type) { changeType(new BibtexEntry[] {entry}, type); } public void changeType(BibtexEntryType type) { BibtexEntry[] bes = mainTable.getSelectedEntries(); changeType(bes, type); } public void changeType(BibtexEntry[] bes, BibtexEntryType type) { if ((bes == null) || (bes.length == 0)) { output("First select the entries you wish to change type "+ "for."); return; } if (bes.length > 1) { int choice = JOptionPane.showConfirmDialog (this, "Multiple entries selected. Do you want to change" +"\nthe type of all these to '"+type.getName()+"'?", "Change type", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE); if (choice == JOptionPane.NO_OPTION) return; } NamedCompound ce = new NamedCompound(Globals.lang("change type")); for (int i=0; i<bes.length; i++) { ce.addEdit(new UndoableChangeType(bes[i], bes[i].getType(), type)); bes[i].setType(type); } output(Globals.lang("Changed type to")+" '"+type.getName()+"' " +Globals.lang("for")+" "+bes.length +" "+Globals.lang("entries")+"."); ce.end(); undoManager.addEdit(ce); markBaseChanged(); updateEntryEditorIfShowing(); } public boolean showDeleteConfirmationDialog(int numberOfEntries) { if (Globals.prefs.getBoolean("confirmDelete")) { String msg = Globals.lang("Really delete the selected") + " " + Globals.lang("entry") + "?", title = Globals.lang("Delete entry"); if (numberOfEntries > 1) { msg = Globals.lang("Really delete the selected") + " " + numberOfEntries + " " + Globals.lang("entries") + "?"; title = Globals.lang("Delete multiple entries"); } CheckBoxMessage cb = new CheckBoxMessage (msg, Globals.lang("Disable this confirmation dialog"), false); int answer = JOptionPane.showConfirmDialog(frame, cb, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (cb.isSelected()) Globals.prefs.putBoolean("confirmDelete", false); return (answer == JOptionPane.YES_OPTION); } else return true; } /** * If the relevant option is set, autogenerate keys for all entries that are * lacking keys. */ public void autoGenerateKeysBeforeSaving() { if (Globals.prefs.getBoolean("generateKeysBeforeSaving")) { NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys")); boolean any = false; for (BibtexEntry bes : database.getEntries()){ String oldKey = bes.getCiteKey(); if ((oldKey == null) || (oldKey.equals(""))) { LabelPatternUtil.makeLabel(metaData, database, bes); ce.addEdit(new UndoableKeyChange(database, bes.getId(), null, bes.getField(BibtexFields.KEY_FIELD))); any = true; } } // Store undo information, if any: if (any) { ce.end(); undoManager.addEdit(ce); } } } /** * Activates or deactivates the entry preview, depending on the argument. * When deactivating, makes sure that any visible preview is hidden. * @param enabled */ public void setPreviewActive(boolean enabled) { selectionListener.setPreviewActive(enabled); } public void setSelectionListenerEnabled(boolean enabled) { selectionListener.setEnabled(enabled); } /** * Depending on whether a preview or an entry editor is showing, save the current * divider location in the correct preference setting. */ public void saveDividerLocation() { if (mode == SHOWING_PREVIEW) Globals.prefs.putInt("previewPanelHeight", splitPane.getHeight()-splitPane.getDividerLocation()); else if (mode == SHOWING_EDITOR) Globals.prefs.putInt("entryEditorHeight", splitPane.getHeight()-splitPane.getDividerLocation()); } class UndoAction extends BaseAction { public void action() { try { JComponent focused = Globals.focusListener.getFocused(); if ((focused != null) && (focused instanceof FieldEditor) && (focused.hasFocus())) { // User is currently editing a field: // Check if it is the preamble: if ((preambleEditor != null) && (focused == preambleEditor.getFieldEditor())) { preambleEditor.storeCurrentEdit(); } else storeCurrentEdit(); } String name = undoManager.getUndoPresentationName(); undoManager.undo(); markBaseChanged(); frame.output(name); } catch (CannotUndoException ex) { ex.printStackTrace(); frame.output(Globals.lang("Nothing to undo")+"."); } // After everything, enable/disable the undo/redo actions // appropriately. //updateUndoState(); //redoAction.updateRedoState(); markChangedOrUnChanged(); } } class RedoAction extends BaseAction { public void action() { try { JComponent focused = Globals.focusListener.getFocused(); if ((focused != null) && (focused instanceof FieldEditor) && (focused.hasFocus())) { // User is currently editing a field: storeCurrentEdit(); } String name = undoManager.getRedoPresentationName(); undoManager.redo(); markBaseChanged(); frame.output(name); } catch (CannotRedoException ex) { frame.output(Globals.lang("Nothing to redo")+"."); } // After everything, enable/disable the undo/redo actions // appropriately. //updateRedoState(); //undoAction.updateUndoState(); markChangedOrUnChanged(); } } // Method pertaining to the ClipboardOwner interface. public void lostOwnership(Clipboard clipboard, Transferable contents) {} public void setEntryEditorEnabled(boolean enabled) { if ((getShowing() != null) && (splitPane.getBottomComponent() instanceof EntryEditor)) { EntryEditor ed = (EntryEditor)splitPane.getBottomComponent(); if (ed.isEnabled() != enabled) ed.setEnabled(enabled); } } public String fileMonitorHandle() { return fileMonitorHandle; } public void fileUpdated() { if (saving) return; // We are just saving the file, so this message is most likely due //if (updatedExternally) { // return; //} // to bad timing. If not, we'll handle it on the next polling. //Util.pr("File '"+file.getPath()+"' has been modified."); updatedExternally = true; final ChangeScanner scanner = new ChangeScanner(frame, BasePanel.this); // Adding the sidepane component is Swing work, so we must do this in the Swing // thread: Thread t = new Thread() { public void run() { // Check if there is already a notification about external // changes: boolean hasAlready = sidePaneManager.hasComponent(FileUpdatePanel.NAME); if (hasAlready) { sidePaneManager.hideComponent(FileUpdatePanel.NAME); sidePaneManager.unregisterComponent(FileUpdatePanel.NAME); } FileUpdatePanel pan = new FileUpdatePanel(frame, BasePanel.this, sidePaneManager, getFile(), scanner); sidePaneManager.register(FileUpdatePanel.NAME, pan); sidePaneManager.show(FileUpdatePanel.NAME); //setUpdatedExternally(false); //scanner.displayResult(); } }; // Test: running scan automatically in background if ((BasePanel.this.getFile() != null) && !Util.waitForFileLock(BasePanel.this.getFile(), 10)) { // The file is locked even after the maximum wait. Do nothing. System.err.println("File updated externally, but change scan failed because the file is locked."); // Perturb the stored timestamp so successive checks are made: Globals.fileUpdateMonitor.perturbTimestamp(getFileMonitorHandle()); return; } scanner.changeScan(BasePanel.this.getFile()); try { scanner.join(); } catch (InterruptedException e) { e.printStackTrace(); } if (scanner.changesFound()) { SwingUtilities.invokeLater(t); } else { setUpdatedExternally(false); //System.out.println("No changes found."); } } public void fileRemoved() { Util.pr("File '"+getFile().getPath()+"' has been deleted."); } /** * Perform necessary cleanup when this BasePanel is closed. */ public void cleanUp() { if (fileMonitorHandle != null) Globals.fileUpdateMonitor.removeUpdateListener(fileMonitorHandle); // Check if there is a FileUpdatePanel for this BasePanel being shown. If so, // remove it: if (sidePaneManager.hasComponent("fileUpdate")) { FileUpdatePanel fup = (FileUpdatePanel)sidePaneManager.getComponent("fileUpdate"); if (fup.getPanel() == this) { sidePaneManager.hideComponent("fileUpdate"); } } } public void setUpdatedExternally(boolean b) { updatedExternally = b; } /** * Get an array containing the currently selected entries. * * @return An array containing the selected entries. */ public BibtexEntry[] getSelectedEntries() { return mainTable.getSelectedEntries(); } /** * Get the file where this database was last saved to or loaded from, if any. * * @return The relevant File, or null if none is defined. */ public File getFile() { return metaData.getFile(); } /** * Get a String containing a comma-separated list of the bibtex keys * of the selected entries. * * @return A comma-separated list of the keys of the selected entries. */ public String getKeysForSelection() { StringBuffer result = new StringBuffer(); String citeKey = "";//, message = ""; boolean first = true; for (BibtexEntry bes : mainTable.getSelected()){ citeKey = bes.getField(BibtexFields.KEY_FIELD); // if the key is empty we give a warning and ignore this entry if (citeKey == null || citeKey.equals("")) continue; if (first) { result.append(citeKey); first = false; } else { result.append(",").append(citeKey); } } return result.toString(); } public GroupSelector getGroupSelector() { return frame.groupSelector; } public boolean isUpdatedExternally() { return updatedExternally; } public String getFileMonitorHandle() { return fileMonitorHandle; } public void setFileMonitorHandle(String fileMonitorHandle) { this.fileMonitorHandle = fileMonitorHandle; } public SidePaneManager getSidePaneManager() { return sidePaneManager; } public void setNonUndoableChange(boolean nonUndoableChange) { this.nonUndoableChange = nonUndoableChange; } public void setBaseChanged(boolean baseChanged) { this.baseChanged = baseChanged; } public void setSaving(boolean saving) { this.saving = saving; } public boolean isSaving() { return saving; } public BibtexEntry getShowing() { return showing; } /** * Update the pointer to the currently shown entry in all cases where the user has * moved to a new entry, except when using Back and Forward commands. Also updates * history for Back command, and clears history for Forward command. * @param entry The entry that is now to be shown. */ public void newEntryShowing(BibtexEntry entry) { // If this call is the result of a Back or Forward operation, we must take // care not to make any history changes, since the necessary changes will // already have been done in the back() or forward() method: if (backOrForwardInProgress) { showing = entry; backOrForwardInProgress = false; setBackAndForwardEnabledState(); return; } nextEntries.clear(); if (entry != showing) { // Add the entry we are leaving to the history: if (showing != null) { previousEntries.add(showing); if (previousEntries.size() > GUIGlobals.MAX_BACK_HISTORY_SIZE) previousEntries.remove(0); } showing = entry; setBackAndForwardEnabledState(); } } /** * Go back (if there is any recorded history) and update the histories for * the Back and Forward commands. */ private void back() { if (previousEntries.size() > 0) { BibtexEntry toShow = previousEntries.get(previousEntries.size()-1); previousEntries.remove(previousEntries.size()-1); // Add the entry we are going back from to the Forward history: if (showing != null) nextEntries.add(showing); backOrForwardInProgress = true; // to avoid the history getting updated erroneously //showEntry(toShow); highlightEntry(toShow); } } private void forward() { if (nextEntries.size() > 0) { BibtexEntry toShow = nextEntries.get(nextEntries.size()-1); nextEntries.remove(nextEntries.size()-1); // Add the entry we are going forward from to the Back history: if (showing != null) previousEntries.add(showing); backOrForwardInProgress = true; // to avoid the history getting updated erroneously //showEntry(toShow); highlightEntry(toShow); } } public void setBackAndForwardEnabledState() { frame.back.setEnabled(previousEntries.size() > 0); frame.forward.setEnabled(nextEntries.size() > 0); } }