net.sf.jabref.gui.entryeditor.EntryEditor.java Source code

Java tutorial

Introduction

Here is the source code for net.sf.jabref.gui.entryeditor.EntryEditor.java

Source

/*  Copyright (C) 2003-2016 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.gui.entryeditor;

import java.awt.AWTKeyStroke;
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.KeyboardFocusManager;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.KeyStroke;
import javax.swing.ScrollPaneConstants;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.text.JTextComponent;

import net.sf.jabref.Globals;
import net.sf.jabref.external.WriteXMPEntryEditorAction;
import net.sf.jabref.gui.BasePanel;
import net.sf.jabref.gui.EntryContainer;
import net.sf.jabref.gui.FieldContentSelector;
import net.sf.jabref.gui.GUIGlobals;
import net.sf.jabref.gui.IconTheme;
import net.sf.jabref.gui.JabRefFrame;
import net.sf.jabref.gui.OSXCompatibleToolbar;
import net.sf.jabref.gui.actions.Actions;
import net.sf.jabref.gui.fieldeditors.FieldEditor;
import net.sf.jabref.gui.fieldeditors.FieldEditorFocusListener;
import net.sf.jabref.gui.fieldeditors.FileListEditor;
import net.sf.jabref.gui.fieldeditors.JTextAreaWithHighlighting;
import net.sf.jabref.gui.fieldeditors.TextField;
import net.sf.jabref.gui.help.HelpAction;
import net.sf.jabref.gui.help.HelpFile;
import net.sf.jabref.gui.keyboard.KeyBinding;
import net.sf.jabref.gui.menus.ChangeEntryTypeMenu;
import net.sf.jabref.gui.undo.NamedCompound;
import net.sf.jabref.gui.undo.UndoableChangeType;
import net.sf.jabref.gui.undo.UndoableFieldChange;
import net.sf.jabref.gui.undo.UndoableKeyChange;
import net.sf.jabref.gui.undo.UndoableRemoveEntry;
import net.sf.jabref.gui.util.FocusRequester;
import net.sf.jabref.gui.util.component.CheckBoxMessage;
import net.sf.jabref.gui.util.component.VerticalLabelUI;
import net.sf.jabref.importer.ParserResult;
import net.sf.jabref.importer.fileformat.BibtexParser;
import net.sf.jabref.logic.TypedBibEntry;
import net.sf.jabref.logic.autocompleter.AutoCompleter;
import net.sf.jabref.logic.bibtex.BibEntryWriter;
import net.sf.jabref.logic.bibtex.LatexFieldFormatter;
import net.sf.jabref.logic.bibtex.LatexFieldFormatterPreferences;
import net.sf.jabref.logic.l10n.Localization;
import net.sf.jabref.logic.labelpattern.LabelPatternUtil;
import net.sf.jabref.logic.search.SearchQueryHighlightListener;
import net.sf.jabref.logic.util.date.TimeStamp;
import net.sf.jabref.model.EntryTypes;
import net.sf.jabref.model.database.BibDatabase;
import net.sf.jabref.model.database.BibDatabaseMode;
import net.sf.jabref.model.entry.BibEntry;
import net.sf.jabref.model.entry.EntryConverter;
import net.sf.jabref.model.entry.EntryType;
import net.sf.jabref.model.entry.FieldName;
import net.sf.jabref.model.entry.FieldProperties;
import net.sf.jabref.model.entry.InternalBibtexFields;
import net.sf.jabref.model.event.FieldChangedEvent;
import net.sf.jabref.preferences.JabRefPreferences;
import net.sf.jabref.specialfields.SpecialFieldUpdateListener;

import com.google.common.eventbus.Subscribe;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * GUI component that allows editing of the fields of a BibEntry (i.e. the
 * one that shows up, when you double click on an entry in the table)
 * <p>
 * It hosts the tabs (required, general, optional) and the buttons to the left.
 * <p>
 * EntryEditor also registers itself to the event bus, receiving
 * events whenever a field of the entry changes, enabling the text fields to
 * update themselves if the change is made from somewhere else.
 */
public class EntryEditor extends JPanel implements EntryContainer {

    private static final Log LOGGER = LogFactory.getLog(EntryEditor.class);

    // A reference to the entry this object works on.
    private BibEntry entry;
    // The currently displayed type
    private final String displayedBibEntryType;

    // The action concerned with closing the window.
    private final CloseAction closeAction;

    // The action that deletes the current entry, and closes the editor.
    private final DeleteAction deleteAction = new DeleteAction();

    // Actions for switching to next/previous entry.
    private final AbstractAction nextEntryAction = new NextEntryAction();
    private final AbstractAction prevEntryAction = new PrevEntryAction();

    // The action concerned with storing a field value.
    private final StoreFieldAction storeFieldAction;

    // The actions concerned with switching the panels.
    private final SwitchLeftAction switchLeftAction = new SwitchLeftAction();
    private final SwitchRightAction switchRightAction = new SwitchRightAction();

    // The action which generates a bibtexkey for this entry.
    private final GenerateKeyAction generateKeyAction;

    // UGLY HACK to have a pointer to the fileListEditor to call autoSetLinks()
    private FileListEditor fileListEditor;
    private final AutoLinkAction autoLinkAction = new AutoLinkAction();

    private final AbstractAction writeXmp;

    private final SaveDatabaseAction saveDatabaseAction = new SaveDatabaseAction();

    private final JPanel srcPanel = new JPanel();

    private JTextArea source;

    private final JTabbedPane tabbed = new JTabbedPane();

    private final JabRefFrame frame;

    private final BasePanel panel;

    private final Set<FieldContentSelector> contentSelectors = new HashSet<>();

    private boolean updateSource = true; // This can be set to false to stop the source
    private boolean movingToDifferentEntry; // Indicates that we are about to go to the next or previous entry
    private boolean validEntry = true;

    private final List<Object> tabs = new ArrayList<>();

    // text area from getting updated. This is used in cases where the source
    // couldn't be parsed, and the user is given the option to edit it.
    private boolean lastSourceAccepted = true; // This indicates whether the last

    // attempt
    // at parsing the source was successful. It is used to determine whether the
    // dialog should close; it should stay open if the user received an error
    // message about the source, whatever he or she chose to do about it.
    private String lastSourceStringAccepted; // This is used to prevent double

    // fields.
    // These values can be used to calculate the preferred height for the form.
    // reqW starts at 1 because it needs room for the bibtex key field.
    private int sourceIndex = -1; // The index the source panel has in tabbed.

    private final HelpAction helpAction;

    private final UndoAction undoAction = new UndoAction();

    private final RedoAction redoAction = new RedoAction();

    private final TabListener tabListener = new TabListener();

    public EntryEditor(JabRefFrame frame, BasePanel panel, BibEntry entry) {
        this.frame = frame;
        this.panel = panel;
        this.entry = entry;

        entry.registerListener(this);
        entry.registerListener(SpecialFieldUpdateListener.getInstance());

        displayedBibEntryType = entry.getType();

        helpAction = new HelpAction(HelpFile.ENTRY_EDITOR, IconTheme.JabRefIcon.HELP.getIcon());
        closeAction = new CloseAction();
        generateKeyAction = new GenerateKeyAction();
        storeFieldAction = new StoreFieldAction();
        writeXmp = new WriteXMPEntryEditorAction(panel, this);

        BorderLayout borderLayout = new BorderLayout();
        setLayout(borderLayout);
        setupToolBar();
        setupFieldPanels();
        setupSourcePanel();
        add(tabbed, BorderLayout.CENTER);
        tabbed.addChangeListener(tabListener);
        if (Globals.prefs.getBoolean(JabRefPreferences.DEFAULT_SHOW_SOURCE)) {
            tabbed.setSelectedIndex(sourceIndex);
        }

        updateAllFields();
        if (this.fileListEditor != null) {
            this.fileListEditor.adjustColumnWidth();
        }
    }

    private void setupFieldPanels() {
        tabbed.removeAll();
        tabs.clear();

        EntryType type = EntryTypes.getTypeOrDefault(entry.getType(),
                this.frame.getCurrentBasePanel().getBibDatabaseContext().getMode());

        // required fields
        List<String> requiredFields = addRequiredTab(type);

        // optional fields
        List<String> displayedOptionalFields = new ArrayList<>();

        if ((type.getOptionalFields() != null) && !type.getOptionalFields().isEmpty()) {
            if (!frame.getCurrentBasePanel().getBibDatabaseContext().isBiblatexMode()) {
                addOptionalTab(type);
            } else {
                displayedOptionalFields.addAll(type.getPrimaryOptionalFields());
                displayedOptionalFields.addAll(type.getSecondaryOptionalFields());

                addOptionalTab(type);

                Set<String> deprecatedFields = new HashSet<>(EntryConverter.FIELD_ALIASES_TEX_TO_LTX.keySet());
                deprecatedFields.add(FieldName.YEAR);
                deprecatedFields.add(FieldName.MONTH);
                List<String> secondaryOptionalFields = type.getSecondaryOptionalFields();
                List<String> optionalFieldsNotPrimaryOrDeprecated = new ArrayList<>(secondaryOptionalFields);
                optionalFieldsNotPrimaryOrDeprecated.removeAll(deprecatedFields);

                // Get list of all optional fields of this entry and their aliases
                Set<String> optionalFieldsAndAliases = new HashSet<>();
                for (String field : type.getOptionalFields()) {
                    optionalFieldsAndAliases.add(field);
                    if (EntryConverter.FIELD_ALIASES_LTX_TO_TEX.containsKey(field)) {
                        optionalFieldsAndAliases.add(EntryConverter.FIELD_ALIASES_LTX_TO_TEX.get(field));
                    }
                }

                // Get all optional fields which are deprecated
                Set<String> usedOptionalFieldsDeprecated = new HashSet<>(deprecatedFields);
                usedOptionalFieldsDeprecated.retainAll(optionalFieldsAndAliases);

                // Add tabs
                EntryEditorTab optPan2 = new EntryEditorTab(frame, panel, optionalFieldsNotPrimaryOrDeprecated,
                        this, false, true, Localization.lang("Optional fields 2"));
                if (optPan2.fileListEditor != null) {
                    fileListEditor = optPan2.fileListEditor;
                }
                tabbed.addTab(Localization.lang("Optional fields 2"), IconTheme.JabRefIcon.OPTIONAL.getSmallIcon(),
                        optPan2.getPane(), Localization.lang("Show optional fields"));
                tabs.add(optPan2);

                if (!usedOptionalFieldsDeprecated.isEmpty()) {
                    EntryEditorTab optPan3;
                    optPan3 = new EntryEditorTab(frame, panel, new ArrayList<>(usedOptionalFieldsDeprecated), this,
                            false, true, Localization.lang("Deprecated fields"));
                    if (optPan3.fileListEditor != null) {
                        fileListEditor = optPan3.fileListEditor;
                    }
                    tabbed.addTab(Localization.lang("Deprecated fields"),
                            IconTheme.JabRefIcon.OPTIONAL.getSmallIcon(), optPan3.getPane(),
                            Localization.lang("Show deprecated BibTeX fields"));
                    tabs.add(optPan3);
                }
            }
        }

        // other fields
        List<String> displayedFields = Stream.concat(requiredFields.stream(), displayedOptionalFields.stream())
                .map(String::toLowerCase).collect(Collectors.toList());
        List<String> otherFields = entry.getFieldNames().stream().map(String::toLowerCase)
                .filter(f -> !displayedFields.contains(f)).collect(Collectors.toList());
        otherFields.remove(BibEntry.KEY_FIELD);
        otherFields.removeAll(Globals.prefs.getCustomTabFieldNames());

        if (!otherFields.isEmpty()) {
            addOtherTab(otherFields);
        }

        // general fields from preferences
        addGeneralTabs();
        // source tab
        addSourceTab();
    }

    private void addGeneralTabs() {
        EntryEditorTabList tabList = Globals.prefs.getEntryEditorTabList();
        for (int i = 0; i < tabList.getTabCount(); i++) {
            EntryEditorTab newTab = new EntryEditorTab(frame, panel, tabList.getTabFields(i), this, false, false,
                    tabList.getTabName(i));
            if (newTab.fileListEditor != null) {
                fileListEditor = newTab.fileListEditor;
            }
            tabbed.addTab(tabList.getTabName(i), newTab.getPane());
            tabs.add(newTab);
        }
    }

    private void addSourceTab() {
        srcPanel.setName(Localization.lang("BibTeX source"));
        tabbed.addTab(Localization.lang("BibTeX source"), IconTheme.JabRefIcon.SOURCE.getSmallIcon(), srcPanel,
                Localization.lang("Show/edit BibTeX source"));
        tabs.add(srcPanel);
        sourceIndex = tabs.size() - 1; // Set the sourceIndex variable.
        srcPanel.setFocusCycleRoot(true);
    }

    private void addOtherTab(List<String> otherFields) {
        EntryEditorTab otherPanel = new EntryEditorTab(frame, panel, otherFields, this, false, false,
                Localization.lang("Other fields"));
        if (otherPanel.fileListEditor != null) {
            fileListEditor = otherPanel.fileListEditor;
        }
        tabbed.addTab(Localization.lang("Other fields"), IconTheme.JabRefIcon.OPTIONAL.getSmallIcon(),
                otherPanel.getPane(), Localization.lang("Show remaining fields"));
        tabs.add(otherPanel);
    }

    private List<String> addRequiredTab(EntryType type) {
        List<String> requiredFields = type.getRequiredFieldsFlat();

        EntryEditorTab requiredPanel = new EntryEditorTab(frame, panel, requiredFields, this, true, false,
                Localization.lang("Required fields"));
        if (requiredPanel.fileListEditor != null) {
            fileListEditor = requiredPanel.fileListEditor;
        }
        tabbed.addTab(Localization.lang("Required fields"), IconTheme.JabRefIcon.REQUIRED.getSmallIcon(),
                requiredPanel.getPane(), Localization.lang("Show required fields"));
        tabs.add(requiredPanel);
        return requiredFields;
    }

    private void addOptionalTab(EntryType type) {
        EntryEditorTab optionalPanel = new EntryEditorTab(frame, panel, type.getPrimaryOptionalFields(), this,
                false, true, Localization.lang("Optional fields"));

        if (optionalPanel.fileListEditor != null) {
            fileListEditor = optionalPanel.fileListEditor;
        }
        tabbed.addTab(Localization.lang("Optional fields"), IconTheme.JabRefIcon.OPTIONAL.getSmallIcon(),
                optionalPanel.getPane(), Localization.lang("Show optional fields"));
        tabs.add(optionalPanel);
    }

    public String getDisplayedBibEntryType() {
        return displayedBibEntryType;
    }

    /**
     * @return reference to the currently edited entry
     */
    @Override
    public BibEntry getEntry() {
        return entry;
    }

    public BibDatabase getDatabase() {
        return panel.getDatabase();
    }

    private void setupToolBar() {
        JPanel leftPan = new JPanel();
        leftPan.setLayout(new BorderLayout());
        JToolBar toolBar = new OSXCompatibleToolbar(SwingConstants.VERTICAL);

        toolBar.setBorder(null);
        toolBar.setRollover(true);

        toolBar.setMargin(new Insets(0, 0, 0, 2));

        // The toolbar carries all the key bindings that are valid for the whole
        // window.
        ActionMap actionMap = toolBar.getActionMap();
        InputMap inputMap = toolBar.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);

        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.CLOSE_ENTRY_EDITOR), "close");
        actionMap.put("close", closeAction);
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_STORE_FIELD), "store");
        actionMap.put("store", getStoreFieldAction());
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.AUTOGENERATE_BIBTEX_KEYS), "generateKey");
        actionMap.put("generateKey", getGenerateKeyAction());
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.AUTOMATICALLY_LINK_FILES), "autoLink");
        actionMap.put("autoLink", autoLinkAction);
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_PREVIOUS_ENTRY), "prev");
        actionMap.put("prev", getPrevEntryAction());
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_NEXT_ENTRY), "next");
        actionMap.put("next", getNextEntryAction());
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.UNDO), "undo");
        actionMap.put("undo", undoAction);
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.REDO), "redo");
        actionMap.put("redo", redoAction);
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.HELP), "help");
        actionMap.put("help", getHelpAction());

        toolBar.setFloatable(false);

        // Add actions (and thus buttons)
        JButton closeBut = new JButton(closeAction);
        closeBut.setText(null);
        closeBut.setBorder(null);
        closeBut.setMargin(new Insets(8, 0, 8, 0));
        leftPan.add(closeBut, BorderLayout.NORTH);

        // Create type-label
        TypedBibEntry typedEntry = new TypedBibEntry(entry, Optional.empty(),
                panel.getBibDatabaseContext().getMode());
        leftPan.add(new TypeLabel(typedEntry.getTypeForDisplay()), BorderLayout.CENTER);
        TypeButton typeButton = new TypeButton();

        toolBar.add(typeButton);
        toolBar.add(getGenerateKeyAction());
        toolBar.add(autoLinkAction);

        toolBar.add(writeXmp);

        toolBar.addSeparator();

        toolBar.add(deleteAction);
        toolBar.add(getPrevEntryAction());
        toolBar.add(getNextEntryAction());

        toolBar.addSeparator();

        toolBar.add(getHelpAction());

        Component[] comps = toolBar.getComponents();

        for (Component comp : comps) {
            ((JComponent) comp).setOpaque(false);
        }

        leftPan.add(toolBar, BorderLayout.SOUTH);
        add(leftPan, BorderLayout.WEST);
    }

    /**
     * Rebuild the field tabs. This is called e.g. when a new content selector
     * has been added.
     */
    public void rebuildPanels() {
        // Remove change listener, because the rebuilding causes meaningless
        // events and trouble:
        tabbed.removeChangeListener(tabListener);

        setupFieldPanels();
        // Add the change listener again:
        tabbed.addChangeListener(tabListener);
        revalidate();
        repaint();
    }

    /**
     * getExtra checks the field name against InternalBibtexFields.getFieldExtras(name).
     * If the name has an entry, the proper component to be shown is created and
     * returned. Otherwise, null is returned. In addition, e.g. listeners can be
     * added to the field editor, even if no component is returned.
     *
     * @param editor Field editor
     * @return Component to show, or null if none.
     */
    public Optional<JComponent> getExtra(final FieldEditor editor) {
        final String fieldName = editor.getFieldName();

        final Set<FieldProperties> fieldExtras = InternalBibtexFields.getFieldExtras(fieldName);

        // timestamp or a other field with datepicker command
        if (Globals.prefs.get(JabRefPreferences.TIME_STAMP_FIELD).equals(fieldName)
                || fieldExtras.contains(FieldProperties.DATE)) {
            // double click AND datefield => insert the current date (today)
            return FieldExtraComponents.getDateTimeExtraComponent(editor,
                    fieldExtras.contains(FieldProperties.DATE));
        } else if (fieldExtras.contains(FieldProperties.EXTERNAL)) {
            return FieldExtraComponents.getExternalExtraComponent(panel, editor);
        } else if (fieldExtras.contains(FieldProperties.JOURNAL_NAME)) {
            // Add controls for switching between abbreviated and full journal names.
            // If this field also has a FieldContentSelector, we need to combine these.
            return FieldExtraComponents.getJournalExtraComponent(frame, panel, editor, entry, contentSelectors,
                    getStoreFieldAction());
        } else if (!panel.getBibDatabaseContext().getMetaData().getContentSelectors(fieldName).isEmpty()) {
            return FieldExtraComponents.getSelectorExtraComponent(frame, panel, editor, contentSelectors,
                    getStoreFieldAction());
        } else if (fieldExtras.contains(FieldProperties.URL)) {
            return FieldExtraComponents.getURLExtraComponent(editor, getStoreFieldAction());
        } else if (fieldExtras.contains(FieldProperties.DOI)) {
            return FieldExtraComponents.getDoiExtraComponent(panel, this, editor);
        } else if (fieldExtras.contains(FieldProperties.OWNER)) {
            return FieldExtraComponents.getSetOwnerExtraComponent(editor, getStoreFieldAction());
        } else if (fieldExtras.contains(FieldProperties.YES_NO)) {
            return FieldExtraComponents.getYesNoExtraComponent(editor, this);
        } else if (fieldExtras.contains(FieldProperties.MONTH)) {
            return FieldExtraComponents.getMonthExtraComponent(editor, this,
                    frame.getCurrentBasePanel().getBibDatabaseContext().getMode());
        } else if (fieldExtras.contains(FieldProperties.GENDER)) {
            return FieldExtraComponents.getGenderExtraComponent(editor, this);
        } else if (fieldExtras.contains(FieldProperties.EDITOR_TYPE)) {
            return FieldExtraComponents.getEditorTypeExtraComponent(editor, this);
        } else if (fieldExtras.contains(FieldProperties.PAGINATION)) {
            return FieldExtraComponents.getPaginationExtraComponent(editor, this);
        } else if (fieldExtras.contains(FieldProperties.TYPE)) {
            return FieldExtraComponents.getTypeExtraComponent(editor, this,
                    "patent".equalsIgnoreCase(entry.getType()));
        } else if (fieldExtras.contains(FieldProperties.CROSSREF)) {
            return FieldExtraComponents.getCrossrefExtraComponent(editor, frame.getCurrentBasePanel());
        }
        return Optional.empty();
    }

    private void setupSourcePanel() {
        source = new JTextAreaWithHighlighting();
        panel.getSearchBar().getSearchQueryHighlightObservable()
                .addSearchListener((SearchQueryHighlightListener) source);

        source.setEditable(true);
        source.setLineWrap(true);
        source.setTabSize(Globals.prefs.getInt(JabRefPreferences.INDENT));
        source.addFocusListener(new FieldEditorFocusListener());
        // Add the global focus listener, so a menu item can see if this field was focused when an action was called.
        source.addFocusListener(Globals.getFocusListener());
        source.setFont(new Font("Monospaced", Font.PLAIN, Globals.prefs.getInt(JabRefPreferences.FONT_SIZE)));
        setupJTextComponent(source);
        updateSource();

        JScrollPane scrollPane = new JScrollPane(source, ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
                ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);

        srcPanel.setLayout(new BorderLayout());
        srcPanel.add(scrollPane, BorderLayout.CENTER);
    }

    public void updateSource() {
        if (updateSource) {

            try {
                String srcString = getSourceString(entry, panel.getBibDatabaseContext().getMode());
                source.setText(srcString);
                lastSourceStringAccepted = srcString;

                //////////////////////////////////////////////////////////
                // Set the current Entry to be selected.
                // Fixes the bug of losing selection after, e.g.
                // an autogeneration of a BibTeX key.
                // - ILC (16/02/2010) -
                //////////////////////////////////////////////////////////
                SwingUtilities.invokeLater(() -> {
                    final int row = panel.getMainTable().findEntry(entry);
                    if (row >= 0) {
                        if (panel.getMainTable().getSelectedRowCount() == 0) {
                            panel.getMainTable().setRowSelectionInterval(row, row);
                        }
                        panel.getMainTable().ensureVisible(row);
                    }
                });
            } catch (IOException ex) {
                source.setText(ex.getMessage() + "\n\n"
                        + Localization.lang("Correct the entry, and " + "reopen editor to display/edit source."));
                source.setEditable(false);
                LOGGER.debug("Incorrect entry", ex);
            }

        }
    }

    public static String getSourceString(BibEntry entry, BibDatabaseMode type) throws IOException {
        StringWriter stringWriter = new StringWriter(200);
        LatexFieldFormatter formatter = LatexFieldFormatter
                .buildIgnoreHashes(LatexFieldFormatterPreferences.fromPreferences(Globals.prefs));
        new BibEntryWriter(formatter, false).writeWithoutPrependedNewlines(entry, stringWriter, type);

        return stringWriter.getBuffer().toString();
    }

    /**
     * NOTE: This method is only used for the source panel, not for the
     * other tabs. Look at EntryEditorTab for the setup of text components
     * in the other tabs.
     */
    private void setupJTextComponent(JTextComponent textComponent) {
        // Set up key bindings and focus listener for the FieldEditor.
        InputMap inputMap = textComponent.getInputMap(JComponent.WHEN_FOCUSED);
        ActionMap actionMap = textComponent.getActionMap();

        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_STORE_FIELD), "store");
        actionMap.put("store", getStoreFieldAction());

        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_NEXT_PANEL), "right");
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_NEXT_PANEL_2), "right");
        actionMap.put("right", getSwitchRightAction());

        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_PREVIOUS_PANEL), "left");
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_PREVIOUS_PANEL_2), "left");
        actionMap.put("left", getSwitchLeftAction());

        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.HELP), "help");
        actionMap.put("help", getHelpAction());
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.SAVE_DATABASE), "save");
        actionMap.put("save", getSaveDatabaseAction());

        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.NEXT_TAB), "nexttab");
        actionMap.put("nexttab", frame.nextTab);
        inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.PREVIOUS_TAB), "prevtab");
        actionMap.put("prevtab", frame.prevTab);

        Set<AWTKeyStroke> keys = new HashSet<>(
                textComponent.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
        keys.clear();
        keys.add(AWTKeyStroke.getAWTKeyStroke("pressed TAB"));
        textComponent.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, keys);
        keys = new HashSet<>(textComponent.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
        keys.clear();
        keys.add(KeyStroke.getKeyStroke("shift pressed TAB"));
        textComponent.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, keys);

        textComponent.addFocusListener(new FieldListener());
    }

    @Override
    public void requestFocus() {
        activateVisible();
    }

    private void activateVisible() {
        Object activeTab = tabs.get(tabbed.getSelectedIndex());

        if (activeTab instanceof EntryEditorTab) {
            ((EntryEditorTab) activeTab).activate();
        } else {
            new FocusRequester(source);
        }
    }

    /**
     * Reports the enabled status of the editor, as set by setEnabled()
     */
    @Override
    public boolean isEnabled() {
        return source.isEnabled();
    }

    /**
     * Sets the enabled status of all text fields of the entry editor.
     */
    @Override
    public void setEnabled(boolean enabled) {
        for (Object tab : tabs) {
            if (tab instanceof EntryEditorTab) {
                ((EntryEditorTab) tab).setEnabled(enabled);
            }
        }
        source.setEnabled(enabled);

    }

    /**
     * Centers the given row, and highlights it.
     *
     * @param row an <code>int</code> value
     */
    private void scrollTo(int row) {
        movingToDifferentEntry = true;
        panel.getMainTable().setRowSelectionInterval(row, row);
        panel.getMainTable().ensureVisible(row);
    }

    /**
     * Makes sure the current edit is stored.
     */
    public void storeCurrentEdit() {
        Component comp = Globals.getFocusListener().getFocused();
        if (Objects.equals(comp, source) || ((comp instanceof FieldEditor) && this.isAncestorOf(comp))) {
            if (comp instanceof FieldEditor) {
                ((FieldEditor) comp).clearAutoCompleteSuggestion();
            }
            getStoreFieldAction().actionPerformed(new ActionEvent(comp, 0, ""));
        }
    }

    /**
     * Returns the index of the active (visible) panel.
     *
     * @return an <code>int</code> value
     */
    public int getVisiblePanel() {
        return tabbed.getSelectedIndex();
    }

    /**
     * Returns the name of the currently selected component.
     */
    public String getVisiblePanelName() {
        return tabbed.getSelectedComponent().getName();
    }

    public void setVisiblePanel(String name) {
        for (int i = 0; i < tabbed.getTabCount(); ++i) {
            if ((tabbed.getComponent(i).getName() != null) && tabbed.getComponent(i).getName().equals(name)) {
                tabbed.setSelectedIndex(i);
                return;
            }
        }
        if (tabbed.getTabCount() > 0) {
            tabbed.setSelectedIndex(0);
        }
    }

    public void setFocusToField(String fieldName) {
        for (Object tab : tabs) {
            if ((tab instanceof EntryEditorTab) && ((EntryEditorTab) tab).getFields().contains(fieldName)) {
                EntryEditorTab entryEditorTab = (EntryEditorTab) tab;
                setVisiblePanel(entryEditorTab.getTabTitle());
                entryEditorTab.setActive(fieldName);
                entryEditorTab.activate();
            }
        }
    }

    /**
     * Updates this editor to show the given entry, regardless of type
     * correspondence.
     *
     * @param switchEntry a <code>BibEntry</code> value
     */
    public synchronized void switchTo(BibEntry switchEntry) {
        storeCurrentEdit();

        // Remove this instance as property listener for the entry:
        this.entry.unregisterListener(this);

        this.entry = switchEntry;

        // Register as property listener for the new entry:
        this.entry.registerListener(this);

        updateAllFields();
        validateAllFields();
        updateSource();
        panel.newEntryShowing(switchEntry);

    }

    private boolean storeSource() {
        BibtexParser bibtexParser = new BibtexParser(new StringReader(source.getText()));

        try {
            ParserResult parserResult = bibtexParser.parse();
            BibDatabase database = parserResult.getDatabase();

            if (database.getEntryCount() > 1) {
                throw new IllegalStateException("More than one entry found.");
            }

            if (!database.hasEntries()) {
                if (parserResult.hasWarnings()) {
                    // put the warning into as exception text -> it will be displayed to the user
                    throw new IllegalStateException(parserResult.warnings().get(0));
                } else {
                    throw new IllegalStateException("No entries found.");
                }
            }

            NamedCompound compound = new NamedCompound(Localization.lang("source edit"));
            BibEntry newEntry = database.getEntries().get(0);
            String newKey = newEntry.getCiteKey();
            boolean entryChanged = false;
            boolean duplicateWarning = false;
            boolean emptyWarning = (newKey == null) || newKey.isEmpty();

            if (panel.getDatabase().setCiteKeyForEntry(entry, newKey)) {
                duplicateWarning = true;
            }

            // First, remove fields that the user has removed.
            for (String field : entry.getFieldNames()) {
                if (InternalBibtexFields.isDisplayableField(field) && !newEntry.hasField(field)) {
                    compound.addEdit(new UndoableFieldChange(entry, field, entry.getField(field), null));
                    entry.clearField(field);
                    entryChanged = true;
                }
            }

            // Then set all fields that have been set by the user.
            for (String field : newEntry.getFieldNames()) {
                String oldValue = entry.getField(field);
                String newValue = newEntry.getField(field);
                if (!Objects.equals(oldValue, newValue)) {
                    // Test if the field is legally set.
                    new LatexFieldFormatter(LatexFieldFormatterPreferences.fromPreferences(Globals.prefs))
                            .format(newValue, field);

                    compound.addEdit(new UndoableFieldChange(entry, field, oldValue, newValue));
                    entry.setField(field, newValue);
                    entryChanged = true;
                }
            }

            // See if the user has changed the entry type:
            if (!Objects.equals(newEntry.getType(), entry.getType())) {
                compound.addEdit(new UndoableChangeType(entry, entry.getType(), newEntry.getType()));
                entry.setType(newEntry.getType());
                entryChanged = true;
            }
            compound.end();

            if (!entryChanged) {
                return true;
            }

            panel.getUndoManager().addEdit(compound);

            if (duplicateWarning) {
                warnDuplicateBibtexkey();
            } else if (emptyWarning) {
                warnEmptyBibtexkey();
            } else {
                panel.output(Localization.lang("Stored entry") + '.');
            }

            lastSourceStringAccepted = source.getText();
            // Update UI
            // TODO: we need to repaint the entryeditor if fields that are not displayed have been added
            panel.updateEntryEditorIfShowing();
            lastSourceAccepted = true;
            updateSource = true;
            // TODO: does updating work properly after source stored?
            panel.markBaseChanged();

            SwingUtilities.invokeLater(() -> {
                final int row = panel.getMainTable().findEntry(entry);
                if (row >= 0) {
                    panel.getMainTable().ensureVisible(row);
                }
            });

            return true;
        } catch (IllegalStateException | IOException ex) {
            // The source couldn't be parsed, so the user is given an
            // error message, and the choice to keep or revert the contents
            // of the source text field.
            updateSource = false;
            lastSourceAccepted = false;
            tabbed.setSelectedComponent(srcPanel);

            Object[] options = { Localization.lang("Edit"), Localization.lang("Revert to original source") };

            int answer = JOptionPane.showOptionDialog(frame, Localization.lang("Error") + ": " + ex.getMessage(),
                    Localization.lang("Problem with parsing entry"), JOptionPane.YES_NO_OPTION,
                    JOptionPane.ERROR_MESSAGE, null, options, options[0]);

            if (answer != 0) {
                updateSource = true;
                updateSource();
            }

            LOGGER.debug("Incorrect source", ex);

            return false;
        }
    }

    private void setField(String fieldName, String newFieldData) {
        for (Object tab : tabs) {
            if (tab instanceof EntryEditorTab) {
                ((EntryEditorTab) tab).updateField(fieldName, newFieldData);
            }
        }
    }

    /**
     * Sets all the text areas according to the shown entry.
     */
    public void updateAllFields() {
        for (Object tab : tabs) {
            if (tab instanceof EntryEditorTab) {
                ((EntryEditorTab) tab).setEntry(entry);
            }
        }
    }

    /**
     * Removes the "invalid field" color from all text areas.
     */
    public void validateAllFields() {
        for (Object tab : tabs) {
            if (tab instanceof EntryEditorTab) {
                ((EntryEditorTab) tab).validateAllFields();
            }
        }
    }

    public void updateAllContentSelectors() {
        if (!contentSelectors.isEmpty()) {
            for (FieldContentSelector contentSelector : contentSelectors) {
                contentSelector.rebuildComboBox();
            }
        }
    }

    /**
     * Update the JTextArea when a field has changed.
     */
    @Subscribe
    public void listen(FieldChangedEvent fieldChangedEvent) {
        String newValue = fieldChangedEvent.getNewValue() == null ? "" : fieldChangedEvent.getNewValue();
        setField(fieldChangedEvent.getFieldName(), newValue);
    }

    public void updateField(final Object sourceObject) {
        getStoreFieldAction().actionPerformed(new ActionEvent(sourceObject, 0, ""));
    }

    public void setMovingToDifferentEntry() {
        movingToDifferentEntry = true;
    }

    private class TypeButton extends JButton {
        public TypeButton() {
            super(IconTheme.JabRefIcon.EDIT.getIcon());
            setToolTipText(Localization.lang("Change entry type"));
            addActionListener(e -> showChangeEntryTypePopupMenu());
        }
    }

    private void showChangeEntryTypePopupMenu() {
        JPopupMenu typeMenu = new ChangeEntryTypeMenu().getChangeentryTypePopupMenu(panel);
        typeMenu.show(this, 0, 0);
    }

    private class TypeLabel extends JLabel {
        public TypeLabel(String type) {
            super(type);
            setUI(new VerticalLabelUI(false));
            setForeground(GUIGlobals.ENTRY_EDITOR_LABEL_COLOR);
            setHorizontalAlignment(SwingConstants.RIGHT);
            setFont(new Font("dialog", Font.ITALIC + Font.BOLD, 18));

            // Add a mouse listener so the user can right-click the type label to change the entry type:
            addMouseListener(new MouseAdapter() {

                @Override
                public void mouseReleased(MouseEvent e) {
                    if (e.isPopupTrigger() || (e.getButton() == MouseEvent.BUTTON3)) {
                        handleTypeChange();
                    }
                }

                @Override
                public void mouseClicked(MouseEvent e) {
                    if (e.isPopupTrigger() || (e.getButton() == MouseEvent.BUTTON3)) {
                        handleTypeChange();
                    }
                }

                private void handleTypeChange() {
                    showChangeEntryTypePopupMenu();
                }
            });
        }

        @Override
        public void paintComponent(Graphics g) {
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            super.paintComponent(g2);
        }
    }

    private class FieldListener extends FocusAdapter {

        /*
         * Focus listener that fires the storeFieldAction when a TextArea
         * loses focus.
         */
        @Override
        public void focusGained(FocusEvent e) {
            // Do nothing
        }

        @Override
        public void focusLost(FocusEvent event) {
            if (!event.isTemporary()) {
                updateField(event.getSource());
            }
        }
    }

    private class TabListener implements ChangeListener {
        @Override
        public void stateChanged(ChangeEvent event) {
            // We tell the editor tab to update all its fields.
            //  This makes sure they are updated even if the tab we
            // just left contained one
            // or more of the same fields as this one:
            SwingUtilities.invokeLater(() -> {
                Object activeTab = tabs.get(tabbed.getSelectedIndex());
                if (activeTab instanceof EntryEditorTab) {
                    ((EntryEditorTab) activeTab).updateAll();
                    activateVisible();
                }
            });
        }
    }

    class DeleteAction extends AbstractAction {
        public DeleteAction() {
            super(Localization.lang("Delete"), IconTheme.JabRefIcon.DELETE_ENTRY.getIcon());
            putValue(Action.SHORT_DESCRIPTION, Localization.lang("Delete entry"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // Show confirmation dialog if not disabled:
            boolean goOn = panel.showDeleteConfirmationDialog(1);

            if (!goOn) {
                return;
            }

            panel.entryEditorClosing(EntryEditor.this);
            panel.getDatabase().removeEntry(entry);
            panel.markBaseChanged();
            panel.getUndoManager().addEdit(new UndoableRemoveEntry(panel.getDatabase(), entry, panel));
            panel.output(Localization.lang("Deleted entry"));
        }
    }

    class CloseAction extends AbstractAction {
        public CloseAction() {
            super(Localization.lang("Close window"), IconTheme.JabRefIcon.CLOSE.getSmallIcon());
            putValue(Action.SHORT_DESCRIPTION, Localization.lang("Close window"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (tabbed.getSelectedComponent() == srcPanel) {
                updateField(source);
                if (lastSourceAccepted) {
                    panel.entryEditorClosing(EntryEditor.this);
                }
            } else {
                panel.entryEditorClosing(EntryEditor.this);
            }
        }
    }

    public class StoreFieldAction extends AbstractAction {

        public StoreFieldAction() {
            super("Store field value");
            putValue(Action.SHORT_DESCRIPTION, "Store field value");
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            boolean movingAway = movingToDifferentEntry;
            movingToDifferentEntry = false;

            if (event.getSource() instanceof TextField) {
                // Storage from bibtex key field.
                TextField textField = (TextField) event.getSource();
                String oldValue = entry.getCiteKey();
                String newValue = textField.getText();

                if (newValue.isEmpty()) {
                    newValue = null;
                }

                if (((oldValue == null) && (newValue == null)) || (Objects.equals(oldValue, newValue))) {
                    return; // No change.
                }

                // Make sure the key is legal:
                String cleaned = LabelPatternUtil.checkLegalKey(newValue);
                if ((cleaned == null) || cleaned.equals(newValue)) {
                    textField.setValidBackgroundColor();
                } else {
                    JOptionPane.showMessageDialog(frame, Localization.lang("Invalid BibTeX key"),
                            Localization.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
                    textField.setInvalidBackgroundColor();
                    return;
                }

                boolean isDuplicate = panel.getDatabase().setCiteKeyForEntry(entry, newValue);

                if (newValue == null) {
                    warnEmptyBibtexkey();
                } else {
                    if (isDuplicate) {
                        warnDuplicateBibtexkey();
                    } else {
                        panel.output(Localization.lang("BibTeX key is unique."));
                    }
                }

                // Add an UndoableKeyChange to the baseframe's undoManager.
                UndoableKeyChange undoableKeyChange = new UndoableKeyChange(panel.getDatabase(), entry, oldValue,
                        newValue);
                if (TimeStamp.updateTimeStampIsSet()) {
                    NamedCompound ce = new NamedCompound(undoableKeyChange.getPresentationName());
                    ce.addEdit(undoableKeyChange);
                    TimeStamp.doUpdateTimeStamp(entry)
                            .ifPresent(fieldChange -> ce.addEdit(new UndoableFieldChange(fieldChange)));
                    ce.end();
                    panel.getUndoManager().addEdit(ce);
                } else {
                    panel.getUndoManager().addEdit(undoableKeyChange);
                }

                textField.setValidBackgroundColor();

                if (textField.getTextComponent().hasFocus()) {
                    textField.setActiveBackgroundColor();
                }
                updateSource();
                panel.markBaseChanged();
            } else if (event.getSource() instanceof FieldEditor) {
                String toSet = null;
                FieldEditor fieldEditor = (FieldEditor) event.getSource();
                boolean set;
                // Trim the whitespace off this value
                String currentText = fieldEditor.getText();
                String trim = currentText.trim();
                if (!trim.isEmpty()) {
                    toSet = trim;
                }

                // We check if the field has changed, since we don't want to
                // mark the base as changed unless we have a real change.
                if (toSet == null) {
                    set = entry.hasField(fieldEditor.getFieldName());
                } else {
                    set = !((entry.hasField(fieldEditor.getFieldName()))
                            && toSet.equals(entry.getField(fieldEditor.getFieldName())));
                }

                if (set) {
                    try {
                        // The following statement attempts to write the
                        // new contents into a StringWriter, and this will
                        // cause an IOException if the field is not
                        // properly formatted. If that happens, the field
                        // is not stored and the textarea turns red.
                        if (toSet != null) {
                            new LatexFieldFormatter(LatexFieldFormatterPreferences.fromPreferences(Globals.prefs))
                                    .format(toSet, fieldEditor.getFieldName());
                        }

                        String oldValue = entry.getField(fieldEditor.getFieldName());

                        if (toSet == null) {
                            entry.clearField(fieldEditor.getFieldName());
                        } else {
                            entry.setField(fieldEditor.getFieldName(), toSet);
                        }

                        fieldEditor.setValidBackgroundColor();

                        // See if we need to update an AutoCompleter instance:
                        AutoCompleter<String> aComp = panel.getAutoCompleters().get(fieldEditor.getFieldName());
                        if (aComp != null) {
                            aComp.addBibtexEntry(entry);
                        }

                        // Add an UndoableFieldChange to the baseframe's undoManager.
                        UndoableFieldChange undoableFieldChange = new UndoableFieldChange(entry,
                                fieldEditor.getFieldName(), oldValue, toSet);
                        if (TimeStamp.updateTimeStampIsSet()) {
                            NamedCompound ce = new NamedCompound(undoableFieldChange.getPresentationName());
                            ce.addEdit(undoableFieldChange);

                            TimeStamp.doUpdateTimeStamp(entry)
                                    .ifPresent(fieldChange -> ce.addEdit(new UndoableFieldChange(fieldChange)));
                            ce.end();

                            panel.getUndoManager().addEdit(ce);

                        } else {
                            panel.getUndoManager().addEdit(undoableFieldChange);
                        }
                        updateSource();
                        panel.markBaseChanged();
                    } catch (IllegalArgumentException ex) {
                        JOptionPane.showMessageDialog(frame, Localization.lang("Error") + ": " + ex.getMessage(),
                                Localization.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
                        fieldEditor.setInvalidBackgroundColor();
                        LOGGER.debug("Error setting field", ex);
                    }
                } else {
                    // set == false
                    // We set the field and label color.
                    fieldEditor.setValidBackgroundColor();
                }
                if (fieldEditor.getTextComponent().hasFocus()) {
                    fieldEditor.setBackground(GUIGlobals.ACTIVE_EDITOR_COLOR);
                }
            } else if (source.isEditable() && !source.getText().equals(lastSourceStringAccepted)) {
                validEntry = storeSource();
            }

            // Make sure we scroll to the entry if it moved in the table.
            // Should only be done if this editor is currently showing:
            if (!movingAway && isShowing()) {
                SwingUtilities.invokeLater(() -> {
                    final int row = panel.getMainTable().findEntry(entry);
                    if (row >= 0) {
                        panel.getMainTable().ensureVisible(row);
                    }
                });
            }
        }
    }

    class SwitchLeftAction extends AbstractAction {
        public SwitchLeftAction() {
            super("Switch to the panel to the left");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int i = tabbed.getSelectedIndex();
            tabbed.setSelectedIndex(i > 0 ? i - 1 : tabbed.getTabCount() - 1);

            activateVisible();
        }
    }

    class SwitchRightAction extends AbstractAction {
        public SwitchRightAction() {
            super("Switch to the panel to the right");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int i = tabbed.getSelectedIndex();
            tabbed.setSelectedIndex(i < (tabbed.getTabCount() - 1) ? i + 1 : 0);
            activateVisible();

        }
    }

    class NextEntryAction extends AbstractAction {
        public NextEntryAction() {
            super(Localization.lang("Next entry"), IconTheme.JabRefIcon.DOWN.getIcon());

            putValue(Action.SHORT_DESCRIPTION, Localization.lang("Next entry"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {

            int thisRow = panel.getMainTable().findEntry(entry);
            int newRow;

            if ((thisRow + 1) < panel.getDatabase().getEntryCount()) {
                newRow = thisRow + 1;
            } else if (thisRow > 0) {
                newRow = 0;
            } else {
                return; // newRow is still -1, so we can assume the database has
                // only one entry.
            }

            scrollTo(newRow);
            panel.getMainTable().setRowSelectionInterval(newRow, newRow);

        }
    }

    class PrevEntryAction extends AbstractAction {
        public PrevEntryAction() {
            super(Localization.lang("Previous entry"), IconTheme.JabRefIcon.UP.getIcon());

            putValue(Action.SHORT_DESCRIPTION, Localization.lang("Previous entry"));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int thisRow = panel.getMainTable().findEntry(entry);
            int newRow;

            if ((thisRow - 1) >= 0) {
                newRow = thisRow - 1;
            } else if (thisRow != (panel.getDatabase().getEntryCount() - 1)) {
                newRow = panel.getDatabase().getEntryCount() - 1;
            } else {
                return; // newRow is still -1, so we can assume the database has
                // only one entry.

            }

            scrollTo(newRow);
            panel.getMainTable().setRowSelectionInterval(newRow, newRow);

        }
    }

    class GenerateKeyAction extends AbstractAction {

        public GenerateKeyAction() {
            super(Localization.lang("Generate BibTeX key"), IconTheme.JabRefIcon.MAKE_KEY.getIcon());

            putValue(Action.SHORT_DESCRIPTION, Localization.lang("Generate BibTeX key"));

        }

        @Override
        public void actionPerformed(ActionEvent e) {
            // 1. get BibEntry for selected index (already have)
            // 2. update label

            // Store the current edit in case this action is called during the
            // editing of a field:
            storeCurrentEdit();

            // This is a partial clone of net.sf.jabref.gui.BasePanel.setupActions().new AbstractWorker() {...}.run()

            // this updates the table automatically, on close, but not
            // within the tab
            Object oldValue = entry.getCiteKey();

            if (oldValue != null) {
                if (Globals.prefs.getBoolean(JabRefPreferences.AVOID_OVERWRITING_KEY)) {
                    panel.output(Localization.lang(
                            "Not overwriting existing key. To change this setting, open Options -> Prefererences -> BibTeX key generator"));
                    return;
                } else if (Globals.prefs.getBoolean(JabRefPreferences.WARN_BEFORE_OVERWRITING_KEY)) {
                    CheckBoxMessage cbm = new CheckBoxMessage(
                            Localization.lang("The current BibTeX key will be overwritten. Continue?"),
                            Localization.lang("Disable this confirmation dialog"), false);
                    int answer = JOptionPane.showConfirmDialog(frame, cbm, Localization.lang("Overwrite key"),
                            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.
                        return;
                    }
                }
            }

            LabelPatternUtil.makeLabel(panel.getBibDatabaseContext().getMetaData(), panel.getDatabase(), entry,
                    Globals.prefs);

            // Store undo information:
            panel.getUndoManager().addEdit(
                    new UndoableKeyChange(panel.getDatabase(), entry, (String) oldValue, entry.getCiteKey()));

            // here we update the field
            String bibtexKeyData = entry.getCiteKey();
            setField(BibEntry.KEY_FIELD, bibtexKeyData);
            updateSource();
            panel.markBaseChanged();

        }
    }

    class UndoAction extends AbstractAction {
        public UndoAction() {
            super("Undo", IconTheme.JabRefIcon.UNDO.getIcon());
            putValue(Action.SHORT_DESCRIPTION, "Undo");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            panel.runCommand(Actions.UNDO);
        }
    }

    class RedoAction extends AbstractAction {
        public RedoAction() {
            super("Redo", IconTheme.JabRefIcon.REDO.getIcon());
            putValue(Action.SHORT_DESCRIPTION, "Redo");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            panel.runCommand(Actions.REDO);
        }
    }

    class SaveDatabaseAction extends AbstractAction {
        public SaveDatabaseAction() {
            super("Save database");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            Object activeTab = tabs.get(tabbed.getSelectedIndex());
            if (activeTab instanceof EntryEditorTab) {
                // Normal panel.
                EntryEditorTab tab = (EntryEditorTab) activeTab;
                FieldEditor fieldEditor = tab.getActive();
                fieldEditor.clearAutoCompleteSuggestion();
                updateField(fieldEditor);
            } else {
                // Source panel.
                updateField(activeTab);
            }

            if (validEntry) {
                panel.runCommand(Actions.SAVE);
            }
        }
    }

    private void warnDuplicateBibtexkey() {
        panel.output(Localization.lang("Duplicate BibTeX key") + ". "
                + Localization.lang("Grouping may not work for this entry."));
    }

    private void warnEmptyBibtexkey() {
        panel.output(Localization.lang("Empty BibTeX key") + ". "
                + Localization.lang("Grouping may not work for this entry."));
    }

    public AbstractAction getNextEntryAction() {
        return nextEntryAction;
    }

    public AbstractAction getPrevEntryAction() {
        return prevEntryAction;
    }

    public SwitchLeftAction getSwitchLeftAction() {
        return switchLeftAction;
    }

    public SwitchRightAction getSwitchRightAction() {
        return switchRightAction;
    }

    public SaveDatabaseAction getSaveDatabaseAction() {
        return saveDatabaseAction;
    }

    public HelpAction getHelpAction() {
        return helpAction;
    }

    public GenerateKeyAction getGenerateKeyAction() {
        return generateKeyAction;
    }

    public StoreFieldAction getStoreFieldAction() {
        return storeFieldAction;
    }

    private class AutoLinkAction extends AbstractAction {
        public AutoLinkAction() {
            putValue(Action.SMALL_ICON, IconTheme.JabRefIcon.AUTO_FILE_LINK.getIcon());
            putValue(Action.SHORT_DESCRIPTION,
                    Localization.lang("Automatically set file links for this entry") + " (Alt-F)");
        }

        @Override
        public void actionPerformed(ActionEvent event) {
            FileListEditor localFileListEditor = EntryEditor.this.fileListEditor;
            if (localFileListEditor == null) {
                LOGGER.warn("No file list editor found.");
            } else {
                localFileListEditor.autoSetLinks();
            }
        }
    }

}