package org.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.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
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.JMenuItem;
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 javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javafx.scene.Scene;
import javafx.scene.layout.StackPane;
import org.jabref.Globals;
import org.jabref.gui.BasePanel;
import org.jabref.gui.EntryContainer;
import org.jabref.gui.GUIGlobals;
import org.jabref.gui.IconTheme;
import org.jabref.gui.JabRefFrame;
import org.jabref.gui.OSXCompatibleToolbar;
import org.jabref.gui.actions.Actions;
import org.jabref.gui.contentselector.FieldContentSelector;
import org.jabref.gui.externalfiles.WriteXMPEntryEditorAction;
import org.jabref.gui.fieldeditors.FieldEditor;
import org.jabref.gui.fieldeditors.FieldEditorFocusListener;
import org.jabref.gui.fieldeditors.JTextAreaWithHighlighting;
import org.jabref.gui.fieldeditors.TextField;
import org.jabref.gui.help.HelpAction;
import org.jabref.gui.keyboard.KeyBinding;
import org.jabref.gui.menus.ChangeEntryTypeMenu;
import org.jabref.gui.mergeentries.EntryFetchAndMergeWorker;
import org.jabref.gui.specialfields.SpecialFieldUpdateListener;
import org.jabref.gui.undo.NamedCompound;
import org.jabref.gui.undo.UndoableChangeType;
import org.jabref.gui.undo.UndoableFieldChange;
import org.jabref.gui.undo.UndoableKeyChange;
import org.jabref.gui.undo.UndoableRemoveEntry;
import org.jabref.gui.util.component.CheckBoxMessage;
import org.jabref.gui.util.component.VerticalLabelUI;
import org.jabref.logic.TypedBibEntry;
import org.jabref.logic.autocompleter.AutoCompleter;
import org.jabref.logic.bibtex.BibEntryWriter;
import org.jabref.logic.bibtex.InvalidFieldValueException;
import org.jabref.logic.bibtex.LatexFieldFormatter;
import org.jabref.logic.bibtexkeypattern.BibtexKeyPatternUtil;
import org.jabref.logic.help.HelpFile;
import org.jabref.logic.importer.EntryBasedFetcher;
import org.jabref.logic.importer.ParserResult;
import org.jabref.logic.importer.WebFetchers;
import org.jabref.logic.importer.fileformat.BibtexParser;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.search.SearchQueryHighlightListener;
import org.jabref.logic.util.UpdateField;
import org.jabref.model.EntryTypes;
import org.jabref.model.FieldChange;
import org.jabref.model.database.BibDatabase;
import org.jabref.model.database.BibDatabaseMode;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.EntryConverter;
import org.jabref.model.entry.EntryType;
import org.jabref.model.entry.FieldName;
import org.jabref.model.entry.FieldProperty;
import org.jabref.model.entry.InternalBibtexFields;
import org.jabref.model.entry.event.FieldChangedEvent;
import org.jabref.model.entry.identifier.MathSciNetId;
import org.jabref.preferences.JabRefPreferences;
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 final BibEntry entry;
/**
* The currently displayed type
*/
private final String displayedBibEntryType;
/**
* The action concerned with closing the window.
*/
private final CloseAction closeAction = new CloseAction();
/**
* The action that deletes the current entry, and closes the editor.
*/
private final DeleteAction deleteAction = new DeleteAction();
/**
* The action for switching to the next entry.
*/
private final AbstractAction nextEntryAction = new NextEntryAction();
/**
* The action for switching to the previous entry.
*/
private final AbstractAction prevEntryAction = new PrevEntryAction();
/**
* The action concerned with storing a field value.
*/
private final StoreFieldAction storeFieldAction = new StoreFieldAction();
/**
* The action for switching to the next tab
*/
private final SwitchLeftAction switchLeftAction = new SwitchLeftAction();
/**
* The action for switching to the previous tab
*/
private final SwitchRightAction switchRightAction = new SwitchRightAction();
/**
* The action which generates a BibTeX key for this entry.
*/
private final GenerateKeyAction generateKeyAction = new GenerateKeyAction();
private final AutoLinkAction autoLinkAction = new AutoLinkAction();
private final AbstractAction writeXmp;
private final JPanel srcPanel = new JPanel();
private final JPanel relatedArticlePanel = new JPanel();
private final JTabbedPane tabbed = new JTabbedPane();
private final JabRefFrame frame;
private final BasePanel panel;
private final Set<FieldContentSelector> contentSelectors = new HashSet<>();
private final List<Object> tabs = new ArrayList<>();
private final HelpAction helpAction = new HelpAction(HelpFile.ENTRY_EDITOR, IconTheme.JabRefIcon.HELP.getIcon());
private final UndoAction undoAction = new UndoAction();
private final RedoAction redoAction = new RedoAction();
private final TabListener tabListener = new TabListener();
private final List<SearchQueryHighlightListener> searchListeners = new ArrayList<>();
private EntryEditorTabRelatedArticles relatedArticlesTab;
private JTextArea source;
private FileAnnotationTab fileAnnotationTab;
/**
* This can be set to false to stop the source 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 updateSource = true;
/**
* Indicates that we are about to go to the next or previous entry
*/
private boolean movingToDifferentEntry;
private boolean validEntry = true;
private boolean lastFieldAccepted = 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 boolean lastSourceAccepted = true;
/**
* This is used to prevent double updates after editing source.
*/
private String lastSourceStringAccepted;
/**
* The index the source panel has in tabbed.
*/
private int sourceIndex = -1;
private Action saveDatabaseAction;
public EntryEditor(JabRefFrame frame, BasePanel panel, BibEntry entry) {
this.frame = frame;
this.panel = panel;
this.entry = Objects.requireNonNull(entry);
entry.registerListener(this);
entry.registerListener(SpecialFieldUpdateListener.getInstance());
displayedBibEntryType = entry.getType();
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();
}
private static String getSourceString(BibEntry entry, BibDatabaseMode type) throws IOException {
StringWriter stringWriter = new StringWriter(200);
LatexFieldFormatter formatter = LatexFieldFormatter
.buildIgnoreHashes(Globals.prefs.getLatexFieldFormatterPreferences());
new BibEntryWriter(formatter, false).writeWithoutPrependedNewlines(entry, stringWriter, type);
return stringWriter.getBuffer().toString();
}
private void setupFieldPanels() {
tabbed.removeAll();
tabs.clear();
EntryType type = EntryTypes.getTypeOrDefault(entry.getType(),
this.frame.getCurrentBasePanel().getBibDatabaseContext().getMode());
// required fields
addRequiredTab(type);
// optional fields
Set<String> deprecatedFields = new HashSet<>(EntryConverter.FIELD_ALIASES_TEX_TO_LTX.keySet());
Set<String> usedOptionalFieldsDeprecated = new HashSet<>(deprecatedFields);
if ((type.getOptionalFields() != null) && !type.getOptionalFields().isEmpty()) {
if (!frame.getCurrentBasePanel().getBibDatabaseContext().isBiblatexMode()) {
addOptionalTab(type);
} else {
addOptionalTab(type);
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
usedOptionalFieldsDeprecated.retainAll(optionalFieldsAndAliases);
// Get other deprecated fields
usedOptionalFieldsDeprecated.add(FieldName.MONTH);
// Add tabs
EntryEditorTab optPan2 = new EntryEditorTab(frame, panel, optionalFieldsNotPrimaryOrDeprecated, this,
false, true, Localization.lang("Optional fields 2"), entry);
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"), entry);
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 = type.getAllFields().stream().map(String::toLowerCase)
.collect(Collectors.toList());
List<String> otherFields = entry.getFieldNames().stream().map(String::toLowerCase)
.filter(f -> !displayedFields.contains(f)).collect(Collectors.toList());
if (!usedOptionalFieldsDeprecated.isEmpty()) {
otherFields.removeAll(usedOptionalFieldsDeprecated);
}
otherFields.remove(BibEntry.KEY_FIELD);
otherFields.removeAll(Globals.prefs.getCustomTabFieldNames());
if (!otherFields.isEmpty()) {
addOtherTab(otherFields);
}
// general fields from preferences
addGeneralTabs();
// special tabs (like MathSciNet Reviews)
addSpecialTabs();
// pdf annotations tab
addPDFAnnotationTab();
//related articles
if (Globals.prefs.getBoolean(JabRefPreferences.SHOW_RECOMMENDATIONS)) {
addRelatedArticlesTab();
}
// 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), entry);
tabbed.addTab(tabList.getTabName(i), newTab.getPane());
tabs.add(newTab);
}
}
private void addSpecialTabs() {
// MathSciNet Review
entry.getField(FieldName.MR_NUMBER).flatMap(MathSciNetId::parse).ifPresent(mrNumber -> {
JFXPanel reviewPane = new JFXPanel();
tabbed.addTab(Localization.lang("MathSciNet Review"), reviewPane);
tabs.add(reviewPane);
// Execute on JavaFX Application Thread
Platform.runLater(() -> {
StackPane root = new MathSciNetPaneView(mrNumber).getPane();
reviewPane.setScene(new Scene(root));
});
});
}
private void addSourceTab() {
String panelName = Localization.lang("%0 source", panel.getBibDatabaseContext().getMode().getFormattedName());
String toolTip = Localization.lang("Show/edit %0 source",
panel.getBibDatabaseContext().getMode().getFormattedName());
srcPanel.setName(panelName);
tabbed.addTab(panelName, IconTheme.JabRefIcon.SOURCE.getSmallIcon(), srcPanel, toolTip);
tabs.add(srcPanel);
sourceIndex = tabs.size() - 1;
srcPanel.setFocusCycleRoot(true);
}
private void addOtherTab(List<String> otherFields) {
EntryEditorTab otherPanel = new EntryEditorTab(frame, panel, otherFields, this,
false, false, Localization.lang("Other fields"), entry);
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"), entry);
tabbed.addTab(Localization.lang("Required fields"), IconTheme.JabRefIcon.REQUIRED.getSmallIcon(), requiredPanel
.getPane(), Localization.lang("Show required fields"));
tabs.add(requiredPanel);
return requiredFields;
}
/**
* Creates the related Article Tab
*/
private void addRelatedArticlesTab() {
relatedArticlePanel.setName(Localization.lang("Related articles"));
relatedArticlePanel.setLayout(new BorderLayout());
relatedArticlesTab = new EntryEditorTabRelatedArticles(entry);
JScrollPane relatedArticleScrollPane = new JScrollPane(relatedArticlesTab,
ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,
ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
relatedArticlePanel.add(relatedArticleScrollPane, BorderLayout.CENTER);
tabbed.addTab(Localization.lang("Related articles"), IconTheme.getImage("mdl"),
relatedArticlePanel,
Localization.lang("Related articles"));
tabs.add(relatedArticlePanel);
relatedArticlePanel.setFocusCycleRoot(true);
}
private void addOptionalTab(EntryType type) {
EntryEditorTab optionalPanel = new EntryEditorTab(frame, panel, type.getPrimaryOptionalFields(), this,
false, true, Localization.lang("Optional fields"), entry);
tabbed.addTab(Localization.lang("Optional fields"), IconTheme.JabRefIcon.OPTIONAL.getSmallIcon(),
optionalPanel.getPane(), Localization.lang("Show optional fields"));
tabs.add(optionalPanel);
}
/**
* Add a tab for displaying comments from a PDF
*/
private void addPDFAnnotationTab() {
tabbed.remove(fileAnnotationTab);
tabs.remove(fileAnnotationTab);
Optional<String> field = entry.getField(FieldName.FILE);
if (field.isPresent()) {
fileAnnotationTab = new FileAnnotationTab(this, panel.getAnnotationCache());
tabbed.addTab(Localization.lang("File annotations"), IconTheme.JabRefIcon.OPTIONAL.getSmallIcon(), fileAnnotationTab,
Localization.lang("Show file annotations"));
tabs.add(fileAnnotationTab);
}
}
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", storeFieldAction);
inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.AUTOGENERATE_BIBTEX_KEYS), "generateKey");
actionMap.put("generateKey", generateKeyAction);
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", prevEntryAction);
inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.ENTRY_EDITOR_NEXT_ENTRY), "next");
actionMap.put("next", nextEntryAction);
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", helpAction);
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, panel.getBibDatabaseContext().getMode());
leftPan.add(new TypeLabel(typedEntry.getTypeForDisplay()), BorderLayout.CENTER);
TypeButton typeButton = new TypeButton();
toolBar.add(typeButton);
toolBar.add(generateKeyAction);
toolBar.add(autoLinkAction);
toolBar.add(writeXmp);
JPopupMenu fetcherPopup = new JPopupMenu();
for (EntryBasedFetcher fetcher : WebFetchers
.getEntryBasedFetchers(Globals.prefs.getImportFormatPreferences())) {
fetcherPopup.add(new JMenuItem(new AbstractAction(fetcher.getName()) {
@Override
public void actionPerformed(ActionEvent e) {
new EntryFetchAndMergeWorker(panel, getEntry(), fetcher).execute();
}
}));
}
JButton fetcherButton = new JButton(IconTheme.JabRefIcon.REFRESH.getIcon());
fetcherButton.setToolTipText(Localization.lang("Update with bibliographic information from the web"));
fetcherButton.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
fetcherPopup.show(e.getComponent(), e.getX(), e.getY());
}
});
toolBar.add(fetcherButton);
toolBar.addSeparator();
toolBar.add(deleteAction);
toolBar.add(prevEntryAction);
toolBar.add(nextEntryAction);
toolBar.addSeparator();
toolBar.add(helpAction);
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<FieldProperty> fieldExtras = InternalBibtexFields.getFieldProperties(fieldName);
if (!panel.getBibDatabaseContext().getMetaData().getContentSelectorValuesForField(fieldName).isEmpty()) {
return FieldExtraComponents.getSelectorExtraComponent(frame, panel, editor, contentSelectors,
storeFieldAction);
}
return Optional.empty();
}
private void setupSourcePanel() {
source = new JTextAreaWithHighlighting();
addSearchListener((SearchQueryHighlightListener) source);
source.setEditable(true);
source.setLineWrap(true);
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);
}
void addSearchListener(SearchQueryHighlightListener listener) {
searchListeners.add(listener);
panel.frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener(listener);
}
private void removeSearchListeners() {
for (SearchQueryHighlightListener listener : searchListeners) {
panel.frame().getGlobalSearchBar().getSearchQueryHighlightObservable().removeSearchListener(listener);
}
}
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.
// This is also important for the "select all"-action (else it will always select the first entry except it is already)
SwingUtilities.invokeLater(() -> {
panel.getMainTable().ensureVisible(entry);
});
} 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);
}
}
}
/**
* 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", storeFieldAction);
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", switchRightAction);
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", switchLeftAction);
inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.HELP), "help");
actionMap.put("help", helpAction);
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).focus();
} else if (activeTab instanceof FileAnnotationTab) {
((FileAnnotationTab) activeTab).requestFocus();
} else {
source.requestFocus();
}
}
/**
* 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);
}
/**
* 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();
}
storeFieldAction.actionPerformed(new ActionEvent(comp, 0, ""));
}
}
/**
* 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.focus();
}
}
}
private boolean storeSource() {
BibtexParser bibtexParser = new BibtexParser(Globals.prefs.getImportFormatPreferences());
try {
ParserResult parserResult = bibtexParser.parse(new StringReader(source.getText()));
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.getCiteKeyOptional().orElse(null);
boolean entryChanged = false;
boolean emptyWarning = (newKey == null) || newKey.isEmpty();
if (newKey != null) {
entry.setCiteKey(newKey);
} else {
entry.clearCiteKey();
}
// First, remove fields that the user has removed.
for (Entry<String, String> field : entry.getFieldMap().entrySet()) {
String fieldName = field.getKey();
String fieldValue = field.getValue();
if (InternalBibtexFields.isDisplayableField(fieldName) && !newEntry.hasField(fieldName)) {
compound.addEdit(
new UndoableFieldChange(entry, fieldName, fieldValue, null));
entry.clearField(fieldName);
entryChanged = true;
}
}
// Then set all fields that have been set by the user.
for (Entry<String, String> field : newEntry.getFieldMap().entrySet()) {
String fieldName = field.getKey();
String oldValue = entry.getField(fieldName).orElse(null);
String newValue = field.getValue();
if (!Objects.equals(oldValue, newValue)) {
// Test if the field is legally set.
new LatexFieldFormatter(Globals.prefs.getLatexFieldFormatterPreferences())
.format(newValue, fieldName);
compound.addEdit(new UndoableFieldChange(entry, fieldName, oldValue, newValue));
entry.setField(fieldName, 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 (panel.getDatabase().getDuplicationChecker().isDuplicateCiteKeyExisting(entry)) {
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();
panel.highlightEntry(entry);
return true;
} catch (InvalidFieldValueException | 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")};
if (!SwingUtilities.isEventDispatchThread()) {
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;
lastSourceAccepted = 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.
*/
private void updateAllFields() {
for (Object tab : tabs) {
if (tab instanceof EntryEditorTab) {
((EntryEditorTab) tab).setEntry(entry);
}
}
}
public void updateAllContentSelectors() {
if (!contentSelectors.isEmpty()) {
for (FieldContentSelector contentSelector : contentSelectors) {
contentSelector.rebuildComboBox();
}
}
}
/**
* Update the JTextArea when a field has changed.
*/
@Subscribe
@SuppressWarnings("unused")
public void listen(FieldChangedEvent fieldChangedEvent) {
String newValue = fieldChangedEvent.getNewValue() == null ? "" : fieldChangedEvent.getNewValue();
if (SwingUtilities.isEventDispatchThread()) {
setField(fieldChangedEvent.getFieldName(), newValue);
} else {
SwingUtilities.invokeLater(() -> setField(fieldChangedEvent.getFieldName(), newValue));
}
}
public void updateField(final Object sourceObject) {
storeFieldAction.actionPerformed(new ActionEvent(sourceObject, 0, ""));
}
public void setMovingToDifferentEntry() {
movingToDifferentEntry = true;
unregisterListeners();
}
private void unregisterListeners() {
entry.unregisterListener(this);
removeSearchListeners();
}
public GenerateKeyAction getGenerateKeyAction() {
return generateKeyAction;
}
public AbstractAction getPrevEntryAction() {
return prevEntryAction;
}
public AbstractAction getNextEntryAction() {
return nextEntryAction;
}
public StoreFieldAction getStoreFieldAction() {
return storeFieldAction;
}
public SwitchLeftAction getSwitchLeftAction() {
return switchLeftAction;
}
public SwitchRightAction getSwitchRightAction() {
return switchRightAction;
}
public HelpAction getHelpAction() {
return helpAction;
}
public Action getSaveDatabaseAction() {
return saveDatabaseAction;
}
private void showChangeEntryTypePopupMenu() {
JPopupMenu typeMenu = new ChangeEntryTypeMenu().getChangeentryTypePopupMenu(panel);
typeMenu.show(this, 0, 0);
}
public void close() {
if (tabbed.getSelectedComponent() == srcPanel) {
updateField(source);
if (lastSourceAccepted) {
panel.entryEditorClosing(EntryEditor.this);
} else {
panel.runCommand(Actions.SAVE);
lastSourceAccepted = true;
}
} else {
if (lastFieldAccepted) {
panel.entryEditorClosing(EntryEditor.this);
} else {
panel.runCommand(Actions.SAVE);
lastFieldAccepted = true;
}
}
}
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."));
}
private boolean updateTimeStampIsSet() {
return Globals.prefs.getBoolean(JabRefPreferences.USE_TIME_STAMP)
&& Globals.prefs.getBoolean(JabRefPreferences.UPDATE_TIMESTAMP);
}
/**
* Updates the timestamp of the given entry and returns the FieldChange
*/
private Optional<FieldChange> doUpdateTimeStamp() {
String timeStampField = Globals.prefs.get(JabRefPreferences.TIME_STAMP_FIELD);
String timeStampFormat = Globals.prefs.get(JabRefPreferences.TIME_STAMP_FORMAT);
String timestamp = DateTimeFormatter.ofPattern(timeStampFormat).format(LocalDateTime.now());
return UpdateField.updateField(entry, timeStampField, timestamp);
}
private class TypeButton extends JButton {
private TypeButton() {
super(IconTheme.JabRefIcon.EDIT.getIcon());
setToolTipText(Localization.lang("Change entry type"));
addActionListener(e -> showChangeEntryTypePopupMenu());
}
}
private class TypeLabel extends JLabel {
private 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);
}
}
/**
* Focus listener that fires the storeFieldAction when a TextArea loses focus.
*/
private class FieldListener extends FocusAdapter {
@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 FileAnnotationTab) && !((FileAnnotationTab) activeTab).isInitialized()) {
//Initialize by getting notes from cache if they are cached
FileAnnotationTab tab = (FileAnnotationTab) activeTab;
tab.initializeTab(tab);
}
if (activeTab instanceof EntryEditorTab) {
((EntryEditorTab) activeTab).updateAll();
activateVisible();
}
// When the tab "Related articles" gets selected, the request to get the recommendations is started.
if (activeTab == relatedArticlePanel) {
relatedArticlesTab.focus();
}
});
}
}
private class DeleteAction extends AbstractAction {
private 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"));
}
}
private class CloseAction extends AbstractAction {
private CloseAction() {
super(Localization.lang("Close window"), IconTheme.JabRefIcon.CLOSE.getSmallIcon());
putValue(Action.SHORT_DESCRIPTION, Localization.lang("Close window"));
}
@Override
public void actionPerformed(ActionEvent e) {
close();
}
}
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.getCiteKeyOptional().orElse(null);
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 = BibtexKeyPatternUtil.checkLegalKey(newValue,
Globals.prefs.getBoolean(JabRefPreferences.ENFORCE_LEGAL_BIBTEX_KEY));
if ((cleaned == null) || cleaned.equals(newValue)) {
textField.setValidBackgroundColor();
} else {
lastFieldAccepted = false;
textField.setInvalidBackgroundColor();
if (!SwingUtilities.isEventDispatchThread()) {
JOptionPane.showMessageDialog(frame, Localization.lang("Invalid BibTeX key"),
Localization.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
requestFocus();
}
return;
}
if (newValue == null) {
entry.clearCiteKey();
warnEmptyBibtexkey();
} else {
entry.setCiteKey(newValue);
boolean isDuplicate = panel.getDatabase().getDuplicationChecker().isDuplicateCiteKeyExisting(entry);
if (isDuplicate) {
warnDuplicateBibtexkey();
} else {
panel.output(Localization.lang("BibTeX key is unique."));
}
}
// Add an UndoableKeyChange to the baseframe's undoManager.
UndoableKeyChange undoableKeyChange = new UndoableKeyChange(entry, oldValue, newValue);
if (updateTimeStampIsSet()) {
NamedCompound ce = new NamedCompound(undoableKeyChange.getPresentationName());
ce.addEdit(undoableKeyChange);
doUpdateTimeStamp().ifPresent(fieldChange -> ce.addEdit(new UndoableFieldChange(fieldChange)));
ce.end();
panel.getUndoManager().addEdit(ce);
} else {
panel.getUndoManager().addEdit(undoableKeyChange);
}
textField.setValidBackgroundColor();
if (textField.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().trim();
if (!currentText.isEmpty()) {
toSet = currentText;
}
// 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()).orElse(null)));
}
if (!set) {
// We set the field and label color.
fieldEditor.setValidBackgroundColor();
} else {
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(Globals.prefs.getLatexFieldFormatterPreferences()).format(toSet,
fieldEditor.getFieldName());
}
String oldValue = entry.getField(fieldEditor.getFieldName()).orElse(null);
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 (updateTimeStampIsSet()) {
NamedCompound ce = new NamedCompound(undoableFieldChange.getPresentationName());
ce.addEdit(undoableFieldChange);
doUpdateTimeStamp()
.ifPresent(fieldChange -> ce.addEdit(new UndoableFieldChange(fieldChange)));
ce.end();
panel.getUndoManager().addEdit(ce);
} else {
panel.getUndoManager().addEdit(undoableFieldChange);
}
updateSource();
panel.markBaseChanged();
} catch (InvalidFieldValueException ex) {
lastFieldAccepted = false;
fieldEditor.setInvalidBackgroundColor();
if (!SwingUtilities.isEventDispatchThread()) {
JOptionPane.showMessageDialog(frame, Localization.lang("Error") + ": " + ex.getMessage(),
Localization.lang("Error setting field"), JOptionPane.ERROR_MESSAGE);
LOGGER.debug("Error setting field", ex);
requestFocus();
}
}
}
if (fieldEditor.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:
// don't select the current entry again (eg use BasePanel#highlightEntry} in case another entry was selected)
if (!movingAway && isShowing()) {
SwingUtilities.invokeLater(() -> {
panel.getMainTable().ensureVisible(entry);
});
}
}
}
private class SwitchLeftAction extends AbstractAction {
private 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();
}
}
private class SwitchRightAction extends AbstractAction {
private 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();
}
}
private class NextEntryAction extends AbstractAction {
private NextEntryAction() {
super(Localization.lang("Next entry"), IconTheme.JabRefIcon.DOWN.getIcon());
putValue(Action.SHORT_DESCRIPTION, Localization.lang("Next entry"));
}
@Override
public void actionPerformed(ActionEvent e) {
panel.selectNextEntry();
}
}
private class PrevEntryAction extends AbstractAction {
private PrevEntryAction() {
super(Localization.lang("Previous entry"), IconTheme.JabRefIcon.UP.getIcon());
putValue(Action.SHORT_DESCRIPTION, Localization.lang("Previous entry"));
}
@Override
public void actionPerformed(ActionEvent e) {
panel.selectPreviousEntry();
}
}
private class GenerateKeyAction extends AbstractAction {
private 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 org.jabref.gui.BasePanel.setupActions().new AbstractWorker() {...}.run()
// this updates the table automatically, on close, but not within the tab
Optional<String> oldValue = entry.getCiteKeyOptional();
if (oldValue.isPresent()) {
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;
}
}
}
BibtexKeyPatternUtil.makeAndSetLabel(panel.getBibDatabaseContext().getMetaData()
.getCiteKeyPattern(Globals.prefs.getBibtexKeyPatternPreferences().getKeyPattern()),
panel.getDatabase(), entry,
Globals.prefs.getBibtexKeyPatternPreferences());
// Store undo information:
panel.getUndoManager().addEdit(
new UndoableKeyChange(entry, oldValue.orElse(null),
entry.getCiteKeyOptional().get())); // Cite key always set here
// here we update the field
String bibtexKeyData = entry.getCiteKeyOptional().get();
setField(BibEntry.KEY_FIELD, bibtexKeyData);
updateSource();
panel.markBaseChanged();
}
}
private class UndoAction extends AbstractAction {
private UndoAction() {
super("Undo", IconTheme.JabRefIcon.UNDO.getIcon());
putValue(Action.SHORT_DESCRIPTION, "Undo");
}
@Override
public void actionPerformed(ActionEvent e) {
panel.runCommand(Actions.UNDO);
}
}
private class RedoAction extends AbstractAction {
private RedoAction() {
super("Redo", IconTheme.JabRefIcon.REDO.getIcon());
putValue(Action.SHORT_DESCRIPTION, "Redo");
}
@Override
public void actionPerformed(ActionEvent e) {
panel.runCommand(Actions.REDO);
}
}
private class SaveDatabaseAction extends AbstractAction {
private SaveDatabaseAction() {
super("Save library");
}
@Override
public void actionPerformed(ActionEvent e) {
Object activeTab = tabs.get(tabbed.getSelectedIndex());
if (activeTab instanceof EntryEditorTab) {
// Normal panel.
EntryEditorTab tab = (EntryEditorTab) activeTab;
// TODO: Reenable this
//FieldEditor fieldEditor = tab.getActive();
//fieldEditor.clearAutoCompleteSuggestion();
//updateField(fieldEditor);
} else {
// Source panel.
updateField(activeTab);
}
if (validEntry) {
panel.runCommand(Actions.SAVE);
}
}
}
private class AutoLinkAction extends AbstractAction {
private 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) {
// TODO: Reimplement this
//localFileListEditor.autoSetLinks();
}
}
}