diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 8cf926b0a4f..806020c9bfa 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -178,7 +178,8 @@ public class BasePanel extends JPanel implements ClipboardOwner { // To contain instantiated entry editors. This is to save time // As most enums, this must not be null private BasePanelMode mode = BasePanelMode.SHOWING_NOTHING; - private EntryEditor currentEditor; + private final EntryEditor entryEditor; + private final JFXPanel entryEditorContainer; private MainTableSelectionListener selectionListener; private JSplitPane splitPane; private boolean saving; @@ -245,11 +246,41 @@ public BasePanel(JabRefFrame frame, BibDatabaseContext bibDatabaseContext) { this.getDatabase().registerListener(new UpdateTimestampListener(Globals.prefs)); + entryEditor = new EntryEditor(this); + entryEditorContainer = setupEntryEditor(entryEditor); + this.preview = new PreviewPanel(this, getBibDatabaseContext()); DefaultTaskExecutor.runInJavaFXThread(() -> frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener(preview)); this.previewContainer = CustomJFXPanel.wrap(new Scene(preview)); } + private static JFXPanel setupEntryEditor(EntryEditor entryEditor) { + JFXPanel container = CustomJFXPanel.wrap(new Scene(entryEditor)); + container.addKeyListener(new KeyAdapter() { + + @Override + public void keyPressed(KeyEvent e) { + + //We need to consume this event here to prevent the propgation of keybinding events back to the JFrame + Optional keyBinding = Globals.getKeyPrefs().mapToKeyBinding(e); + if (keyBinding.isPresent()) { + switch (keyBinding.get()) { + case CUT: + case COPY: + case PASTE: + case DELETE_ENTRY: + case SELECT_ALL: + e.consume(); + break; + default: + //do nothing + } + } + } + }); + return container; + } + public static void runWorker(AbstractWorker worker) throws Exception { // This part uses Spin's features: Runnable wrk = worker.getWorker(); @@ -775,7 +806,16 @@ private void cut() { * "deleted". If true the action will be localized as "cut" */ private void delete(boolean cut) { - List entries = mainTable.getSelectedEntries(); + delete(cut, mainTable.getSelectedEntries()); + } + + /** + * Removes the selected entries from the database + * + * @param cut If false the user will get asked if he really wants to delete the entries, and it will be localized as + * "deleted". If true the action will be localized as "cut" + */ + private void delete(boolean cut, List entries) { if (entries.isEmpty()) { return; } @@ -814,6 +854,10 @@ private void delete(boolean cut) { mainTable.requestFocus(); } + public void delete(BibEntry entry) { + delete(false, Collections.singletonList(entry)); + } + private void paste() { Collection bes = new ClipBoardManager().extractBibEntriesFromClipboard(); @@ -1076,7 +1120,7 @@ private boolean saveDatabase(File file, boolean selectedOnly, Charset enc, if (ex.specificEntry()) { // Error occurred during processing of the entry. Highlight it: highlightEntry(ex.getEntry()); - showEntry(ex.getEntry()); + showAndEdit(ex.getEntry()); } else { LOGGER.warn("Could not save", ex); } @@ -1182,9 +1226,7 @@ public BibEntry newEntry(EntryType type) { // The database just changed. markBaseChanged(); - final EntryEditor entryEditor = getEntryEditor(be); - this.showEntryEditor(entryEditor); - entryEditor.requestFocus(); + this.showAndEdit(be); return be; } catch (KeyCollisionException ex) { @@ -1223,14 +1265,11 @@ public void insertEntry(final BibEntry bibEntry) { } public void editEntryByIdAndFocusField(final String entryId, final String fieldName) { - final Optional entry = bibDatabaseContext.getDatabase().getEntryById(entryId); - entry.ifPresent(e -> { - mainTable.setSelected(mainTable.findEntry(e)); + bibDatabaseContext.getDatabase().getEntryById(entryId).ifPresent(entry -> { + mainTable.setSelected(mainTable.findEntry(entry)); selectionListener.editSignalled(); - final EntryEditor editor = getEntryEditor(e); - editor.setFocusToField(fieldName); - this.showEntryEditor(editor); - editor.requestFocus(); + showAndEdit(entry); + entryEditor.setFocusToField(fieldName); }); } @@ -1273,7 +1312,7 @@ public void actionPerformed(ActionEvent e) { break; case SHOWING_EDITOR: case WILL_SHOW_EDITOR: - entryEditorClosing(getCurrentEditor()); + entryEditorClosing(getEntryEditor()); break; default: LOGGER.warn("unknown BasePanelMode: '" + mode + "', doing nothing"); @@ -1454,81 +1493,29 @@ public void adjustSplitter() { } } - private boolean isShowingEditor() { - return (splitPane.getBottomComponent() != null) && (splitPane.getBottomComponent() instanceof EntryEditor); - } - - public void showEntry(final BibEntry bibEntry) { - - if (getShowing() == bibEntry) { - if (splitPane.getBottomComponent() == null) { - // This is the special occasion when showing is set to an - // entry, but no entry editor is in fact shown. This happens - // after Preferences dialog is closed, and it means that we - // must make sure the same entry is shown again. We do this by - // setting showing to null, and recursively calling this method. - newEntryShowing(null); - showEntry(bibEntry); - } - return; - } - - String visName = null; - if ((getShowing() != null) && isShowingEditor()) { - visName = ((EntryEditor) splitPane.getBottomComponent()).getVisibleTabName(); - } - - // We must instantiate a new editor. - EntryEditor entryEditor = getEntryEditor(bibEntry); - if (visName != null) { - entryEditor.setVisibleTab(visName); - } - showEntryEditor(entryEditor); - - newEntryShowing(bibEntry); - } - - /** - * Get an entry editor ready to edit the given entry. - * - * @param entry The entry to be edited. - * @return A suitable entry editor. - */ - public EntryEditor getEntryEditor(BibEntry entry) { - if (currentEditor != null) { - currentEditor.setEntry(entry); - return currentEditor; - } else { - EntryEditor editor = new EntryEditor(this); - editor.setEntry(entry); - return editor; - } - } - - public EntryEditor getCurrentEditor() { - return currentEditor; + public EntryEditor getEntryEditor() { + return entryEditor; } /** - * Sets the given entry editor as the bottom component in the split pane. If an entry editor already was shown, + * Sets the entry editor as the bottom component in the split pane. If an entry editor already was shown, * makes sure that the divider doesn't move. Updates the mode to SHOWING_EDITOR. + * Then shows the given entry. * - * @param editor The entry editor to add. + * @param entry The entry to edit. */ - public void showEntryEditor(EntryEditor editor) { + public void showAndEdit(BibEntry entry) { if (mode == BasePanelMode.SHOWING_EDITOR) { - Globals.prefs.putInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT, - splitPane.getHeight() - splitPane.getDividerLocation()); + Globals.prefs.putInt(JabRefPreferences.ENTRY_EDITOR_HEIGHT, splitPane.getHeight() - splitPane.getDividerLocation()); } mode = BasePanelMode.SHOWING_EDITOR; - if (currentEditor != null) { - currentEditor.setMovingToDifferentEntry(); - } - currentEditor = editor; - splitPane.setBottomComponent(editor); - if (editor.getEntry() != getShowing()) { - newEntryShowing(editor.getEntry()); + splitPane.setBottomComponent(entryEditorContainer); + + if (entry != getShowing()) { + entryEditor.setEntry(entry); + newEntryShowing(entry); } + entryEditor.requestFocus(); adjustSplitter(); } @@ -1628,7 +1615,7 @@ public void entryEditorClosing(EntryEditor editor) { * Closes the entry editor or preview panel if it is showing the given entry. */ public void ensureNotShowingBottomPanel(BibEntry entry) { - if (((mode == BasePanelMode.SHOWING_EDITOR) && (currentEditor.getEntry() == entry)) + if (((mode == BasePanelMode.SHOWING_EDITOR) && (entryEditor.getEntry() == entry)) || ((mode == BasePanelMode.SHOWING_PREVIEW) && (selectionListener.getPreview().getEntry() == entry))) { hideBottomComponent(); } @@ -1636,12 +1623,9 @@ public void ensureNotShowingBottomPanel(BibEntry entry) { public void updateEntryEditorIfShowing() { if (mode == BasePanelMode.SHOWING_EDITOR) { - if (!currentEditor.getDisplayedBibEntryType().equals(currentEditor.getEntry().getType())) { - // The entry has changed type, so we must get a new editor. - newEntryShowing(null); - final EntryEditor newEditor = getEntryEditor(currentEditor.getEntry()); - showEntryEditor(newEditor); - } + BibEntry currentEntry = entryEditor.getEntry(); + showAndEdit(null); + showAndEdit(currentEntry); } } @@ -1813,15 +1797,6 @@ public void lostOwnership(Clipboard clipboard, Transferable contents) { // Nothing } - private void setEntryEditorEnabled(boolean enabled) { - if ((getShowing() != null) && (splitPane.getBottomComponent() instanceof EntryEditor)) { - EntryEditor ed = (EntryEditor) splitPane.getBottomComponent(); - if (ed.isEnabled() != enabled) { - ed.setEnabled(enabled); - } - } - } - /** * Perform necessary cleanup when this BasePanel is closed. */ @@ -1890,7 +1865,7 @@ private BibEntry getShowing() { * * @param entry The entry that is now to be shown. */ - public void newEntryShowing(BibEntry entry) { + private void newEntryShowing(BibEntry entry) { // If this call is the result of a Back or Forward operation, we must take // care not to make any history changes, since the necessary changes will @@ -2085,8 +2060,8 @@ private class EntryRemovedListener { @Subscribe public void listen(EntryRemovedEvent entryRemovedEvent) { // if the entry that is displayed in the current entry editor is removed, close the entry editor - if (isShowingEditor() && currentEditor.getEntry().equals(entryRemovedEvent.getBibEntry())) { - currentEditor.close(); + if (mode == BasePanelMode.SHOWING_EDITOR && entryEditor.getEntry().equals(entryRemovedEvent.getBibEntry())) { + entryEditor.close(); } BibEntry previewEntry = selectionListener.getPreview().getEntry(); diff --git a/src/main/java/org/jabref/gui/Main.css b/src/main/java/org/jabref/gui/Main.css index e0c053b230a..5f01e7afd41 100644 --- a/src/main/java/org/jabref/gui/Main.css +++ b/src/main/java/org/jabref/gui/Main.css @@ -33,6 +33,11 @@ */ -fx-dark-background: #757575; + /* + * A strong white as background + */ + -fx-light-background: #ffffff; + /* * A strong blue for backgrounds of active items (toggle button, selected list item) */ @@ -85,6 +90,8 @@ -fx-font-family: monospace; } +.flatButtonNoSpaceBottom, +.flatButtonNoSpaceTop, .flatButton { -fx-shadow-highlight-color: transparent; -fx-outer-border: transparent; @@ -101,3 +108,11 @@ -fx-text-fill: white; -fx-fill: white; } + +.flatButtonNoSpaceBottom { + -fx-padding: 0.5em 0.5em -0.1em 0.5em; +} + +.flatButtonNoSpaceTop { + -fx-padding: -0.1em 0.5em 0.5em 0.5em; +} diff --git a/src/main/java/org/jabref/gui/actions/ChangeTypeAction.java b/src/main/java/org/jabref/gui/actions/ChangeTypeAction.java index c2a19f8d26d..b7b907a662c 100644 --- a/src/main/java/org/jabref/gui/actions/ChangeTypeAction.java +++ b/src/main/java/org/jabref/gui/actions/ChangeTypeAction.java @@ -1,10 +1,19 @@ package org.jabref.gui.actions; import java.awt.event.ActionEvent; +import java.util.Collections; +import java.util.List; import javax.swing.AbstractAction; +import javax.swing.undo.UndoManager; + +import javafx.scene.control.MenuItem; import org.jabref.gui.BasePanel; +import org.jabref.gui.undo.NamedCompound; +import org.jabref.gui.undo.UndoableChangeType; +import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.EntryType; public class ChangeTypeAction extends AbstractAction { @@ -22,4 +31,22 @@ public ChangeTypeAction(EntryType type, BasePanel bp) { public void actionPerformed(ActionEvent evt) { panel.changeTypeOfSelectedEntries(type); } + + public static MenuItem as(EntryType type, BibEntry entry, UndoManager undoManager) { + return as(type, Collections.singletonList(entry), undoManager); + } + + public static MenuItem as(EntryType type, List entries, UndoManager undoManager) { + MenuItem menuItem = new MenuItem(type.getName()); + menuItem.setOnAction(event -> { + NamedCompound compound = new NamedCompound(Localization.lang("Change entry type")); + for (BibEntry entry : entries) { + entry.setType(type) + .ifPresent(change -> compound.addEdit(new UndoableChangeType(change))); + } + + undoManager.addEdit(compound); + }); + return menuItem; + } } diff --git a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java index be865ab02a7..590c2795028 100644 --- a/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java +++ b/src/main/java/org/jabref/gui/actions/IntegrityCheckAction.java @@ -29,6 +29,7 @@ import org.jabref.logic.integrity.IntegrityCheck; import org.jabref.logic.integrity.IntegrityMessage; import org.jabref.logic.l10n.Localization; +import org.jabref.preferences.JabRefPreferences; import com.jgoodies.forms.builder.FormBuilder; import com.jgoodies.forms.layout.FormLayout; @@ -53,8 +54,8 @@ public void actionPerformed(ActionEvent e) { IntegrityCheck check = new IntegrityCheck(frame.getCurrentBasePanel().getBibDatabaseContext(), Globals.prefs.getFileDirectoryPreferences(), Globals.prefs.getBibtexKeyPatternPreferences(), - Globals.journalAbbreviationLoader - .getRepository(Globals.prefs.getJournalAbbreviationPreferences())); + Globals.journalAbbreviationLoader.getRepository(Globals.prefs.getJournalAbbreviationPreferences()), + Globals.prefs.getBoolean(JabRefPreferences.ENFORCE_LEGAL_BIBTEX_KEY)); final JDialog integrityDialog = new JDialog(frame, true); integrityDialog.setUndecorated(true); diff --git a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java index 87279266213..d56cec1b4d9 100644 --- a/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/DeprecatedFieldsTab.java @@ -2,6 +2,8 @@ import java.util.Collection; +import javax.swing.undo.UndoManager; + import javafx.scene.control.Tooltip; import org.jabref.gui.IconTheme; @@ -12,8 +14,8 @@ import org.jabref.model.entry.EntryType; public class DeprecatedFieldsTab extends FieldsEditorTab { - public DeprecatedFieldsTab(BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders) { - super(false, databaseContext, suggestionProviders); + public DeprecatedFieldsTab(BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager) { + super(false, databaseContext, suggestionProviders, undoManager); setText(Localization.lang("Deprecated fields")); setTooltip(new Tooltip(Localization.lang("Show deprecated BibTeX fields"))); diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css index 7ff9150d3e3..0e66bb6c607 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css @@ -10,8 +10,8 @@ .editorPane { -fx-hgap: 10; -fx-vgap: 6; - -fx-background-color: text-area-background; - -fx-padding: 5 0 0 5; + -fx-background-color: -fx-light-background; + -fx-padding: 5 5 0 15; } .date-picker > .date-picker-display-node { @@ -35,3 +35,55 @@ .list-cell:odd { -fx-background-color: text-area-background; } + +#typeLabel { + -fx-padding: 0.1em; + -fx-font-size: 1.166667em; + -fx-font-weight: bold; +} + +.flatButton.narrow { + -fx-padding: 0.1em; +} + +.flatButtonNoSpaceBottom.narrow { + -fx-padding: 0.1em 0.1em -0.2em 0.1em; +} + +.flatButtonNoSpaceTop.narrow { + -fx-padding: -0.2em 0.1em 0.1em 0.1em; +} + +#tabbed { + -fx-open-tab-animation: NONE; + -fx-close-tab-animation: NONE; +} + +#tabbed > .tab-header-area > .tab-header-background { + -fx-background-color: #dadad8; +} + +#tabbed > .tab-header-area > .headers-region > .tab:selected { + -fx-background-color: -fx-outer-border, -fx-inner-border, -fx-light-background; +} + +#tabbed > .tab-header-area .glyph-icon { + -glyph-size: 13px; +} + +#tabbed > .tab-header-area { + -fx-padding: 0.416667em 5 0.0em 0em; +} + +.editorPane .glyph-icon { + -glyph-size: 16px; +} + +.tool-bar { + -fx-background-color: #dadad8; + -fx-background-insets: 0; +} + +.tool-bar > .container > .separator { + -fx-background-color: #5e5f5e; +} diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.fxml b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.fxml new file mode 100644 index 00000000000..fbcf17e8a92 --- /dev/null +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.fxml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+
diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java index 140abc77233..93ae312d24e 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.java @@ -1,93 +1,42 @@ package org.jabref.gui.entryeditor; -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.RenderingHints; -import java.awt.event.ActionEvent; -import java.awt.event.KeyAdapter; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; 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.JToolBar; -import javax.swing.SwingConstants; -import javax.swing.SwingUtilities; -import javax.swing.undo.UndoableEdit; - -import javafx.beans.property.BooleanProperty; -import javafx.beans.property.SimpleBooleanProperty; -import javafx.embed.swing.JFXPanel; -import javafx.scene.Scene; +import javafx.fxml.FXML; +import javafx.geometry.Side; +import javafx.scene.control.Button; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; import javafx.scene.control.Tab; import javafx.scene.control.TabPane; import javafx.scene.input.KeyEvent; +import javafx.scene.layout.BorderPane; 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.customjfx.CustomJFXPanel; import org.jabref.gui.entryeditor.fileannotationtab.FileAnnotationTab; -import org.jabref.gui.externalfiles.WriteXMPEntryEditorAction; -import org.jabref.gui.fieldeditors.FieldEditor; -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.undo.NamedCompound; -import org.jabref.gui.undo.UndoableFieldChange; -import org.jabref.gui.undo.UndoableKeyChange; -import org.jabref.gui.undo.UndoableRemoveEntry; +import org.jabref.gui.undo.CountingUndoManager; +import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.DefaultTaskExecutor; -import org.jabref.gui.util.component.CheckBoxMessage; -import org.jabref.gui.util.component.VerticalLabelUI; import org.jabref.logic.TypedBibEntry; -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.WebFetchers; -import org.jabref.logic.integrity.BracesCorrector; -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.database.BibDatabase; +import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.EntryType; -import org.jabref.model.entry.event.FieldAddedOrRemovedEvent; import org.jabref.preferences.JabRefPreferences; -import com.google.common.eventbus.Subscribe; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.fxmisc.easybind.EasyBind; /** @@ -100,90 +49,39 @@ * 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 { +public class EntryEditor extends BorderPane { - private static final Log LOGGER = LogFactory.getLog(EntryEditor.class); + private final BibDatabaseContext bibDatabaseContext; + private final CountingUndoManager undoManager; /** - * A reference to the entry this object works on. + * A reference to the entry this editor works on. */ private BibEntry entry; - /** - * The currently displayed type - */ - private 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 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 TabPane tabbed = new TabPane(); - private final JabRefFrame frame; + @FXML private TabPane tabbed; + @FXML private Button typeChangeButton; + @FXML private Button fetcherButton; private final BasePanel panel; - 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 List searchListeners = new ArrayList<>(); - private final JFXPanel container; private final List tabs; - - /** - * Indicates that we are about to go to the next or previous entry - */ - private final BooleanProperty movingToDifferentEntry = new SimpleBooleanProperty(); - private EntryType entryType; private SourceTab sourceTab; - private final TypeLabel typeLabel; + @FXML private Label typeLabel; public EntryEditor(BasePanel panel) { - this.frame = panel.frame(); this.panel = panel; + this.bibDatabaseContext = panel.getBibDatabaseContext(); + this.undoManager = panel.getUndoManager(); - writeXmp = new WriteXMPEntryEditorAction(panel, this); + ControlHelper.loadFXMLForControl(this); - BorderLayout layout = new BorderLayout(); - setLayout(layout); + getStylesheets().add(EntryEditor.class.getResource("EntryEditor.css").toExternalForm()); + setStyle("-fx-font-size: " + Globals.prefs.getFontSizeFX() + "pt;"); - container = CustomJFXPanel.create(); - // Create type-label - typeLabel = new TypeLabel(""); - setupToolBar(); - DefaultTaskExecutor.runInJavaFXThread(() -> { - tabbed.setTabClosingPolicy(TabPane.TabClosingPolicy.UNAVAILABLE); - tabbed.setStyle( - "-fx-font-size: " + Globals.prefs.getFontSizeFX() + "pt;" + - "-fx-open-tab-animation: NONE; -fx-close-tab-animation: NONE;"); - container.setScene(new Scene(tabbed)); - }); - add(container, BorderLayout.CENTER); - - DefaultTaskExecutor.runInJavaFXThread(() -> { - EasyBind.subscribe(tabbed.getSelectionModel().selectedItemProperty(), tab -> { - EntryEditorTab activeTab = (EntryEditorTab) tab; - if (activeTab != null) { - activeTab.notifyAboutFocus(entry); - } - }); + EasyBind.subscribe(tabbed.getSelectionModel().selectedItemProperty(), tab -> { + EntryEditorTab activeTab = (EntryEditorTab) tab; + if (activeTab != null) { + activeTab.notifyAboutFocus(entry); + } }); setupKeyBindings(); @@ -191,66 +89,10 @@ public EntryEditor(BasePanel panel) { tabs = createTabs(); } - public void setEntry(BibEntry entry) { - this.entry = Objects.requireNonNull(entry); - entryType = EntryTypes.getTypeOrDefault(entry.getType(), - this.frame.getCurrentBasePanel().getBibDatabaseContext().getMode()); - - displayedBibEntryType = entry.getType(); - - DefaultTaskExecutor.runInJavaFXThread(() -> { - recalculateVisibleTabs(); - if (Globals.prefs.getBoolean(JabRefPreferences.DEFAULT_SHOW_SOURCE)) { - tabbed.getSelectionModel().select(sourceTab); - } - - // Notify current tab about new entry - EntryEditorTab selectedTab = (EntryEditorTab) tabbed.getSelectionModel().getSelectedItem(); - selectedTab.notifyAboutFocus(entry); - }); - - TypedBibEntry typedEntry = new TypedBibEntry(entry, panel.getBibDatabaseContext().getMode()); - typeLabel.setText(typedEntry.getTypeForDisplay()); - } - - @Subscribe - public synchronized void listen(FieldAddedOrRemovedEvent event) { - // Rebuild entry editor based on new information (e.g. hide/add tabs) - recalculateVisibleTabs(); - } - /** * Set-up key bindings specific for the entry editor. */ private void setupKeyBindings() { - container.addKeyListener(new KeyAdapter() { - - @Override - public void keyPressed(java.awt.event.KeyEvent e) { - - //We need to consume this event here to prevent the propgation of keybinding events back to the JFrame - Optional keyBinding = Globals.getKeyPrefs().mapToKeyBinding(e); - if (keyBinding.isPresent()) { - switch (keyBinding.get()) { - case CUT: - case COPY: - case PASTE: - case CLOSE_ENTRY_EDITOR: - case DELETE_ENTRY: - case SELECT_ALL: - case ENTRY_EDITOR_NEXT_PANEL: - case ENTRY_EDITOR_NEXT_PANEL_2: - case ENTRY_EDITOR_PREVIOUS_PANEL: - case ENTRY_EDITOR_PREVIOUS_PANEL_2: - e.consume(); - break; - default: - //do nothing - } - } - } - }); - tabbed.addEventFilter(KeyEvent.KEY_PRESSED, event -> { Optional keyBinding = Globals.getKeyPrefs().mapToKeyBinding(event); if (keyBinding.isPresent()) { @@ -266,11 +108,11 @@ public void keyPressed(java.awt.event.KeyEvent e) { event.consume(); break; case HELP: - helpAction.actionPerformed(null); + HelpAction.openHelpPage(HelpFile.ENTRY_EDITOR); event.consume(); break; case CLOSE_ENTRY_EDITOR: - closeAction.actionPerformed(null); + close(); event.consume(); break; default: @@ -280,55 +122,44 @@ public void keyPressed(java.awt.event.KeyEvent e) { }); } + @FXML public void close() { - closeAction.actionPerformed(null); + panel.entryEditorClosing(EntryEditor.this); } - private void recalculateVisibleTabs() { - List visibleTabs = tabs.stream().filter(tab -> tab.shouldShow(entry)).collect(Collectors.toList()); - - // Start of ugly hack: - // We need to find out, which tabs will be shown and which not and remove and re-add the appropriate tabs - // to the editor. We don't want to simply remove all and re-add the complete list of visible tabs, because - // the tabs give an ugly animation the looks like all tabs are shifting in from the right. - // This hack is required since tabbed.getTabs().setAll(visibleTabs) changes the order of the tabs in the editor - - // First, remove tabs that we do not want to show - List toBeRemoved = tabs.stream().filter(tab -> !tab.shouldShow(entry)).collect(Collectors.toList()); - tabbed.getTabs().removeAll(toBeRemoved); + @FXML + private void deleteEntry() { + panel.delete(entry); + } - // Next add all the visible tabs (if not already present) at the right position - for (int i = 0; i < visibleTabs.size(); i++) { - Tab toBeAdded = visibleTabs.get(i); - Tab shown = null; - if (i < tabbed.getTabs().size()) { - shown = tabbed.getTabs().get(i); - } + @FXML + private void navigateToPreviousEntry() { + panel.selectPreviousEntry(); + } - if (!toBeAdded.equals(shown)) { - tabbed.getTabs().add(i, toBeAdded); - } - } + @FXML + private void navigateToNextEntry() { + panel.selectNextEntry(); } private List createTabs() { List tabs = new LinkedList<>(); // Required fields - tabs.add(new RequiredFieldsTab(panel.getDatabaseContext(), panel.getSuggestionProviders())); + tabs.add(new RequiredFieldsTab(panel.getDatabaseContext(), panel.getSuggestionProviders(), undoManager)); // Optional fields - tabs.add(new OptionalFieldsTab(panel.getDatabaseContext(), panel.getSuggestionProviders())); - tabs.add(new OptionalFields2Tab(panel.getDatabaseContext(), panel.getSuggestionProviders())); - tabs.add(new DeprecatedFieldsTab(panel.getDatabaseContext(), panel.getSuggestionProviders())); + tabs.add(new OptionalFieldsTab(panel.getDatabaseContext(), panel.getSuggestionProviders(), undoManager)); + tabs.add(new OptionalFields2Tab(panel.getDatabaseContext(), panel.getSuggestionProviders(), undoManager)); + tabs.add(new DeprecatedFieldsTab(panel.getDatabaseContext(), panel.getSuggestionProviders(), undoManager)); // Other fields - tabs.add(new OtherFieldsTab(panel.getDatabaseContext(), panel.getSuggestionProviders())); + tabs.add(new OtherFieldsTab(panel.getDatabaseContext(), panel.getSuggestionProviders(), undoManager)); // General fields from preferences EntryEditorTabList tabList = Globals.prefs.getEntryEditorTabList(); for (int i = 0; i < tabList.getTabCount(); i++) { - tabs.add(new UserDefinedFieldsTab(tabList.getTabName(i), tabList.getTabFields(i), panel.getDatabaseContext(), panel.getSuggestionProviders())); + tabs.add(new UserDefinedFieldsTab(tabList.getTabName(i), tabList.getTabFields(i), panel.getDatabaseContext(), panel.getSuggestionProviders(), undoManager)); } // Special tabs @@ -337,118 +168,84 @@ private List createTabs() { tabs.add(new RelatedArticlesTab(Globals.prefs)); // Source tab - sourceTab = new SourceTab(panel.getBibDatabaseContext(), panel.getUndoManager(), Globals.prefs.getLatexFieldFormatterPreferences()); + sourceTab = new SourceTab(bibDatabaseContext, undoManager, Globals.prefs.getLatexFieldFormatterPreferences()); tabs.add(sourceTab); return tabs; } - public String getDisplayedBibEntryType() { - return displayedBibEntryType; + private void recalculateVisibleTabs() { + List visibleTabs = tabs.stream().filter(tab -> tab.shouldShow(entry)).collect(Collectors.toList()); + + // Start of ugly hack: + // We need to find out, which tabs will be shown and which not and remove and re-add the appropriate tabs + // to the editor. We don't want to simply remove all and re-add the complete list of visible tabs, because + // the tabs give an ugly animation the looks like all tabs are shifting in from the right. + // This hack is required since tabbed.getTabs().setAll(visibleTabs) changes the order of the tabs in the editor + + // First, remove tabs that we do not want to show + List toBeRemoved = tabs.stream().filter(tab -> !tab.shouldShow(entry)).collect(Collectors.toList()); + tabbed.getTabs().removeAll(toBeRemoved); + + // Next add all the visible tabs (if not already present) at the right position + for (int i = 0; i < visibleTabs.size(); i++) { + Tab toBeAdded = visibleTabs.get(i); + Tab shown = null; + if (i < tabbed.getTabs().size()) { + shown = tabbed.getTabs().get(i); + } + + if (!toBeAdded.equals(shown)) { + tabbed.getTabs().add(i, toBeAdded); + } + } } /** - * @return reference to the currently edited entry + * @return 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.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); + /** + * Sets the entry to edit. + */ + public void setEntry(BibEntry entry) { + this.entry = Objects.requireNonNull(entry); - // 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); + DefaultTaskExecutor.runInJavaFXThread(() -> { + recalculateVisibleTabs(); + if (Globals.prefs.getBoolean(JabRefPreferences.DEFAULT_SHOW_SOURCE)) { + tabbed.getSelectionModel().select(sourceTab); + } - leftPan.add(typeLabel, BorderLayout.CENTER); - TypeButton typeButton = new TypeButton(); + // Notify current tab about new entry + EntryEditorTab selectedTab = (EntryEditorTab) tabbed.getSelectionModel().getSelectedItem(); + selectedTab.notifyAboutFocus(entry); - toolBar.add(typeButton); - toolBar.add(generateKeyAction); - toolBar.add(autoLinkAction); + setupToolBar(); + }); + } - toolBar.add(writeXmp); + private void setupToolBar() { + // Update type label + TypedBibEntry typedEntry = new TypedBibEntry(entry, bibDatabaseContext.getMode()); + typeLabel.setText(typedEntry.getTypeForDisplay()); - JPopupMenu fetcherPopup = new JPopupMenu(); + // Add type change menu + ContextMenu typeMenu = new ChangeEntryTypeMenu().getChangeEntryTypePopupMenu(entry, bibDatabaseContext, undoManager); + typeLabel.setOnMouseClicked(event -> typeMenu.show(typeLabel, Side.RIGHT, 0, 0)); + typeChangeButton.setOnMouseClicked(event -> typeMenu.show(typeChangeButton, Side.RIGHT, 0, 0)); + // Add menu for fetching bibliographic information + ContextMenu fetcherMenu = new ContextMenu(); 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(); - } - })); + MenuItem fetcherMenuItem = new MenuItem(fetcher.getName()); + fetcherMenuItem.setOnAction(event -> new EntryFetchAndMergeWorker(panel, getEntry(), fetcher).execute()); + fetcherMenu.getItems().add(fetcherMenuItem); } - 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); + fetcherButton.setOnMouseClicked(event -> fetcherMenu.show(fetcherButton, Side.RIGHT, 0, 0)); } void addSearchListener(SearchQueryHighlightListener listener) { @@ -457,48 +254,10 @@ void addSearchListener(SearchQueryHighlightListener listener) { panel.frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener(listener); } - private void removeSearchListeners() { - for (SearchQueryHighlightListener listener : searchListeners) { - panel.frame().getGlobalSearchBar().getSearchQueryHighlightObservable().removeSearchListener(listener); - } - } - - @Override - public void requestFocus() { - container.requestFocus(); - } - - /** - * Reports the enabled status of the editor, as set by setEnabled() - */ - @Override - public boolean isEnabled() { - return true; - } - - /** - * Returns the name of the currently selected tab. - */ - public String getVisibleTabName() { - Tab selectedTab = tabbed.getSelectionModel().getSelectedItem(); - if (selectedTab != null) { - return selectedTab.getText(); - } - return ""; - } - - public void setVisibleTab(String name) { - for (Tab tab : tabbed.getTabs()) { - if (tab.getText().equals(name)) { - tabbed.getSelectionModel().select(tab); - } - } - } - public void setFocusToField(String fieldName) { DefaultTaskExecutor.runInJavaFXThread(() -> { for (Tab tab : tabbed.getTabs()) { - if ((tab instanceof FieldsEditorTab) && ((FieldsEditorTab) tab).determineFieldsToShow(entry, entryType).contains(fieldName)) { + if ((tab instanceof FieldsEditorTab) && ((FieldsEditorTab) tab).getShownFields().contains(fieldName)) { FieldsEditorTab fieldsEditorTab = (FieldsEditorTab) tab; tabbed.getSelectionModel().select(tab); fieldsEditorTab.requestFocus(fieldName); @@ -506,409 +265,4 @@ public void setFocusToField(String fieldName) { } }); } - - public void setMovingToDifferentEntry() { - movingToDifferentEntry.set(true); - unregisterListeners(); - } - - private void unregisterListeners() { - removeSearchListeners(); - - } - - private void showChangeEntryTypePopupMenu() { - JPopupMenu typeMenu = new ChangeEntryTypeMenu().getChangeentryTypePopupMenu(panel); - typeMenu.show(this, 0, 0); - } - - 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 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); - } - } - - 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) { - Map cleanedEntries = entry - .getFieldMap() - .entrySet() - .stream() - .collect(Collectors.toMap(Map.Entry::getKey, f -> BracesCorrector.apply(f.getValue()))); - if (!cleanedEntries.equals(entry.getFieldMap())) { - frame.output(Localization.lang("Added missing braces.")); - } - entry.setField(cleanedEntries); - 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.get(); - movingToDifferentEntry.set(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 { - 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); - updateTimestamp(undoableKeyChange); - - textField.setValidBackgroundColor(); - - if (textField.hasFocus()) { - textField.setActiveBackgroundColor(); - } - - 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(); - - //TODO: See if we need to update an AutoCompleter instance: - /* - AutoCompleter aComp = panel.getSuggestionProviders().get(fieldEditor.getName()); - if (aComp != null) { - aComp.addBibtexEntry(entry); - } - */ - - // Add an UndoableFieldChange to the baseframe's undoManager. - UndoableFieldChange undoableFieldChange = new UndoableFieldChange(entry, - fieldEditor.getFieldName(), oldValue, toSet); - updateTimestamp(undoableFieldChange); - - panel.markBaseChanged(); - } catch (InvalidFieldValueException ex) { - 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); - } - } - - // 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 void updateTimestamp(UndoableEdit undoableEdit) { - if (Globals.prefs.getTimestampPreferences().includeTimestamps()) { - NamedCompound compound = new NamedCompound(undoableEdit.getPresentationName()); - compound.addEdit(undoableEdit); - UpdateField.updateField(entry, Globals.prefs.getTimestampPreferences().getTimestampField(), Globals.prefs.getTimestampPreferences().now()).ifPresent(fieldChange -> compound.addEdit(new UndoableFieldChange(fieldChange))); - compound.end(); - panel.getUndoManager().addEdit(compound); - } else { - panel.getUndoManager().addEdit(undoableEdit); - } - } - } - - 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 - - // 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 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()); - - if (entry.hasCiteKey()) { - // 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(); - entry.setField(BibEntry.KEY_FIELD, bibtexKeyData); - 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) { - DefaultTaskExecutor.runInJavaFXThread(() -> 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 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") + - Globals.getKeyPrefs().get(KeyBinding.AUTOMATICALLY_LINK_FILES).map(b -> " (" + b + ")").orElse("")); - } - - @Override - public void actionPerformed(ActionEvent event) { - // TODO: Reimplement this - //localFileListEditor.autoSetLinks(); - } - } } diff --git a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java index e9f89a48cb0..b1ac6b38071 100644 --- a/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java +++ b/src/main/java/org/jabref/gui/entryeditor/FieldsEditorTab.java @@ -8,6 +8,8 @@ import java.util.Set; import java.util.stream.Stream; +import javax.swing.undo.UndoManager; + import javafx.geometry.VPos; import javafx.scene.Node; import javafx.scene.Parent; @@ -46,11 +48,14 @@ abstract class FieldsEditorTab extends EntryEditorTab { private FieldEditorFX activeField; private final BibDatabaseContext databaseContext; + private UndoManager undoManager; + private Collection fields; - public FieldsEditorTab(boolean compressed, BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders) { + public FieldsEditorTab(boolean compressed, BibDatabaseContext databaseContext, SuggestionProviders suggestionProviders, UndoManager undoManager) { this.isCompressed = compressed; this.databaseContext = databaseContext; this.suggestionProviders = suggestionProviders; + this.undoManager = undoManager; } private static void addColumn(GridPane gridPane, int columnIndex, List