net.sf.jabref.BasePanel.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jabref.BasePanel.java

Source

/*  Copyright (C) 2003-2015 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.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.logging.Logger;

import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
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.DatabaseChangeEvent.ChangeType;
import net.sf.jabref.autocompleter.AutoCompleter;
import net.sf.jabref.autocompleter.AutoCompleterFactory;
import net.sf.jabref.autocompleter.ContentAutoCompleters;
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.FileActions.DatabaseSaveType;
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.AttachFileAction;
import net.sf.jabref.external.AutoSetExternalFileForEntries;
import net.sf.jabref.external.ExternalFileMenuItem;
import net.sf.jabref.external.ExternalFileType;
import net.sf.jabref.external.FindFullTextAction;
import net.sf.jabref.external.RegExpFileSearch;
import net.sf.jabref.external.SynchronizeFileField;
import net.sf.jabref.external.WriteXMPAction;
import net.sf.jabref.groups.GroupSelector;
import net.sf.jabref.groups.GroupTreeNode;
import net.sf.jabref.gui.AutoCompleteListener;
import net.sf.jabref.gui.CleanUpAction;
import net.sf.jabref.gui.FileDialogs;
import net.sf.jabref.gui.FileListEntry;
import net.sf.jabref.gui.FileListTableModel;
import net.sf.jabref.gui.GlazedEntrySorter;
import net.sf.jabref.gui.GroupAddRemoveDialog;
import net.sf.jabref.gui.MainTable;
import net.sf.jabref.gui.MainTableFormat;
import net.sf.jabref.gui.MainTableSelectionListener;
import net.sf.jabref.gui.MergeEntriesDialog;
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.matchers.NoSearchMatcher;
import net.sf.jabref.search.matchers.SearchMatcher;
import net.sf.jabref.specialfields.Printed;
import net.sf.jabref.specialfields.Priority;
import net.sf.jabref.specialfields.Quality;
import net.sf.jabref.specialfields.Rank;
import net.sf.jabref.specialfields.ReadStatus;
import net.sf.jabref.specialfields.Relevance;
import net.sf.jabref.specialfields.SpecialFieldAction;
import net.sf.jabref.specialfields.SpecialFieldDatabaseChangeListener;
import net.sf.jabref.specialfields.SpecialFieldValue;
import net.sf.jabref.sql.DBConnectDialog;
import net.sf.jabref.sql.DBExporterAndImporterFactory;
import net.sf.jabref.sql.DBStrings;
import net.sf.jabref.sql.DbConnectAction;
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.util.FileBasedLock;
import net.sf.jabref.util.Util;
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;

public class BasePanel extends JPanel implements ClipboardOwner, FileUpdateListener {

    private static final long serialVersionUID = 1L;

    private static final Logger logger = Logger.getLogger(BasePanel.class.getName());

    public final static int SHOWING_NOTHING = 0;
    private final static int SHOWING_PREVIEW = 1;
    public final static int SHOWING_EDITOR = 2;
    public final static int 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;
    JSplitPane contentPane = new JSplitPane();

    private JSplitPane splitPane;

    JabRefFrame frame;

    private String fileMonitorHandle = null;
    private boolean saving = false;
    private boolean updatedExternally = false;
    private String encoding;

    GridBagLayout gbl = new GridBagLayout();
    GridBagConstraints con = new GridBagConstraints();

    // Hashtable indexing the only search auto completer
    // required for the SearchAutoCompleterUpdater
    private AutoCompleter searchAutoCompleter;

    private AutoCompleteListener searchCompleteListener = null;

    // The undo manager.
    public final CountingUndoManager undoManager = new CountingUndoManager(this);
    private final UndoAction undoAction = new UndoAction();
    private final RedoAction redoAction = new RedoAction();

    private final List<BibtexEntry> previousEntries = new ArrayList<BibtexEntry>();
    private final List<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;
    private FilterList<BibtexEntry> searchFilterList = null;
    private FilterList<BibtexEntry> groupFilterList = null;

    public RightClickMenu rcm;

    private 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 final 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.

    private PreambleEditor preambleEditor = null;
    // Keeps track of the preamble dialog if it is open.

    private StringDialog stringDialog = null;
    // Keeps track of the string dialog if it is open.

    private SaveDatabaseAction saveAction;

    /**
     * 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;

    private boolean showingSearch = false;

    public boolean sortingBySearchResults = false;
    public boolean coloringBySearchResults = false;
    public boolean hidingNonHits = false;
    public boolean sortingByGroup = false;
    public boolean sortingByCiteSeerResults = false;
    public boolean 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.
    final MetaData metaData;

    private final HashMap<String, Object> actions = new HashMap<String, Object>();
    private SidePaneManager sidePaneManager;

    public ContentAutoCompleters getAutoCompleters() {
        return autoCompleters;
    }

    private ContentAutoCompleters autoCompleters;

    public BasePanel(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);

        // ensure that at each addition of a new entry, the entry is added to the groups interface
        db.addDatabaseChangeListener(new GroupTreeUpdater());

        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) {
                BasePanel.logger.warning(ex.toString());
            }
        }

    }

    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) {
        boolean suppressOutput = false;
        if (!suppressOutput) {
            frame.output(s);
        }
    }

    private void setupActions() {
        saveAction = new SaveDatabaseAction(this);
        CleanUpAction cleanUpAction = new CleanUpAction(this);

        actions.put("undo", undoAction);
        actions.put("redo", redoAction);

        actions.put("focusTable", new BaseAction() {

            @Override
            public void action() throws Throwable {
                new FocusRequester(mainTable);
            }
        });

        // The action for opening an entry editor.
        actions.put("edit", new BaseAction() {

            @Override
            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() {

            @Override
            public void action() throws Throwable {
                saveAction.saveAs();
            }
        });

        actions.put("saveSelectedAs", new SaveSelectedAction(FileActions.DatabaseSaveType.DEFAULT));

        actions.put("saveSelectedAsPlain", new SaveSelectedAction(FileActions.DatabaseSaveType.PLAIN_BIBTEX));

        // The action for copying selected entries.
        actions.put("copy", new BaseAction() {

            @Override
            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() {

            @Override
            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 (BibtexEntry be : bes) {
                        database.removeEntry(be.getId());
                        ensureNotShowing(be);
                        ce.addEdit(new UndoableRemoveEntry(database, be, 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() {

            @Override
            public void action() {
                BibtexEntry[] bes = mainTable.getSelectedEntries();
                if ((bes != null) && (bes.length > 0)) {

                    boolean goOn = showDeleteConfirmationDialog(bes.length);
                    if (goOn) {
                        // 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 (BibtexEntry be : bes) {
                            database.removeEntry(be.getId());
                            ensureNotShowing(be);
                            ce.addEdit(new UndoableRemoveEntry(database, be, 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() {

            @Override
            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 (BibtexEntry be1 : bes) {
                            try {
                                BibtexEntry be = (BibtexEntry) (be1.clone());
                                if (firstBE == null) {
                                    firstBE = be;
                                }
                                Util.setAutomaticFields(be,
                                        Globals.prefs.getBoolean(JabRefPreferences.OVERWRITE_OWNER),
                                        Globals.prefs.getBoolean(JabRefPreferences.OVERWRITE_TIME_STAMP));

                                // We have to clone the
                                // entries, since the pasted
                                // entries must exist
                                // independently of the copied
                                // ones.
                                be.setId(IdGenerator.next());
                                database.insertEntry(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(JabRefPreferences.AUTO_OPEN_FORM)) {
                            selectionListener.editSignalled(firstBE);
                        }
                        highlightEntry(firstBE);
                    }
                }

            }

        });

        actions.put("selectAll", new BaseAction() {

            @Override
            public void action() {
                mainTable.selectAll();
            }
        });

        // The action for opening the preamble editor
        actions.put("editPreamble", new BaseAction() {

            @Override
            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() {

            @Override
            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() {

            @Override
            public void action() {
                sidePaneManager.toggle("groups");
                frame.groupToggle.setSelected(sidePaneManager.isComponentVisible("groups"));
            }
        });

        // The action for toggling the visibility of the toolbar
        actions.put("toggleToolbar", new BaseAction() {

            @Override
            public void action() {
                frame.tlb.setVisible(!frame.tlb.isVisible());
            }
        });

        // 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:
            @Override
            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:
            @Override
            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:
            @Override
            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:
            @Override
            public void init() {
                entries = new ArrayList<BibtexEntry>(Arrays.asList(getSelectedEntries()));
                //rows = entryTable.getSelectedRows() ;
                numSelected = entries.size();

                if (entries.isEmpty()) { // 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:
            @Override
            public void run() {
                BibtexEntry bes;
                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.
                for (Iterator<BibtexEntry> i = entries.iterator(); i.hasNext();) {
                    bes = i.next();
                    if (bes.getField(BibtexFields.KEY_FIELD) != null) {
                        if (Globals.prefs.getBoolean(JabRefPreferences.AVOID_OVERWRITING_KEY)) {
                            // Remove the entry, because its key is already set:
                            i.remove();
                        } else if (Globals.prefs.getBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY)) {
                            // 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(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY, 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;
                        }
                    }
                }

                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(JabRefPreferences.AVOID_OVERWRITING_KEY)) {
                    for (BibtexEntry entry : entries) {
                        bes = entry;
                        // Store the old value:
                        oldvals.put(bes, bes.getField(BibtexFields.KEY_FIELD));
                        database.setCiteKeyForEntry(bes.getId(), null);
                    }
                }

                // Finally, set the new keys:
                for (BibtexEntry entry : entries) {
                    bes = entry;
                    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:
            @Override
            public void update() {
                database.setFollowCrossrefs(true);
                if (cancelled) {
                    frame.unblock();
                    return;
                }
                markBaseChanged();
                numSelected = entries.size();

                ////////////////////////////////////////////////////////////////////////////////
                //          Prevent selection loss for autogenerated BibTeX-Keys
                ////////////////////////////////////////////////////////////////////////////////
                for (final BibtexEntry bibEntry : entries) {
                    SwingUtilities.invokeLater(new Runnable() {

                        @Override
                        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("mergeEntries", new BaseAction() {

            @Override
            public void action() {
                new MergeEntriesDialog(BasePanel.this);
            }
        });

        actions.put("search", new BaseAction() {

            @Override
            public void action() {
                //sidePaneManager.togglePanel("search");
                sidePaneManager.show("search");
                //boolean on = sidePaneManager.isPanelVisible("search");
                frame.searchToggle.setSelected(true);
                frame.getSearchManager().startSearch();
            }
        });

        actions.put("toggleSearch", new BaseAction() {

            @Override
            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() {

            @Override
            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() {

            @Override
            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 (BibtexEntry be : bes) {
                        if (be.getField(BibtexFields.KEY_FIELD) != null) {
                            keys.add(be.getField(BibtexFields.KEY_FIELD));
                        }
                    }
                    if (keys.isEmpty()) {
                        output("None of the selected entries have BibTeX keys.");
                        return;
                    }
                    StringBuilder sb = new StringBuilder((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() {

            @Override
            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 (BibtexEntry be : bes) {
                        if (be.getField(BibtexFields.KEY_FIELD) != null) {
                            keys.add(be.getField(BibtexFields.KEY_FIELD));
                        }
                    }
                    if (keys.isEmpty()) {
                        output("None of the selected entries have BibTeX keys.");
                        return;
                    }
                    StringBuilder sb = new StringBuilder((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() {

            @Override
            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;
                    }

                    StringBuilder sb = new StringBuilder();

                    int copied = 0;
                    // Collect all non-null keys.
                    for (BibtexEntry be : bes) {
                        if (be.getField(BibtexFields.KEY_FIELD) != null) {
                            copied++;
                            sb.append(layout.doLayout(be, 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() {

            @Override
            public void action() {
                JabRefExecutorService.INSTANCE.execute(new Runnable() {

                    @Override
                    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(JabRefPreferences.RUN_AUTOMATIC_FILE_SEARCH)) {

                                    /*  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 (String mdDir : mdDirs) {
                                            dirs.add(new File(mdDir));

                                        }
                                    }
                                    Collection<String> extensions = new ArrayList<String>();
                                    for (final ExternalFileType type : types) {
                                        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.isEmpty()) {
                                            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."));
                        }
                    }
                });
            }
        });

        actions.put("addFileLink", new AttachFileAction(this));

        actions.put("openExternalFile", new BaseAction() {

            @Override
            public void action() {
                JabRefExecutorService.INSTANCE.execute(new Runnable() {

                    @Override
                    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."));
                        }
                    }
                });
            }
        });

        actions.put("openFolder", new BaseAction() {

            @Override
            public void action() {
                JabRefExecutorService.INSTANCE.execute(new Runnable() {

                    @Override
                    public void run() {
                        BibtexEntry[] bes = mainTable.getSelectedEntries();
                        List<File> files = Util.getListOfLinkedFiles(bes,
                                metaData().getFileDirectory(GUIGlobals.FILE_FIELD));
                        for (File f : files) {
                            try {
                                Util.openFolderAndSelectFile(f.getAbsolutePath());
                            } catch (IOException e) {
                                BasePanel.logger.fine(e.getMessage());
                            }
                        }
                    }
                });
            }
        });

        actions.put("openUrl", new BaseAction() {

            @Override
            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();
                            }
                        } else {
                            output(Globals.lang("No url defined") + '.');
                        }
                    }
                } else {
                    output(Globals.lang("No entries or multiple entries selected."));
                }
            }
        });

        actions.put("openSpires", new BaseAction() {

            @Override
            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"));
                    } else if (bes[0].getField("slaccitation") != null) {
                        link = SPIRESFetcher.constructUrlFromSlaccitation(bes[0].getField("slaccitation"));
                    }
                    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() {

            @Override
            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 (BibtexEntry be : bes) {
                        counter += rsd.replace(be, 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() {

            @Override
            public void action() {
                JabRefExecutorService.INSTANCE.execute(new DuplicateSearch(BasePanel.this));
            }
        });

        actions.put("plainTextImport", new BaseAction() {

            @Override
            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 = IdGenerator.next();
                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(Collections.singletonList(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 MarkEntriesAction(frame, 0));

        actions.put("unmarkEntries", new BaseAction() {

            @Override
            public void action() {
                try {
                    BibtexEntry[] bes = mainTable.getSelectedEntries();
                    if (bes.length == 0) {
                        output(Globals.lang("No entries selected."));
                        return;
                    }
                    NamedCompound ce = new NamedCompound(Globals.lang("Unmark entries"));
                    for (BibtexEntry be : bes) {
                        EntryMarker.unmarkEntry(be, false, database, ce);
                    }
                    ce.end();
                    undoManager.addEdit(ce);
                    markBaseChanged();
                    String outputStr;
                    if (bes.length == 1) {
                        outputStr = Globals.lang("Unmarked selected entry");
                    } else {
                        outputStr = Globals.lang("Unmarked all %0 selected entries", Integer.toString(bes.length));
                    }
                    output(outputStr);
                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
        });

        actions.put("unmarkAll", new BaseAction() {

            @Override
            public void action() {
                NamedCompound ce = new NamedCompound(Globals.lang("Unmark all"));

                for (BibtexEntry be : database.getEntries()) {
                    EntryMarker.unmarkEntry(be, false, database, ce);
                }
                ce.end();
                undoManager.addEdit(ce);
                markBaseChanged();
                output(Globals.lang("Unmarked all entries"));
            }
        });

        // Note that we can't put the number of entries that have been reverted into the undoText as the concrete number cannot be injected
        actions.put(Relevance.getInstance().getValues().get(0).getActionName(),
                new SpecialFieldAction(frame, Relevance.getInstance(),
                        Relevance.getInstance().getValues().get(0).getFieldValue(), true,
                        Globals.lang("Toggle relevance"), Globals.lang("Toggled relevance for %0 entries")));
        actions.put(Quality.getInstance().getValues().get(0).getActionName(),
                new SpecialFieldAction(frame, Quality.getInstance(),
                        Quality.getInstance().getValues().get(0).getFieldValue(), true,
                        Globals.lang("Toggle quality"), Globals.lang("Toggled quality for %0 entries")));
        actions.put(Printed.getInstance().getValues().get(0).getActionName(),
                new SpecialFieldAction(frame, Printed.getInstance(),
                        Printed.getInstance().getValues().get(0).getFieldValue(), true,
                        Globals.lang("Toggle print status"), Globals.lang("Toggled print status for %0 entries")));

        for (SpecialFieldValue prio : Priority.getInstance().getValues()) {
            actions.put(prio.getActionName(), prio.getAction(this.frame));
        }
        for (SpecialFieldValue rank : Rank.getInstance().getValues()) {
            actions.put(rank.getActionName(), rank.getAction(this.frame));
        }
        for (SpecialFieldValue status : ReadStatus.getInstance().getValues()) {
            actions.put(status.getActionName(), status.getAction(this.frame));
        }

        actions.put("togglePreview", new BaseAction() {

            @Override
            public void action() {
                boolean enabled = !Globals.prefs.getBoolean(JabRefPreferences.PREVIEW_ENABLED);
                Globals.prefs.putBoolean(JabRefPreferences.PREVIEW_ENABLED, enabled);
                frame.setPreviewActive(enabled);
                frame.previewToggle.setSelected(enabled);
            }
        });

        actions.put("toggleHighlightGroupsMatchingAny", new BaseAction() {

            @Override
            public void action() {
                boolean enabled = !Globals.prefs.getBoolean(JabRefPreferences.HIGHLIGHT_GROUPS_MATCHING_ANY);
                Globals.prefs.putBoolean(JabRefPreferences.HIGHLIGHT_GROUPS_MATCHING_ANY, enabled);
                frame.highlightAny.setSelected(enabled);
                if (enabled) {
                    frame.highlightAll.setSelected(false);
                    Globals.prefs.putBoolean(JabRefPreferences.HIGHLIGHT_GROUPS_MATCHING_ALL, false);
                }
                // ping the listener so it updates:
                groupsHighlightListener.listChanged(null);
            }
        });

        actions.put("toggleHighlightGroupsMatchingAll", new BaseAction() {

            @Override
            public void action() {
                boolean enabled = !Globals.prefs.getBoolean(JabRefPreferences.HIGHLIGHT_GROUPS_MATCHING_ALL);
                Globals.prefs.putBoolean(JabRefPreferences.HIGHLIGHT_GROUPS_MATCHING_ALL, enabled);
                frame.highlightAll.setSelected(enabled);
                if (enabled) {
                    frame.highlightAny.setSelected(false);
                    Globals.prefs.putBoolean(JabRefPreferences.HIGHLIGHT_GROUPS_MATCHING_ANY, false);
                }
                // ping the listener so it updates:
                groupsHighlightListener.listChanged(null);
            }
        });

        actions.put("switchPreview", new BaseAction() {

            @Override
            public void action() {
                selectionListener.switchPreview();
            }
        });

        actions.put("manageSelectors", new BaseAction() {

            @Override
            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() {

            @Override
            public void action() throws Throwable {
                back();
            }
        });
        actions.put("forward", new BaseAction() {

            @Override
            public void action() throws Throwable {
                forward();
            }
        });

        actions.put("resolveDuplicateKeys", new SearchFixDuplicateLabels(this));

        actions.put("addToGroup", new GroupAddRemoveDialog(this, true, false));
        actions.put("removeFromGroup", new GroupAddRemoveDialog(this, false, false));
        actions.put("moveToGroup", new GroupAddRemoveDialog(this, true, true));

        //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) {
        //(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,
            FileActions.DatabaseSaveType saveType) 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, saveType);
            }

        } 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, saveType);
                } else {
                    commit = false;
                }
            } else if (answer == JOptionPane.CANCEL_OPTION) {
                commit = false;
            }

        }

        if (commit) {
            session.commit();
            this.encoding = encoding; // Make sure to remember which encoding we used.
        } else {
            session.cancel();
        }

        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 = IdGenerator.next();
            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 != BasePanel.SHOWING_EDITOR) {
                    mode = BasePanel.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));

                return be;
            } catch (KeyCollisionException ex) {
                Util.pr(ex.getMessage());
            }
        }
        return null;
    }

    /**
     * This listener is used 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
     */
    private class GroupTreeUpdater implements DatabaseChangeListener {

        @Override
        public void databaseChanged(DatabaseChangeEvent e) {
            if ((e.getType() == ChangeType.ADDED_ENTRY)
                    && (Globals.prefs.getBoolean(JabRefPreferences.AUTO_ASSIGN_GROUP))
                    && (frame.groupToggle.isSelected())) {
                BibtexEntry[] entries = { e.getEntry() };
                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);
                    }
                }
                //BasePanel.this.updateEntryEditorIfShowing(); // doesn't seem to be necessary
                SwingUtilities.invokeLater(new Runnable() {

                    @Override
                    public void run() {
                        BasePanel.this.getGroupSelector().valueChanged(null);
                    }
                });
            }
        }
    }

    /**
     * Ensures that the search auto completer is up to date when entries are changed
     * AKA Let the auto completer, if any, harvest words from the entry
     */
    private class SearchAutoCompleterUpdater implements DatabaseChangeListener {

        @Override
        public void databaseChanged(DatabaseChangeEvent e) {
            if ((e.getType() == ChangeType.CHANGED_ENTRY) || (e.getType() == ChangeType.ADDED_ENTRY)) {
                searchAutoCompleter.addBibtexEntry(e.getEntry());
            }
        }
    }

    /**
     * Ensures that auto completers are up to date when entries are changed
     * AKA Let the auto completer, if any, harvest words from the entry
     */
    private class AutoCompletersUpdater implements DatabaseChangeListener {

        @Override
        public void databaseChanged(DatabaseChangeEvent e) {
            if ((e.getType() == ChangeType.CHANGED_ENTRY) || (e.getType() == ChangeType.ADDED_ENTRY)) {
                BasePanel.this.autoCompleters.addEntry(e.getEntry());
            }
        }
    }

    /**
     * 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(JabRefPreferences.USE_OWNER)) {
                    // 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(JabRefPreferences.AUTO_OPEN_FORM)) {
                    selectionListener.editSignalled(bibEntry);
                }
                highlightEntry(bibEntry);
            } catch (KeyCollisionException ex) {
                Util.pr(ex.getMessage());
            }
        }
    }

    public void updateTableFont() {
        mainTable.updateFont();
    }

    private 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>() {

            @Override
            public void listChanged(ListEvent<BibtexEntry> listEvent) {
                if (Globals.prefs.getBoolean(JabRefPreferences.HIGHLIGHT_GROUPS_MATCHING_ANY)) {
                    getGroupSelector().showMatchingGroups(mainTable.getSelectedEntries(), false);
                } else if (Globals.prefs.getBoolean(JabRefPreferences.HIGHLIGHT_GROUPS_MATCHING_ALL)) {
                    getGroupSelector().showMatchingGroups(mainTable.getSelectedEntries(), true);
                } else {
                    // no highlight
                    getGroupSelector().showMatchingGroups(null, true);
                }
            }
        };
        mainTable.addSelectionListener(groupsHighlightListener);

        mainTable.getActionMap().put("cut", new AbstractAction() {

            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    runCommand("cut");
                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
        });
        mainTable.getActionMap().put("copy", new AbstractAction() {

            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    runCommand("copy");
                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
        });
        mainTable.getActionMap().put("paste", new AbstractAction() {

            private static final long serialVersionUID = 1L;

            @Override
            public void actionPerformed(ActionEvent e) {
                try {
                    runCommand("paste");
                } catch (Throwable ex) {
                    ex.printStackTrace();
                }
            }
        });

        mainTable.addKeyListener(new KeyAdapter() {

            @Override
            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());

        // Remove borders
        splitPane.setBorder(BorderFactory.createEmptyBorder());
        setBorder(BorderFactory.createEmptyBorder());

        //setupTable();
        // If an entry is currently being shown, make sure it stays shown,
        // otherwise set the bottom component to null.
        if (mode == BasePanel.SHOWING_PREVIEW) {
            mode = BasePanel.SHOWING_NOTHING;
            int row = mainTable.findEntry(currentPreview.entry);
            if (row >= 0) {
                mainTable.setRowSelectionInterval(row, row);
            }

        } else if (mode == BasePanel.SHOWING_EDITOR) {
            mode = BasePanel.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:
        //if (!Globals.prefs.getBoolean("searchAutoComplete")) {
        instantiateSearchAutoCompleter();
        this.getDatabase().addDatabaseChangeListener(new SearchAutoCompleterUpdater());

        // Set up AutoCompleters for this panel:
        if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_COMPLETE)) {
            autoCompleters = new ContentAutoCompleters(getDatabase(), metaData);
            // ensure that the autocompleters are in sync with entries
            this.getDatabase().addDatabaseChangeListener(new AutoCompletersUpdater());
        }

        splitPane.revalidate();
        revalidate();
        repaint();
    }

    public void updateSearchManager() {
        frame.getSearchManager().setAutoCompleteListener(searchCompleteListener);
    }

    private void instantiateSearchAutoCompleter() {
        searchAutoCompleter = AutoCompleterFactory.getFor("author", "editor");
        for (BibtexEntry entry : database.getEntries()) {
            searchAutoCompleter.addBibtexEntry(entry);
        }
        searchCompleteListener = new AutoCompleteListener(searchAutoCompleter);
        searchCompleteListener.setConsumeEnterKey(false); // So you don't have to press Enter twice
    }

    /*
    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 == BasePanel.SHOWING_PREVIEW) {
            splitPane.setDividerLocation(
                    splitPane.getHeight() - Globals.prefs.getInt(JabRefPreferences.PREVIEW_PANEL_HEIGHT));
        } else {
            splitPane.setDividerLocation(
                    splitPane.getHeight() - Globals.prefs.getInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT));

        }
    }

    /**
     * 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 instanceof EntryEditor)) {
            return ((EntryEditor) c).lastSourceAccepted();
        } else {
            return true;
        }
    }

    public void moveFocusToEntryEditor() {
        Component c = splitPane.getBottomComponent();
        if ((c instanceof EntryEditor)) {
            new FocusRequester(c);
        }
    }

    private 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(JabRefPreferences.ENTRY_EDITOR_HEIGHT));
            //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) {
        if (mode == BasePanel.SHOWING_EDITOR) {
            Globals.prefs.putInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT,
                    splitPane.getHeight() - splitPane.getDividerLocation());
        } else if (mode == BasePanel.SHOWING_PREVIEW) {
            Globals.prefs.putInt(JabRefPreferences.PREVIEW_PANEL_HEIGHT,
                    splitPane.getHeight() - splitPane.getDividerLocation());
        }
        mode = BasePanel.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 = BasePanel.SHOWING_PREVIEW;
        currentPreview = preview;
        splitPane.setBottomComponent(preview);
    }

    /**
     * Removes the bottom component.
     */
    public void hideBottomComponent() {
        mode = BasePanel.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(JabRefPreferences.ENTRY_EDITOR_HEIGHT,
                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 == BasePanel.SHOWING_EDITOR) && (currentEditor.getEntry() == be)) {
            selectionListener.entryEditorClosing(currentEditor);
        }
    }

    public void updateEntryEditorIfShowing() {
        if (mode == BasePanel.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 (Map.Entry<String, EntryEditor> stringEntryEditorEntry : entryEditors.entrySet()) {
            EntryEditor ed = stringEntryEditorEntry.getValue();
            ed.updateAllContentSelectors();
        }
    }

    public void rebuildAllEntryEditors() {
        for (Map.Entry<String, EntryEditor> stringEntryEditorEntry : entryEditors.entrySet()) {
            EntryEditor ed = stringEntryEditorEntry.getValue();
            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();
    }

    private 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);
    }

    public void stopShowingSearchResults() {
        searchFilterList.setMatcher(NoSearchMatcher.INSTANCE);
        showingSearch = false;
    }

    public void stopShowingGroup() {
        groupFilterList.setMatcher(NoSearchMatcher.INSTANCE);
    }

    /**
     * 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);
    }

    private 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 (BibtexEntry be : bes) {
            ce.addEdit(new UndoableChangeType(be, be.getType(), type));
            be.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(JabRefPreferences.CONFIRM_DELETE)) {
            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(JabRefPreferences.CONFIRM_DELETE, 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(JabRefPreferences.GENERATE_KEYS_BEFORE_SAVING)) {
            NamedCompound ce = new NamedCompound(Globals.lang("autogenerate keys"));
            boolean any = false;

            for (BibtexEntry bes : database.getEntries()) {
                String oldKey = bes.getCiteKey();
                if ((oldKey == null) || (oldKey.isEmpty())) {
                    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 == BasePanel.SHOWING_PREVIEW) {
            Globals.prefs.putInt(JabRefPreferences.PREVIEW_PANEL_HEIGHT,
                    splitPane.getHeight() - splitPane.getDividerLocation());
        } else if (mode == BasePanel.SHOWING_EDITOR) {
            Globals.prefs.putInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT,
                    splitPane.getHeight() - splitPane.getDividerLocation());
        }
    }

    class UndoAction implements BaseAction {

        @Override
        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 implements BaseAction {

        @Override
        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.
    @Override
    public void lostOwnership(Clipboard clipboard, Transferable contents) {
    }

    private 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;
    }

    @Override
    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, BasePanel.this.getFile());

        // Adding the sidepane component is Swing work, so we must do this in the Swing
        // thread:
        Runnable t = new Runnable() {

            @Override
            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) && !FileBasedLock.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;
        }

        JabRefExecutorService.INSTANCE.executeWithLowPriorityInOwnThreadAndWait(scanner);

        if (scanner.changesFound()) {
            SwingUtilities.invokeLater(t);
        } else {
            setUpdatedExternally(false);
            //System.out.println("No changes found.");
        }
    }

    @Override
    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.
     * The array is stable and not changed if the selection changes
     *
     * @return An array containing the selected entries. Is never null.
     */
    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() {
        StringBuilder result = new StringBuilder();
        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.isEmpty()) {
                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;
    }

    private 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.isEmpty()) {
            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.isEmpty()) {
            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.isEmpty());
        frame.forward.setEnabled(!nextEntries.isEmpty());
    }

    private class SaveSelectedAction implements BaseAction {

        private final DatabaseSaveType saveType;

        public SaveSelectedAction(DatabaseSaveType saveType) {
            this.saveType = saveType;
        }

        @Override
        public void action() throws Throwable {

            String chosenFile = FileDialogs.getNewFile(frame,
                    new File(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)), ".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(JabRefPreferences.DEFAULT_ENCODING), saveType);
                    //runCommand("save");
                    frame.getFileHistory().newFile(expFile.getPath());
                    frame.output(Globals.lang("Saved selected to") + " '" + expFile.getPath() + "'.");
                }
            }
        }
    }
}