diff --git a/src/main/java/org/jabref/gui/BasePanel.java b/src/main/java/org/jabref/gui/BasePanel.java index 306939d3ace..9bcbc8193b2 100644 --- a/src/main/java/org/jabref/gui/BasePanel.java +++ b/src/main/java/org/jabref/gui/BasePanel.java @@ -1,8 +1,5 @@ package org.jabref.gui; -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.ClipboardOwner; -import java.awt.datatransfer.Transferable; import java.io.File; import java.io.IOException; import java.io.StringReader; @@ -134,7 +131,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class BasePanel extends StackPane implements ClipboardOwner { +public class BasePanel extends StackPane { + private static final Logger LOGGER = LoggerFactory.getLogger(BasePanel.class); private final BibDatabaseContext bibDatabaseContext; @@ -227,8 +225,10 @@ public BasePanel(JabRefFrame frame, BasePanelPreferences preferences, BibDatabas this.entryEditor = new EntryEditor(this, preferences.getEntryEditorPreferences(), Globals.getFileUpdateMonitor(), dialogService); - this.preview = new PreviewPanel(this, getBibDatabaseContext(), preferences.getKeyBindings(), preferences.getPreviewPreferences(), dialogService); + this.preview = new PreviewPanel(this, getBibDatabaseContext(), preferences.getKeyBindings(), preferences.getPreviewPreferences(), dialogService, externalFileTypes); frame().getGlobalSearchBar().getSearchQueryHighlightObservable().addSearchListener(preview); + + } @Subscribe @@ -680,15 +680,16 @@ public void runCommand(final Actions command) { */ private boolean saveDatabase(File file, boolean selectedOnly, Charset encoding, SavePreferences.DatabaseSaveType saveType) - throws SaveException { + throws SaveException { SaveSession session; final String SAVE_DATABASE = Localization.lang("Save library"); try { SavePreferences prefs = Globals.prefs.loadForSaveFromPreferences() - .withEncoding(encoding) - .withSaveType(saveType); + .withEncoding(encoding) + .withSaveType(saveType); + BibtexDatabaseWriter databaseWriter = new BibtexDatabaseWriter<>( - FileSaveSession::new); + FileSaveSession::new); if (selectedOnly) { session = databaseWriter.savePartOfDatabase(bibDatabaseContext, mainTable.getSelectedEntries(), prefs); } else { @@ -700,7 +701,7 @@ private boolean saveDatabase(File file, boolean selectedOnly, Charset encoding, // FIXME: not sure if this is really thrown anywhere catch (UnsupportedCharsetException ex) { frame.getDialogService().showErrorDialogAndWait(Localization.lang("Save library"), Localization.lang("Could not save file.") - + Localization.lang("Character encoding '%0' is not supported.", encoding.displayName())); + + Localization.lang("Character encoding '%0' is not supported.", encoding.displayName())); throw new SaveException("rt"); } catch (SaveException ex) { if (ex.specificEntry()) { @@ -1145,7 +1146,7 @@ public void entryEditorClosing(EntryEditor editor) { */ public void ensureNotShowingBottomPanel(BibEntry entry) { if (((mode == BasePanelMode.SHOWING_EDITOR) && (entryEditor.getEntry() == entry)) - || ((mode == BasePanelMode.SHOWING_PREVIEW) && (preview.getEntry() == entry))) { + || ((mode == BasePanelMode.SHOWING_PREVIEW) && (preview.getEntry() == entry))) { closeBottomPane(); } } @@ -1312,11 +1313,6 @@ private void saveDividerLocation(Number position) { } } - // Method pertaining to the ClipboardOwner interface. - @Override - public void lostOwnership(Clipboard clipboard, Transferable contents) { - // Nothing - } /** * Perform necessary cleanup when this BasePanel is closed. @@ -1500,7 +1496,7 @@ public void listen(EntryAddedEvent addedEntryEvent) { if (Globals.prefs.getBoolean(JabRefPreferences.AUTO_ASSIGN_GROUP)) { final List entries = Collections.singletonList(addedEntryEvent.getBibEntry()); Globals.stateManager.getSelectedGroup(bibDatabaseContext).forEach( - selectedGroup -> selectedGroup.addEntriesToGroup(entries)); + selectedGroup -> selectedGroup.addEntriesToGroup(entries)); } } } @@ -1600,10 +1596,10 @@ public void action() { List files = bes.get(0).getFiles(); Optional linkedFile = files.stream() - .filter(file -> (FieldName.URL.equalsIgnoreCase(file.getFileType()) - || FieldName.PS.equalsIgnoreCase(file.getFileType()) - || FieldName.PDF.equalsIgnoreCase(file.getFileType()))) - .findFirst(); + .filter(file -> (FieldName.URL.equalsIgnoreCase(file.getFileType()) + || FieldName.PS.equalsIgnoreCase(file.getFileType()) + || FieldName.PDF.equalsIgnoreCase(file.getFileType()))) + .findFirst(); if (linkedFile.isPresent()) { diff --git a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java index bee99cbd86a..8776ecaca3f 100644 --- a/src/main/java/org/jabref/gui/DragAndDropDataFormats.java +++ b/src/main/java/org/jabref/gui/DragAndDropDataFormats.java @@ -1,7 +1,11 @@ package org.jabref.gui; +import java.util.List; + import javafx.scene.input.DataFormat; +import org.jabref.model.entry.BibEntry; + /** * Contains all the different {@link DataFormat}s that may occur in JabRef. */ @@ -10,4 +14,6 @@ public class DragAndDropDataFormats { public static final DataFormat GROUP = new DataFormat("dnd/org.jabref.model.groups.GroupTreeNode"); public static final DataFormat LINKED_FILE = new DataFormat("dnd/org.jabref.model.entry.LinkedFile"); public static final DataFormat ENTRIES = new DataFormat("dnd/org.jabref.model.entry.BibEntries"); + @SuppressWarnings("unchecked") public static final Class> BIBENTRY_LIST_CLASS = (Class>) (Class) List.class; + } diff --git a/src/main/java/org/jabref/gui/DragDropPane.java b/src/main/java/org/jabref/gui/DragDropPane.java deleted file mode 100644 index 99890bce454..00000000000 --- a/src/main/java/org/jabref/gui/DragDropPane.java +++ /dev/null @@ -1,160 +0,0 @@ -package org.jabref.gui; - -import java.awt.AlphaComposite; -import java.awt.Component; -import java.awt.Font; -import java.awt.Graphics; -import java.awt.Graphics2D; -import java.awt.Point; -import java.awt.Rectangle; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.awt.event.MouseMotionAdapter; - -import javax.swing.JPanel; -import javax.swing.JTabbedPane; -import javax.swing.SwingUtilities; - -import org.jabref.gui.icon.IconTheme; -import org.jabref.gui.icon.JabRefIcon; - -/** - * Extends the JTabbedPane class to support Drag&Drop of Tabs. - */ -class DragDropPane extends JTabbedPane { - - private boolean draggingState; // State var if we are at dragging or not - private int indexDraggedTab; // The index of the tab we drag at the moment - private final MarkerPane markerPane; // The glass panel for painting the position marker - - - DragDropPane() { - super(); - indexDraggedTab = -1; - markerPane = new MarkerPane(); - markerPane.setVisible(false); - - // ------------------------------------------- - // Adding listeners for Drag&Drop Actions - // ------------------------------------------- - addMouseMotionListener(new MouseMotionAdapter() { - - @Override - public void mouseDragged(MouseEvent e) { // Mouse is dragging - // Calculates the tab index based on the mouse position - int indexActTab = getUI().tabForCoordinate(DragDropPane.this, - e.getX(), e.getY()); - if (draggingState) { // We are at tab dragging - if ((indexDraggedTab >= 0) && (indexActTab >= 0)) { //Is it a valid scenario? - boolean toTheLeft = e.getX() <= getUI().getTabBounds(DragDropPane.this, indexActTab).getCenterX(); //Go to the left or to the right of the actual Tab - DragDropPane.this.getRootPane().setGlassPane(markerPane); //Set the MarkerPane as glass Pane - Rectangle actTabRect = SwingUtilities.convertRectangle(DragDropPane.this, getBoundsAt(indexActTab), - DragDropPane.this.markerPane); //Rectangle with the same dimensions as the tab at the mouse position - if (toTheLeft) { - markerPane.setPicLocation(new Point(actTabRect.x, actTabRect.y - + actTabRect.height)); //Set pic to the left of the tab at the mouse position - } - else { - markerPane.setPicLocation(new Point(actTabRect.x + actTabRect.width, actTabRect.y - + actTabRect.height)); //Set pic to the right of the tab at the mouse position - } - - markerPane.setVisible(true); - markerPane.repaint(); - repaint(); - } else { //We have no valid tab tragging scenario - markerPane.setVisible(false); - markerPane.repaint(); - } - - } else { //We are not at tab dragging - if (indexActTab >= 0) { // Mouse is above a tab, otherwise tabNumber would be -1 - // -->Now we are at tab tragging - draggingState = true; // Mark now we are at dragging - indexDraggedTab = indexActTab; // Set draggedTabIndex to the tabNumber where we are now - repaint(); - } - } - super.mouseDragged(e); - } - }); - - addMouseListener(new MouseAdapter() { - - @Override - public void mouseReleased(MouseEvent e) { - DragDropPane.this.markerPane.setVisible(false); //Set MarkerPane invisible - int indexActTab = getUI().tabForCoordinate(DragDropPane.this, - e.getX(), e.getY()); - if ((indexDraggedTab >= 0) && (indexActTab >= 0) && (indexDraggedTab != indexActTab)) { //Is it a valid scenario? - if (draggingState) { //We are at tab dragging - boolean toTheLeft = e.getX() <= getUI().getTabBounds(DragDropPane.this, indexActTab).getCenterX(); //Go to the left or to the right of the actual Tab - DragDropPane.this.markerPane.setVisible(false); - - Component actTab = getComponentAt(indexDraggedTab); //Save dragged tab - String actTabTitle = getTitleAt(indexDraggedTab); //Save Title of the dragged tab - removeTabAt(indexDraggedTab); //Remove dragged tab - int newTabPos; - if (indexActTab < indexDraggedTab) { //We are dragging the tab to the left of its the position - if (toTheLeft && (indexActTab < (DragDropPane.this.getTabCount()))) { - newTabPos = indexActTab; - } else { - newTabPos = indexActTab + 1; - } - } else { //We are dragging the tab to the right of the old position - if (toTheLeft && (indexActTab > 0)) { - newTabPos = indexActTab - 1; - } else { - newTabPos = indexActTab; - } - } - insertTab(actTabTitle, null, actTab, null, newTabPos); //Insert dragged tab at new position - DragDropPane.this.setSelectedIndex(newTabPos); //Set selection back to the tab (at the new tab position - } - } - draggingState = false; - } - }); - } - - - /** - * A glass panel which sets the marker for Dragging of Tabs. - * - */ - static class MarkerPane extends JPanel { - - private Point locationP; - private final JabRefIcon moveTabArrow; - - - public MarkerPane() { - setOpaque(false); - - // Sets the marker fontIcon - moveTabArrow = IconTheme.JabRefIcons.MOVE_TAB_ARROW; - } - - @Override - public void paintComponent(Graphics g) { - super.paintComponent(g); - Graphics2D g2 = (Graphics2D) g; - g2.setComposite(AlphaComposite.getInstance( - AlphaComposite.SRC_OVER, 0.9f)); // Set transparency - g.setFont(IconTheme.FONT.deriveFont(Font.BOLD, 24f)); - moveTabArrow.getSmallIcon().paintIcon(this, g, locationP.x - (moveTabArrow.getIcon().getIconWidth() / 2), - locationP.y + (moveTabArrow.getIcon().getIconHeight() / 2)); - - } - - /** - * Sets the new location, where the marker should be placed. - * - * @param pt the point for the marker - */ - public void setPicLocation(Point pt) { - this.locationP = pt; - } - - } -} diff --git a/src/main/java/org/jabref/gui/DragDropPopupPane.java b/src/main/java/org/jabref/gui/DragDropPopupPane.java deleted file mode 100644 index 42a49da2cfc..00000000000 --- a/src/main/java/org/jabref/gui/DragDropPopupPane.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.jabref.gui; - -import java.awt.event.MouseEvent; - -import javax.swing.JPopupMenu; - -/** - * Adds popup functionality to DragDropPane - */ -public class DragDropPopupPane extends DragDropPane { - - private final JPopupMenu popupMenu; - - public DragDropPopupPane(JPopupMenu menu) { - this.popupMenu = menu; - - addMouseListener(new java.awt.event.MouseAdapter() { - @Override - public void mouseClicked(MouseEvent e) { - tabRightClick(e); - } - }); - } - - private void tabRightClick(MouseEvent e) { - if ((e.getButton() != MouseEvent.BUTTON1) && (e.getClickCount() == 1)) { - // display popup near location of mouse click - popupMenu.show(e.getComponent(), e.getX(), e.getY() - 10); - } - } -} diff --git a/src/main/java/org/jabref/gui/GUIGlobals.java b/src/main/java/org/jabref/gui/GUIGlobals.java index a8c25e50d94..e1f145054ff 100644 --- a/src/main/java/org/jabref/gui/GUIGlobals.java +++ b/src/main/java/org/jabref/gui/GUIGlobals.java @@ -6,6 +6,7 @@ import org.jabref.Globals; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.EmacsKeyBindings; +import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.logic.l10n.Localization; import org.jabref.preferences.JabRefPreferences; @@ -16,12 +17,15 @@ * Static variables for graphics files and keyboard shortcuts. */ public class GUIGlobals { + public static Color editorTextColor; public static Color validFieldBackgroundColor; public static Color activeBackgroundColor; public static Color invalidFieldBackgroundColor; public static Font currentFont; + public static CustomLocalDragboard localDragboard = new CustomLocalDragboard(); + public static final int WIDTH_ICON_COL = 16 + 12; // add some additional space to improve appearance public static final int WIDTH_ICON_COL_RANKING = 5 * 16; // Width of Ranking Icon Column @@ -58,7 +62,7 @@ public static void init() { IconTheme.loadFonts(); GUIGlobals.currentFont = new Font(Globals.prefs.get(JabRefPreferences.FONT_FAMILY), - Globals.prefs.getInt(JabRefPreferences.FONT_STYLE), Globals.prefs.getInt(JabRefPreferences.FONT_SIZE)); + Globals.prefs.getInt(JabRefPreferences.FONT_STYLE), Globals.prefs.getInt(JabRefPreferences.FONT_SIZE)); } public static void setFont(int size) { @@ -66,5 +70,4 @@ public static void setFont(int size) { // update preferences Globals.prefs.putInt(JabRefPreferences.FONT_SIZE, size); } - } diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 2776ed0d70a..7996a474587 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -15,6 +15,7 @@ import java.util.Objects; import java.util.Optional; import java.util.TimerTask; +import java.util.stream.Collectors; import javax.swing.Action; import javax.swing.JComponent; @@ -41,7 +42,9 @@ import javafx.scene.control.TabPane; import javafx.scene.control.ToolBar; import javafx.scene.control.Tooltip; +import javafx.scene.input.DataFormat; import javafx.scene.input.KeyEvent; +import javafx.scene.input.TransferMode; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Pane; @@ -104,6 +107,7 @@ import org.jabref.logic.autosaveandbackup.AutosaveManager; import org.jabref.logic.autosaveandbackup.BackupManager; import org.jabref.logic.importer.IdFetcher; +import org.jabref.logic.importer.OpenDatabase; import org.jabref.logic.importer.OutputPrinter; import org.jabref.logic.importer.ParserResult; import org.jabref.logic.importer.WebFetchers; @@ -215,6 +219,29 @@ private void init() { initKeyBindings(); + tabbedPane.setOnDragOver(event -> { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); + } + }); + + tabbedPane.setOnDragDropped(event -> { + boolean success = false; + + if (event.getDragboard().hasContent(DataFormat.FILES)) { + List files = event.getDragboard().getFiles().stream().map(File::toPath).filter(FileUtil::isBibFile).collect(Collectors.toList()); + success = true; + + for (Path file : files) { + ParserResult pr = OpenDatabase.loadDatabase(file.toString(), Globals.prefs.getImportFormatPreferences(), Globals.getFileUpdateMonitor()); + addParserResult(pr, true); + } + } + + event.setDropCompleted(success); + event.consume(); + }); + //setBounds(GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds()); //WindowLocation pw = new WindowLocation(this, JabRefPreferences.POS_X, JabRefPreferences.POS_Y, JabRefPreferences.SIZE_X, // JabRefPreferences.SIZE_Y); @@ -335,10 +362,10 @@ public void run() { private Void showTrackingNotification() { if (!Globals.prefs.shouldCollectTelemetry()) { boolean shouldCollect = dialogService.showConfirmationDialogAndWait( - Localization.lang("Telemetry: Help make JabRef better"), - Localization.lang("To improve the user experience, we would like to collect anonymous statistics on the features you use. We will only record what features you access and how often you do it. We will neither collect any personal data nor the content of bibliographic items. If you choose to allow data collection, you can later disable it via Options -> Preferences -> General."), - Localization.lang("Share anonymous statistics"), - Localization.lang("Don't share")); + Localization.lang("Telemetry: Help make JabRef better"), + Localization.lang("To improve the user experience, we would like to collect anonymous statistics on the features you use. We will only record what features you access and how often you do it. We will neither collect any personal data nor the content of bibliographic items. If you choose to allow data collection, you can later disable it via Options -> Preferences -> General."), + Localization.lang("Share anonymous statistics"), + Localization.lang("Don't share")); Globals.prefs.setShouldCollectTelemetry(shouldCollect); } @@ -374,9 +401,9 @@ public void setWindowTitle() { if (panel.getBibDatabaseContext().getLocation() == DatabaseLocation.LOCAL) { String changeFlag = panel.isModified() && !isAutosaveEnabled ? "*" : ""; String databaseFile = panel.getBibDatabaseContext() - .getDatabaseFile() - .map(File::getPath) - .orElse(GUIGlobals.UNTITLED_TITLE); + .getDatabaseFile() + .map(File::getPath) + .orElse(GUIGlobals.UNTITLED_TITLE); //setTitle(FRAME_TITLE + " - " + databaseFile + changeFlag + modeInfo); } else if (panel.getBibDatabaseContext().getLocation() == DatabaseLocation.SHARED) { //setTitle(FRAME_TITLE + " - " + panel.getBibDatabaseContext().getDBMSSynchronizer().getDBName() + " [" @@ -562,7 +589,7 @@ private void setDividerPosition() { splitPane.setDividerPositions(prefs.getDouble(JabRefPreferences.SIDE_PANE_WIDTH)); if (!splitPane.getDividers().isEmpty()) { EasyBind.subscribe(splitPane.getDividers().get(0).positionProperty(), - position -> prefs.putDouble(JabRefPreferences.SIDE_PANE_WIDTH, position.doubleValue())); + position -> prefs.putDouble(JabRefPreferences.SIDE_PANE_WIDTH, position.doubleValue())); } } @@ -582,43 +609,43 @@ private Node createToolbar() { } HBox leftSide = new HBox( - newLibrary, - factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(this)), - factory.createIconButton(StandardActions.SAVE_LIBRARY, new OldDatabaseCommandWrapper(Actions.SAVE, this, Globals.stateManager)), - leftSpacer); + newLibrary, + factory.createIconButton(StandardActions.OPEN_LIBRARY, new OpenDatabaseAction(this)), + factory.createIconButton(StandardActions.SAVE_LIBRARY, new OldDatabaseCommandWrapper(Actions.SAVE, this, Globals.stateManager)), + leftSpacer + ); leftSide.setMinWidth(100); leftSide.prefWidthProperty().bind(sidePane.widthProperty()); leftSide.maxWidthProperty().bind(sidePane.widthProperty()); PushToApplicationButton pushToExternal = new PushToApplicationButton(this, pushApplications.getApplications()); - HBox rightSide = new HBox ( - factory.createIconButton(StandardActions.NEW_ENTRY, new NewEntryAction(this, BiblatexEntryTypes.ARTICLE)), - factory.createIconButton(StandardActions.DELETE_ENTRY, new OldDatabaseCommandWrapper(Actions.DELETE, this, Globals.stateManager)), - - factory.createIconButton(StandardActions.UNDO, new OldDatabaseCommandWrapper(Actions.UNDO, this, Globals.stateManager)), - factory.createIconButton(StandardActions.REDO, new OldDatabaseCommandWrapper(Actions.REDO, this, Globals.stateManager)), - factory.createIconButton(StandardActions.CUT, new OldDatabaseCommandWrapper(Actions.CUT, this, Globals.stateManager)), - factory.createIconButton(StandardActions.COPY, new OldDatabaseCommandWrapper(Actions.COPY, this, Globals.stateManager)), - factory.createIconButton(StandardActions.PASTE, new OldDatabaseCommandWrapper(Actions.PASTE, this, Globals.stateManager)), - - factory.createIconButton(StandardActions.CLEANUP_ENTRIES, new OldDatabaseCommandWrapper(Actions.CLEANUP, this, Globals.stateManager)), - factory.createIconButton(pushToExternal.getMenuAction(), pushToExternal), - - factory.createIconButton(StandardActions.FORK_ME, new OpenBrowserAction("https://github.com/JabRef/jabref")), - factory.createIconButton(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/")), - factory.createIconButton(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org")) - ); + HBox rightSide = new HBox( + factory.createIconButton(StandardActions.NEW_ENTRY, new NewEntryAction(this, BiblatexEntryTypes.ARTICLE)), + factory.createIconButton(StandardActions.DELETE_ENTRY, new OldDatabaseCommandWrapper(Actions.DELETE, this, Globals.stateManager)), + + factory.createIconButton(StandardActions.UNDO, new OldDatabaseCommandWrapper(Actions.UNDO, this, Globals.stateManager)), + factory.createIconButton(StandardActions.REDO, new OldDatabaseCommandWrapper(Actions.REDO, this, Globals.stateManager)), + factory.createIconButton(StandardActions.CUT, new OldDatabaseCommandWrapper(Actions.CUT, this, Globals.stateManager)), + factory.createIconButton(StandardActions.COPY, new OldDatabaseCommandWrapper(Actions.COPY, this, Globals.stateManager)), + factory.createIconButton(StandardActions.PASTE, new OldDatabaseCommandWrapper(Actions.PASTE, this, Globals.stateManager)), + + factory.createIconButton(StandardActions.CLEANUP_ENTRIES, new OldDatabaseCommandWrapper(Actions.CLEANUP, this, Globals.stateManager)), + factory.createIconButton(pushToExternal.getMenuAction(), pushToExternal), + + factory.createIconButton(StandardActions.FORK_ME, new OpenBrowserAction("https://github.com/JabRef/jabref")), + factory.createIconButton(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/")), + factory.createIconButton(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org")) + ); HBox.setHgrow(globalSearchBar, Priority.ALWAYS); ToolBar toolBar = new ToolBar( - leftSide, + leftSide, - globalSearchBar, + globalSearchBar, - rightSpacer, - rightSide - ); + rightSpacer, + rightSide); toolBar.getStyleClass().add("mainToolbar"); return toolBar; @@ -707,63 +734,62 @@ private MenuBar createMenu() { Menu help = new Menu(Localization.lang("Help")); file.getItems().addAll( - factory.createMenuItem(StandardActions.NEW_LIBRARY_BIBTEX, new NewDatabaseAction(this, BibDatabaseMode.BIBTEX)), - factory.createMenuItem(StandardActions.NEW_LIBRARY_BIBLATEX, new NewDatabaseAction(this, BibDatabaseMode.BIBLATEX)), - factory.createMenuItem(StandardActions.OPEN_LIBRARY, getOpenDatabaseAction()), - factory.createMenuItem(StandardActions.SAVE_LIBRARY, new OldDatabaseCommandWrapper(Actions.SAVE, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.SAVE_LIBRARY_AS, new OldDatabaseCommandWrapper(Actions.SAVE_AS, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.SAVE_ALL, new SaveAllAction(this)), + factory.createMenuItem(StandardActions.NEW_LIBRARY_BIBTEX, new NewDatabaseAction(this, BibDatabaseMode.BIBTEX)), + factory.createMenuItem(StandardActions.NEW_LIBRARY_BIBLATEX, new NewDatabaseAction(this, BibDatabaseMode.BIBLATEX)), + factory.createMenuItem(StandardActions.OPEN_LIBRARY, getOpenDatabaseAction()), + factory.createMenuItem(StandardActions.SAVE_LIBRARY, new OldDatabaseCommandWrapper(Actions.SAVE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.SAVE_LIBRARY_AS, new OldDatabaseCommandWrapper(Actions.SAVE_AS, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.SAVE_ALL, new SaveAllAction(this)), - factory.createSubMenu(StandardActions.IMPORT_EXPORT, - factory.createMenuItem(StandardActions.MERGE_DATABASE, new OldDatabaseCommandWrapper(Actions.MERGE_DATABASE, this, Globals.stateManager)), // TODO: merge with import - factory.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, new ImportCommand(this, false)), - factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, new ImportCommand(this, true)), - factory.createMenuItem(StandardActions.EXPORT_ALL, new ExportCommand(this, false, Globals.prefs)), - factory.createMenuItem(StandardActions.EXPORT_SELECTED, new ExportCommand(this, true, Globals.prefs)), - factory.createMenuItem(StandardActions.SAVE_SELECTED_AS_PLAIN_BIBTEX, new OldDatabaseCommandWrapper(Actions.SAVE_SELECTED_AS_PLAIN, this, Globals.stateManager)) - ), + factory.createSubMenu(StandardActions.IMPORT_EXPORT, + factory.createMenuItem(StandardActions.MERGE_DATABASE, new OldDatabaseCommandWrapper(Actions.MERGE_DATABASE, this, Globals.stateManager)), // TODO: merge with import + factory.createMenuItem(StandardActions.IMPORT_INTO_CURRENT_LIBRARY, new ImportCommand(this, false)), + factory.createMenuItem(StandardActions.IMPORT_INTO_NEW_LIBRARY, new ImportCommand(this, true)), + factory.createMenuItem(StandardActions.EXPORT_ALL, new ExportCommand(this, false, Globals.prefs)), + factory.createMenuItem(StandardActions.EXPORT_SELECTED, new ExportCommand(this, true, Globals.prefs)), + factory.createMenuItem(StandardActions.SAVE_SELECTED_AS_PLAIN_BIBTEX, new OldDatabaseCommandWrapper(Actions.SAVE_SELECTED_AS_PLAIN, this, Globals.stateManager))), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.CONNECT_TO_SHARED_DB, new ConnectToSharedDatabaseCommand(this)), - factory.createMenuItem(StandardActions.PULL_CHANGES_FROM_SHARED_DB, new OldDatabaseCommandWrapper(Actions.PULL_CHANGES_FROM_SHARED_DATABASE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.CONNECT_TO_SHARED_DB, new ConnectToSharedDatabaseCommand(this)), + factory.createMenuItem(StandardActions.PULL_CHANGES_FROM_SHARED_DB, new OldDatabaseCommandWrapper(Actions.PULL_CHANGES_FROM_SHARED_DATABASE, this, Globals.stateManager)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - fileHistory, + fileHistory, - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction()), - factory.createMenuItem(StandardActions.QUIT, new CloseAction()) + factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction()), + factory.createMenuItem(StandardActions.QUIT, new CloseAction()) ); edit.getItems().addAll( - factory.createMenuItem(StandardActions.UNDO, new OldDatabaseCommandWrapper(Actions.UNDO, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.REDO, new OldDatabaseCommandWrapper(Actions.REDO, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.UNDO, new OldDatabaseCommandWrapper(Actions.UNDO, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.REDO, new OldDatabaseCommandWrapper(Actions.REDO, this, Globals.stateManager)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.CUT, new EditAction(Actions.CUT)), + factory.createMenuItem(StandardActions.CUT, new EditAction(Actions.CUT)), - factory.createMenuItem(StandardActions.COPY, new EditAction(Actions.COPY)), - factory.createSubMenu(StandardActions.COPY_MORE, - factory.createMenuItem(StandardActions.COPY_TITLE, new OldDatabaseCommandWrapper(Actions.COPY_TITLE, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.COPY_KEY, new OldDatabaseCommandWrapper(Actions.COPY_KEY, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.COPY_CITE_KEY, new OldDatabaseCommandWrapper(Actions.COPY_CITE_KEY, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new OldDatabaseCommandWrapper(Actions.COPY_KEY_AND_TITLE, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new OldDatabaseCommandWrapper(Actions.COPY_KEY_AND_LINK, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new OldDatabaseCommandWrapper(Actions.COPY_CITATION_HTML, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.EXPORT_SELECTED_TO_CLIPBOARD, new OldDatabaseCommandWrapper(Actions.EXPORT_TO_CLIPBOARD, this, Globals.stateManager))), + factory.createMenuItem(StandardActions.COPY, new EditAction(Actions.COPY)), + factory.createSubMenu(StandardActions.COPY_MORE, + factory.createMenuItem(StandardActions.COPY_TITLE, new OldDatabaseCommandWrapper(Actions.COPY_TITLE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_KEY, new OldDatabaseCommandWrapper(Actions.COPY_KEY, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_CITE_KEY, new OldDatabaseCommandWrapper(Actions.COPY_CITE_KEY, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_TITLE, new OldDatabaseCommandWrapper(Actions.COPY_KEY_AND_TITLE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_KEY_AND_LINK, new OldDatabaseCommandWrapper(Actions.COPY_KEY_AND_LINK, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_CITATION_PREVIEW, new OldDatabaseCommandWrapper(Actions.COPY_CITATION_HTML, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.EXPORT_SELECTED_TO_CLIPBOARD, new OldDatabaseCommandWrapper(Actions.EXPORT_TO_CLIPBOARD, this, Globals.stateManager))), - factory.createMenuItem(StandardActions.PASTE, new EditAction(Actions.PASTE)), + factory.createMenuItem(StandardActions.PASTE, new EditAction(Actions.PASTE)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new OldDatabaseCommandWrapper(Actions.SEND_AS_EMAIL, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.SEND_AS_EMAIL, new OldDatabaseCommandWrapper(Actions.SEND_AS_EMAIL, this, Globals.stateManager)), - new SeparatorMenuItem() + new SeparatorMenuItem() ); @@ -805,26 +831,26 @@ private MenuBar createMenu() { } edit.getItems().addAll( - factory.createMenuItem(StandardActions.MANAGE_KEYWORDS, new ManageKeywordsAction(this)), - factory.createMenuItem(StandardActions.REPLACE_ALL, new OldDatabaseCommandWrapper(Actions.REPLACE_ALL, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.MASS_SET_FIELDS, new MassSetFieldAction(this)) + factory.createMenuItem(StandardActions.MANAGE_KEYWORDS, new ManageKeywordsAction(this)), + factory.createMenuItem(StandardActions.REPLACE_ALL, new OldDatabaseCommandWrapper(Actions.REPLACE_ALL, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.MASS_SET_FIELDS, new MassSetFieldAction(this)) ); library.getItems().addAll( - factory.createMenuItem(StandardActions.NEW_ARTICLE, new NewEntryAction(this, BibtexEntryTypes.ARTICLE)), - factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this)), - factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAINTEX, new NewEntryFromPlainTextAction(this, Globals.prefs.getUpdateFieldPreferences())), + factory.createMenuItem(StandardActions.NEW_ARTICLE, new NewEntryAction(this, BibtexEntryTypes.ARTICLE)), + factory.createMenuItem(StandardActions.NEW_ENTRY, new NewEntryAction(this)), + factory.createMenuItem(StandardActions.NEW_ENTRY_FROM_PLAINTEX, new NewEntryFromPlainTextAction(this, Globals.prefs.getUpdateFieldPreferences())), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.DELETE_ENTRY, new OldDatabaseCommandWrapper(Actions.DELETE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.DELETE_ENTRY, new OldDatabaseCommandWrapper(Actions.DELETE, this, Globals.stateManager)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, new DatabasePropertiesAction(this)), - factory.createMenuItem(StandardActions.EDIT_PREAMBLE, new PreambleEditor(this)), - factory.createMenuItem(StandardActions.EDIT_STRINGS, new OldDatabaseCommandWrapper(Actions.EDIT_STRINGS, this, Globals.stateManager)) + factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, new DatabasePropertiesAction(this)), + factory.createMenuItem(StandardActions.EDIT_PREAMBLE, new PreambleEditor(this)), + factory.createMenuItem(StandardActions.EDIT_STRINGS, new OldDatabaseCommandWrapper(Actions.EDIT_STRINGS, this, Globals.stateManager)) ); @@ -835,22 +861,22 @@ private MenuBar createMenu() { } quality.getItems().addAll( - factory.createMenuItem(StandardActions.FIND_DUPLICATES, new DuplicateSearch(this)), - factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(this)), + factory.createMenuItem(StandardActions.FIND_DUPLICATES, new DuplicateSearch(this)), + factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(this)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), factory.createMenuItem(StandardActions.RESOLVE_DUPLICATE_KEYS, new OldDatabaseCommandWrapper(Actions.RESOLVE_DUPLICATE_KEYS, this, Globals.stateManager)), factory.createMenuItem(StandardActions.CHECK_INTEGRITY, new IntegrityCheckAction(this)), factory.createMenuItem(StandardActions.CLEANUP_ENTRIES, new OldDatabaseCommandWrapper(Actions.CLEANUP, this, Globals.stateManager)), factory.createMenuItem(StandardActions.GENERATE_CITE_KEYS, new OldDatabaseCommandWrapper(Actions.MAKE_KEY, this, Globals.stateManager)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SET_FILE_LINKS, new AutoLinkFilesAction()), - factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, new FindUnlinkedFilesAction(this)), - lookupIdentifiers, - factory.createMenuItem(StandardActions.DOWNLOAD_FULL_TEXT, new OldDatabaseCommandWrapper(Actions.DOWNLOAD_FULL_TEXT, this, Globals.stateManager)) + factory.createMenuItem(StandardActions.SET_FILE_LINKS, new AutoLinkFilesAction()), + factory.createMenuItem(StandardActions.FIND_UNLINKED_FILES, new FindUnlinkedFilesAction(this)), + lookupIdentifiers, + factory.createMenuItem(StandardActions.DOWNLOAD_FULL_TEXT, new OldDatabaseCommandWrapper(Actions.DOWNLOAD_FULL_TEXT, this, Globals.stateManager)) ); @@ -859,67 +885,67 @@ private MenuBar createMenu() { SidePaneComponent openOffice = sidePaneManager.getComponent(SidePaneType.OPEN_OFFICE); view.getItems().addAll( - factory.createMenuItem(webSearch.getToggleAction(), webSearch.getToggleCommand()), - factory.createMenuItem(groups.getToggleAction(), groups.getToggleCommand()), - factory.createMenuItem(StandardActions.TOGGLE_PREVIEW, new OldDatabaseCommandWrapper(Actions.TOGGLE_PREVIEW, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.EDIT_ENTRY, new OldDatabaseCommandWrapper(Actions.EDIT, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.SHOW_PDV_VIEWER, new ShowDocumentViewerAction()), + factory.createMenuItem(webSearch.getToggleAction(), webSearch.getToggleCommand()), + factory.createMenuItem(groups.getToggleAction(), groups.getToggleCommand()), + factory.createMenuItem(StandardActions.TOGGLE_PREVIEW, new OldDatabaseCommandWrapper(Actions.TOGGLE_PREVIEW, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.EDIT_ENTRY, new OldDatabaseCommandWrapper(Actions.EDIT, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.SHOW_PDV_VIEWER, new ShowDocumentViewerAction()), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SELECT_ALL, new OldDatabaseCommandWrapper(Actions.SELECT_ALL, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.SELECT_ALL, new OldDatabaseCommandWrapper(Actions.SELECT_ALL, this, Globals.stateManager)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.NEXT_PREVIEW_STYLE, new OldDatabaseCommandWrapper(Actions.NEXT_PREVIEW_STYLE, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.PREVIOUS_PREVIEW_STYLE, new OldDatabaseCommandWrapper(Actions.PREVIOUS_PREVIEW_STYLE, this, Globals.stateManager)) + factory.createMenuItem(StandardActions.NEXT_PREVIEW_STYLE, new OldDatabaseCommandWrapper(Actions.NEXT_PREVIEW_STYLE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.PREVIOUS_PREVIEW_STYLE, new OldDatabaseCommandWrapper(Actions.PREVIOUS_PREVIEW_STYLE, this, Globals.stateManager)) ); PushToApplicationButton pushToExternal = new PushToApplicationButton(this, pushApplications.getApplications()); tools.getItems().addAll( - factory.createMenuItem(StandardActions.NEW_SUB_LIBRARY_FROM_AUX, new NewSubLibraryAction(this)), - factory.createMenuItem(StandardActions.WRITE_XMP, new OldDatabaseCommandWrapper(Actions.WRITE_XMP, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.NEW_SUB_LIBRARY_FROM_AUX, new NewSubLibraryAction(this)), + factory.createMenuItem(StandardActions.WRITE_XMP, new OldDatabaseCommandWrapper(Actions.WRITE_XMP, this, Globals.stateManager)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(openOffice.getToggleAction(), openOffice.getToggleCommand()), - factory.createMenuItem(pushToExternal.getMenuAction(), pushToExternal), + factory.createMenuItem(openOffice.getToggleAction(), openOffice.getToggleCommand()), + factory.createMenuItem(pushToExternal.getMenuAction(), pushToExternal), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.OPEN_FOLDER, new OldDatabaseCommandWrapper(Actions.OPEN_FOLDER, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.OPEN_FILE, new OldDatabaseCommandWrapper(Actions.OPEN_EXTERNAL_FILE, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.OPEN_URL, new OldDatabaseCommandWrapper(Actions.OPEN_URL, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OldDatabaseCommandWrapper(Actions.OPEN_CONSOLE, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.COPY_LINKED_FILES, new CopyFilesAction(this)), + factory.createMenuItem(StandardActions.OPEN_FOLDER, new OldDatabaseCommandWrapper(Actions.OPEN_FOLDER, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.OPEN_FILE, new OldDatabaseCommandWrapper(Actions.OPEN_EXTERNAL_FILE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.OPEN_URL, new OldDatabaseCommandWrapper(Actions.OPEN_URL, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OldDatabaseCommandWrapper(Actions.OPEN_CONSOLE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.COPY_LINKED_FILES, new CopyFilesAction(this)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.ABBREVIATE_ISO, new OldDatabaseCommandWrapper(Actions.ABBREVIATE_ISO, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.ABBREVIATE_MEDLINE, new OldDatabaseCommandWrapper(Actions.ABBREVIATE_MEDLINE, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.UNABBREVIATE, new OldDatabaseCommandWrapper(Actions.UNABBREVIATE, this, Globals.stateManager)) + factory.createMenuItem(StandardActions.ABBREVIATE_ISO, new OldDatabaseCommandWrapper(Actions.ABBREVIATE_ISO, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.ABBREVIATE_MEDLINE, new OldDatabaseCommandWrapper(Actions.ABBREVIATE_MEDLINE, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.UNABBREVIATE, new OldDatabaseCommandWrapper(Actions.UNABBREVIATE, this, Globals.stateManager)) ); options.getItems().addAll( - factory.createMenuItem(StandardActions.SHOW_PREFS, new ShowPreferencesAction(this)), + factory.createMenuItem(StandardActions.SHOW_PREFS, new ShowPreferencesAction(this)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SETUP_GENERAL_FIELDS, new SetupGeneralFieldsAction(this)), - factory.createMenuItem(StandardActions.MANAGE_CUSTOM_IMPORTS, new ManageCustomImportsAction(this)), - factory.createMenuItem(StandardActions.MANAGE_CUSTOM_EXPORTS, new ManageCustomExportsAction(this)), - factory.createMenuItem(StandardActions.MANAGE_EXTERNAL_FILETYPES, new EditExternalFileTypesAction()), - factory.createMenuItem(StandardActions.MANAGE_JOURNALS, new ManageJournalsAction()), - factory.createMenuItem(StandardActions.CUSTOMIZE_KEYBINDING, new CustomizeKeyBindingAction()), - factory.createMenuItem(StandardActions.MANAGE_PROTECTED_TERMS, new ManageProtectedTermsAction(this, Globals.protectedTermsLoader)), + factory.createMenuItem(StandardActions.SETUP_GENERAL_FIELDS, new SetupGeneralFieldsAction(this)), + factory.createMenuItem(StandardActions.MANAGE_CUSTOM_IMPORTS, new ManageCustomImportsAction(this)), + factory.createMenuItem(StandardActions.MANAGE_CUSTOM_EXPORTS, new ManageCustomExportsAction(this)), + factory.createMenuItem(StandardActions.MANAGE_EXTERNAL_FILETYPES, new EditExternalFileTypesAction()), + factory.createMenuItem(StandardActions.MANAGE_JOURNALS, new ManageJournalsAction()), + factory.createMenuItem(StandardActions.CUSTOMIZE_KEYBINDING, new CustomizeKeyBindingAction()), + factory.createMenuItem(StandardActions.MANAGE_PROTECTED_TERMS, new ManageProtectedTermsAction(this, Globals.protectedTermsLoader)), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.MANAGE_CONTENT_SELECTORS, new OldDatabaseCommandWrapper(Actions.MANAGE_SELECTORS, this, Globals.stateManager)), - factory.createMenuItem(StandardActions.CUSTOMIZE_ENTRY_TYPES, new CustomizeEntryAction(this)), - factory.createMenuItem(StandardActions.MANAGE_CITE_KEY_PATTERNS, new BibtexKeyPatternAction(this)) + factory.createMenuItem(StandardActions.MANAGE_CONTENT_SELECTORS, new OldDatabaseCommandWrapper(Actions.MANAGE_SELECTORS, this, Globals.stateManager)), + factory.createMenuItem(StandardActions.CUSTOMIZE_ENTRY_TYPES, new CustomizeEntryAction(this)), + factory.createMenuItem(StandardActions.MANAGE_CITE_KEY_PATTERNS, new BibtexKeyPatternAction(this)) ); @@ -927,45 +953,45 @@ private MenuBar createMenu() { factory.createMenuItem(StandardActions.HELP, HelpAction.getMainHelpPageCommand()), factory.createMenuItem(StandardActions.OPEN_FORUM, new OpenBrowserAction("http://discourse.jabref.org/")), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.ERROR_CONSOLE, new ErrorConsoleAction()), + factory.createMenuItem(StandardActions.ERROR_CONSOLE, new ErrorConsoleAction()), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction()), - factory.createSubMenu(StandardActions.WEB_MENU, - factory.createMenuItem(StandardActions.OPEN_WEBPAGE, new OpenBrowserAction("https://jabref.org/")), - factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction("https://blog.jabref.org/")), - factory.createMenuItem(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/")), - factory.createMenuItem(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org")), + factory.createMenuItem(StandardActions.SEARCH_FOR_UPDATES, new SearchForUpdateAction()), + factory.createSubMenu(StandardActions.WEB_MENU, + factory.createMenuItem(StandardActions.OPEN_WEBPAGE, new OpenBrowserAction("https://jabref.org/")), + factory.createMenuItem(StandardActions.OPEN_BLOG, new OpenBrowserAction("https://blog.jabref.org/")), + factory.createMenuItem(StandardActions.OPEN_FACEBOOK, new OpenBrowserAction("https://www.facebook.com/JabRef/")), + factory.createMenuItem(StandardActions.OPEN_TWITTER, new OpenBrowserAction("https://twitter.com/jabref_org")), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.FORK_ME, new OpenBrowserAction("https://github.com/JabRef/jabref")), - factory.createMenuItem(StandardActions.OPEN_DEV_VERSION_LINK, new OpenBrowserAction("https://builds.jabref.org/master/")), - factory.createMenuItem(StandardActions.OPEN_CHANGELOG, new OpenBrowserAction("https://github.com/JabRef/jabref/blob/master/CHANGELOG.md")), + factory.createMenuItem(StandardActions.FORK_ME, new OpenBrowserAction("https://github.com/JabRef/jabref")), + factory.createMenuItem(StandardActions.OPEN_DEV_VERSION_LINK, new OpenBrowserAction("https://builds.jabref.org/master/")), + factory.createMenuItem(StandardActions.OPEN_CHANGELOG, new OpenBrowserAction("https://github.com/JabRef/jabref/blob/master/CHANGELOG.md")), - new SeparatorMenuItem(), + new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.DONATE, new OpenBrowserAction("https://donations.jabref.org")) + factory.createMenuItem(StandardActions.DONATE, new OpenBrowserAction("https://donations.jabref.org")) - ), - factory.createMenuItem(StandardActions.ABOUT, new AboutAction()) + ), + factory.createMenuItem(StandardActions.ABOUT, new AboutAction()) ); MenuBar menu = new MenuBar(); menu.getStyleClass().add("mainMenu"); menu.getMenus().addAll( - file, - edit, - library, - quality, - tools, - view, - options, - help); + file, + edit, + library, + quality, + tools, + view, + options, + help); menu.setUseSystemMenuBar(true); return menu; } @@ -984,8 +1010,8 @@ public void addParserResult(ParserResult pr, boolean focusPanel) { } else { // only add tab if DB is not already open Optional panel = getBasePanelList().stream() - .filter(p -> p.getBibDatabaseContext().getDatabaseFile().equals(pr.getFile())) - .findFirst(); + .filter(p -> p.getBibDatabaseContext().getDatabasePath().equals(pr.getFile())) + .findFirst(); if (panel.isPresent()) { tabbedPane.getSelectionModel().select(getTab(panel.get())); @@ -999,7 +1025,7 @@ public void addParserResult(ParserResult pr, boolean focusPanel) { * displays the String on the Status Line visible on the bottom of the JabRef mainframe */ public void output(final String s) { - statusLine.setText(s); + statusLine.setText(s); } private void initActions() { @@ -1196,8 +1222,9 @@ public BasePanel addTab(BibDatabaseContext databaseContext, boolean raisePanel) private boolean readyForAutosave(BibDatabaseContext context) { return ((context.getLocation() == DatabaseLocation.SHARED) || - ((context.getLocation() == DatabaseLocation.LOCAL) && Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE))) && - context.getDatabaseFile().isPresent(); + ((context.getLocation() == DatabaseLocation.LOCAL) && Globals.prefs.getBoolean(JabRefPreferences.LOCAL_AUTO_SAVE))) + && + context.getDatabaseFile().isPresent(); } /** @@ -1210,7 +1237,7 @@ private boolean readyForAutosave(BibDatabaseContext context) { private void addImportedEntries(final BasePanel panel, final List entries) { SwingUtilities.invokeLater(() -> { ImportInspectionDialog diag = new ImportInspectionDialog(JabRefFrame.this, panel, - Localization.lang("Import"), false); + Localization.lang("Import"), false); diag.addEntries(entries); diag.entryListComplete(); diag.setVisible(true); @@ -1307,9 +1334,9 @@ private boolean confirmClose(BasePanel panel) { ButtonType cancel = new ButtonType(Localization.lang("Return to JabRef"), ButtonBar.ButtonData.CANCEL_CLOSE); Optional response = dialogService.showCustomButtonDialogAndWait(Alert.AlertType.CONFIRMATION, - Localization.lang("Save before closing"), - Localization.lang("Library '%0' has changed.", filename), - saveChanges, discardChanges, cancel); + Localization.lang("Save before closing"), + Localization.lang("Library '%0' has changed.", filename), + saveChanges, discardChanges, cancel); if (response.isPresent() && response.get().equals(saveChanges)) { // The user wants to save. @@ -1324,7 +1351,9 @@ private boolean confirmClose(BasePanel panel) { } catch (Throwable ex) { return false; } - } else return !response.isPresent() || !response.get().equals(cancel); + } else { + return !response.isPresent() || !response.get().equals(cancel); + } return false; } diff --git a/src/main/java/org/jabref/gui/PreviewPanel.java b/src/main/java/org/jabref/gui/PreviewPanel.java index b5067e7accf..5d7f4c6d016 100644 --- a/src/main/java/org/jabref/gui/PreviewPanel.java +++ b/src/main/java/org/jabref/gui/PreviewPanel.java @@ -1,10 +1,14 @@ package org.jabref.gui; +import java.io.File; import java.io.IOException; import java.io.StringReader; +import java.nio.file.Path; +import java.util.List; import java.util.Optional; import java.util.concurrent.Future; import java.util.regex.Pattern; +import java.util.stream.Collectors; import javafx.print.PrinterJob; import javafx.scene.control.ContextMenu; @@ -12,12 +16,15 @@ import javafx.scene.control.ScrollPane; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DataFormat; import javafx.scene.input.Dragboard; import javafx.scene.input.KeyEvent; import javafx.scene.input.TransferMode; import javafx.scene.web.WebView; import org.jabref.Globals; +import org.jabref.gui.externalfiles.NewDroppedFileHandler; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; @@ -33,6 +40,7 @@ import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.event.FieldChangedEvent; +import org.jabref.preferences.JabRefPreferences; import org.jabref.preferences.PreviewPreferences; import com.google.common.eventbus.Subscribe; @@ -63,20 +71,30 @@ public class PreviewPanel extends ScrollPane implements SearchQueryHighlightList * If a database is set, the preview will attempt to resolve strings in the previewed entry using that database. */ private Optional databaseContext = Optional.empty(); - private WebView previewView; + private final WebView previewView; private Optional> citationStyleFuture = Optional.empty(); + private final NewDroppedFileHandler fileHandler; + /** * @param panel (may be null) Only set this if the preview is associated to the main window. * @param databaseContext (may be null) Used for resolving pdf directories for links. */ - public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext, KeyBindingRepository keyBindingRepository, PreviewPreferences preferences, DialogService dialogService) { + public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext, KeyBindingRepository keyBindingRepository, PreviewPreferences preferences, DialogService dialogService, ExternalFileTypes externalFileTypes) { this.databaseContext = Optional.ofNullable(databaseContext); this.basePanel = Optional.ofNullable(panel); this.dialogService = dialogService; this.clipBoardManager = Globals.clipboardManager; this.keyBindingRepository = keyBindingRepository; + fileHandler = new NewDroppedFileHandler(dialogService, databaseContext, externalFileTypes, + Globals.prefs.getFileDirectoryPreferences(), + Globals.prefs.getCleanupPreferences(Globals.journalAbbreviationLoader).getFileDirPattern(), + Globals.prefs.getImportFormatPreferences(), + Globals.prefs.getUpdateFieldPreferences(), + Globals.getFileUpdateMonitor(), + Globals.prefs.get(JabRefPreferences.IMPORT_FILENAMEPATTERN)); + // Set up scroll pane for preview pane setFitToHeight(true); setFitToWidth(true); @@ -89,17 +107,53 @@ public PreviewPanel(BasePanel panel, BibDatabaseContext databaseContext, KeyBind // Handler for drag content of preview to different window // only created for main window (not for windows like the search results dialog) setOnDragDetected(event -> { - Dragboard dragboard = startDragAndDrop(TransferMode.COPY); - ClipboardContent content = new ClipboardContent(); - content.putHtml((String) previewView.getEngine().executeScript("window.getSelection().toString()")); - dragboard.setContent(content); + startFullDrag(); - event.consume(); - } - ); + Dragboard dragboard = startDragAndDrop(TransferMode.COPY); + ClipboardContent content = new ClipboardContent(); + content.putHtml((String) previewView.getEngine().executeScript("window.getSelection().toString()")); + dragboard.setContent(content); + + event.consume(); + }); } + this.previewView.setOnDragOver(event -> { + if (event.getDragboard().hasFiles()) { + event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); + } + event.consume(); + }); + + this.previewView.setOnDragDropped(event -> { + BibEntry entry = this.getEntry(); + boolean success = false; + if (event.getDragboard().hasContent(DataFormat.FILES)) { + List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); + + if (event.getTransferMode() == TransferMode.MOVE) { + + LOGGER.debug("Mode MOVE"); //shift on win or no modifier + fileHandler.addToEntryRenameAndMoveToFileDir(entry, files); + } + if (event.getTransferMode() == TransferMode.LINK) { + LOGGER.debug("Node LINK"); //alt on win + fileHandler.addToEntry(entry, files); + + } + if (event.getTransferMode() == TransferMode.COPY) { + LOGGER.debug("Mode Copy"); //ctrl on win, no modifier on Xubuntu + fileHandler.copyFilesToFileDirAndAddToEntry(entry, files); + } + } + + event.setDropCompleted(success); + event.consume(); + + }); + createKeyBindings(); updateLayout(preferences); + } private void createKeyBindings() { @@ -227,22 +281,22 @@ public void update() { if (layout.isPresent()) { StringBuilder sb = new StringBuilder(); bibEntry.ifPresent(entry -> sb.append(layout.get() - .doLayout(entry, databaseContext.map(BibDatabaseContext::getDatabase).orElse(null)))); + .doLayout(entry, databaseContext.map(BibDatabaseContext::getDatabase).orElse(null)))); setPreviewLabel(sb.toString()); } else if (basePanel.isPresent() && bibEntry.isPresent()) { Future citationStyleWorker = BackgroundTask - .wrap(() -> basePanel.get().getCitationStyleCache().getCitationFor(bibEntry.get())) - .onRunning(() -> { - CitationStyle citationStyle = basePanel.get().getCitationStyleCache().getCitationStyle(); - setPreviewLabel("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + - ": " + citationStyle.getTitle() + " ..." + ""); - }) - .onSuccess(this::setPreviewLabel) - .onFailure(exception -> { - LOGGER.error("Error while generating citation style", exception); - setPreviewLabel(Localization.lang("Error while generating citation style")); - }) - .executeWith(Globals.TASK_EXECUTOR); + .wrap(() -> basePanel.get().getCitationStyleCache().getCitationFor(bibEntry.get())) + .onRunning(() -> { + CitationStyle citationStyle = basePanel.get().getCitationStyleCache().getCitationStyle(); + setPreviewLabel("" + Localization.lang("Processing %0", Localization.lang("Citation Style")) + + ": " + citationStyle.getTitle() + " ..." + ""); + }) + .onSuccess(this::setPreviewLabel) + .onFailure(exception -> { + LOGGER.error("Error while generating citation style", exception); + setPreviewLabel(Localization.lang("Error while generating citation style")); + }) + .executeWith(Globals.TASK_EXECUTOR); this.citationStyleFuture = Optional.of(citationStyleWorker); } } @@ -283,8 +337,8 @@ public void print() { job.endJob(); return null; }) - .onFailure(exception -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), exception)) - .executeWith(Globals.TASK_EXECUTOR); + .onFailure(exception -> dialogService.showErrorDialogAndWait(Localization.lang("Could not print preview"), exception)) + .executeWith(Globals.TASK_EXECUTOR); } public void close() { diff --git a/src/main/java/org/jabref/gui/TransferableBibtexEntry.java b/src/main/java/org/jabref/gui/TransferableBibtexEntry.java deleted file mode 100644 index 742faea2cc9..00000000000 --- a/src/main/java/org/jabref/gui/TransferableBibtexEntry.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.jabref.gui; - -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.IOException; -import java.io.StringWriter; -import java.util.List; - -import javax.swing.JOptionPane; - -import org.jabref.Globals; -import org.jabref.logic.bibtex.BibEntryWriter; -import org.jabref.logic.bibtex.LatexFieldFormatter; -import org.jabref.logic.l10n.Localization; -import org.jabref.model.database.BibDatabaseMode; -import org.jabref.model.entry.BibEntry; - -/* - * A transferable object containing an array of BibEntry objects. Used - * for copy-paste operations. - */ -public class TransferableBibtexEntry implements Transferable { - - public static final DataFlavor ENTRY_FLAVOR = new DataFlavor(BibEntry.class, "JabRef entry"); - private final List data; - - - public TransferableBibtexEntry(List bes) { - this.data = bes; - } - - @Override - public DataFlavor[] getTransferDataFlavors() { - return new DataFlavor[]{TransferableBibtexEntry.ENTRY_FLAVOR, - DataFlavor.stringFlavor}; - } - - @Override - public boolean isDataFlavorSupported(DataFlavor flavor) { - return flavor.equals(TransferableBibtexEntry.ENTRY_FLAVOR) || flavor.equals(DataFlavor.stringFlavor); - } - - @Override - public Object getTransferData(DataFlavor flavor) - throws UnsupportedFlavorException { - if (flavor.equals(TransferableBibtexEntry.ENTRY_FLAVOR)) { - return data; - } else if (flavor.equals(DataFlavor.stringFlavor)) { - try { - StringWriter sw = new StringWriter(); - BibEntryWriter bibtexEntryWriter = new BibEntryWriter( - new LatexFieldFormatter(Globals.prefs.getLatexFieldFormatterPreferences()), false); - for (BibEntry entry : data) { - bibtexEntryWriter.write(entry, sw, BibDatabaseMode.BIBTEX); - } - return sw.toString(); - } catch (IOException ex) { - JOptionPane.showMessageDialog(null, - Localization.lang("Could not paste entry as text:") + "\n" + ex.getLocalizedMessage(), - Localization.lang("Clipboard"), JOptionPane.ERROR_MESSAGE); - return ""; - } - } else { - throw new UnsupportedFlavorException(flavor); - } - } -} diff --git a/src/main/java/org/jabref/gui/bibtexkeypattern/ResolveDuplicateLabelDialog.java b/src/main/java/org/jabref/gui/bibtexkeypattern/ResolveDuplicateLabelDialog.java index e4ecee93456..61434841141 100644 --- a/src/main/java/org/jabref/gui/bibtexkeypattern/ResolveDuplicateLabelDialog.java +++ b/src/main/java/org/jabref/gui/bibtexkeypattern/ResolveDuplicateLabelDialog.java @@ -25,6 +25,7 @@ import org.jabref.gui.FXDialogService; import org.jabref.gui.PreviewPanel; import org.jabref.gui.customjfx.CustomJFXPanel; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; @@ -58,7 +59,7 @@ public ResolveDuplicateLabelDialog(BasePanel panel, String key, List e JCheckBox cb = new JCheckBox(Localization.lang("Generate BibTeX key"), !first); b.appendRows("1dlu, p"); b.add(cb).xy(1, row); - PreviewPanel previewPanel = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService()); + PreviewPanel previewPanel = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService(), ExternalFileTypes.getInstance()); previewPanel.setEntry(entry); JFXPanel container = CustomJFXPanel.wrap(new Scene(previewPanel)); container.setPreferredSize(new Dimension(800, 90)); diff --git a/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java b/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java index e4334d5598b..622dc4142cf 100644 --- a/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/EntryAddChangeViewModel.java @@ -10,6 +10,7 @@ import org.jabref.gui.FXDialogService; import org.jabref.gui.PreviewPanel; import org.jabref.gui.customjfx.CustomJFXPanel; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableInsertEntry; import org.jabref.logic.l10n.Localization; @@ -27,7 +28,7 @@ public EntryAddChangeViewModel(BibEntry diskEntry) { super(Localization.lang("Added entry")); this.diskEntry = diskEntry; - PreviewPanel previewPanel = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService()); + PreviewPanel previewPanel = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService(), ExternalFileTypes.getInstance()); previewPanel.setEntry(diskEntry); container = CustomJFXPanel.wrap(new Scene(previewPanel)); } diff --git a/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java b/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java index a36990be7eb..37511d67830 100644 --- a/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java +++ b/src/main/java/org/jabref/gui/collab/EntryDeleteChangeViewModel.java @@ -10,6 +10,7 @@ import org.jabref.gui.FXDialogService; import org.jabref.gui.PreviewPanel; import org.jabref.gui.customjfx.CustomJFXPanel; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableRemoveEntry; import org.jabref.logic.bibtex.DuplicateCheck; @@ -44,7 +45,7 @@ public EntryDeleteChangeViewModel(BibEntry memEntry, BibEntry tmpEntry) { LOGGER.debug("Modified entry: " + memEntry.getCiteKeyOptional().orElse("") + "\n Modified locally: " + isModifiedLocally); - PreviewPanel previewPanel = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService()); + PreviewPanel previewPanel = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService(), ExternalFileTypes.getInstance()); previewPanel.setEntry(memEntry); container = CustomJFXPanel.wrap(new Scene(previewPanel)); } diff --git a/src/main/java/org/jabref/gui/externalfiles/DroppedFileHandler.java b/src/main/java/org/jabref/gui/externalfiles/DroppedFileHandler.java index 934b1d48622..58bfb1e87f4 100644 --- a/src/main/java/org/jabref/gui/externalfiles/DroppedFileHandler.java +++ b/src/main/java/org/jabref/gui/externalfiles/DroppedFileHandler.java @@ -522,7 +522,7 @@ private boolean doMove(String fileName, String destFilename, NamedCompound edits private boolean doCopy(String fileName, String toFile, NamedCompound edits) { List dirs = panel.getBibDatabaseContext() - .getFileDirectories(Globals.prefs.getFileDirectoryPreferences()); + .getFileDirectories(Globals.prefs.getFileDirectoryPreferences()); int found = -1; for (int i = 0; i < dirs.size(); i++) { if (new File(dirs.get(i)).exists()) { diff --git a/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java new file mode 100644 index 00000000000..7d6e83c6921 --- /dev/null +++ b/src/main/java/org/jabref/gui/externalfiles/ExternalFilesEntryLinker.java @@ -0,0 +1,73 @@ +package org.jabref.gui.externalfiles; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import org.jabref.gui.externalfiletype.ExternalFileType; +import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.gui.externalfiletype.UnknownExternalFileType; +import org.jabref.logic.cleanup.MoveFilesCleanup; +import org.jabref.logic.cleanup.RenamePdfCleanup; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; +import org.jabref.model.metadata.FileDirectoryPreferences; + +public class ExternalFilesEntryLinker { + + private final ExternalFileTypes externalFileTypes; + private final FileDirectoryPreferences fileDirectoryPreferences; + private final BibDatabaseContext bibDatabaseContext; + private final MoveFilesCleanup moveFilesCleanup; + private final RenamePdfCleanup renameFilesCleanup; + + public ExternalFilesEntryLinker(ExternalFileTypes externalFileTypes, FileDirectoryPreferences fileDirectoryPreferences, String fileDirPattern, BibDatabaseContext bibDatabaseContext, String fileNamePattern) { + this.externalFileTypes = externalFileTypes; + this.fileDirectoryPreferences = fileDirectoryPreferences; + this.bibDatabaseContext = bibDatabaseContext; + this.moveFilesCleanup = new MoveFilesCleanup(bibDatabaseContext, fileDirPattern, fileDirectoryPreferences); + this.renameFilesCleanup = new RenamePdfCleanup(false, bibDatabaseContext, fileNamePattern, fileDirectoryPreferences); + } + + public Optional copyFileToFileDir(Path file) { + Optional firstExistingFileDir = bibDatabaseContext.getFirstExistingFileDir(fileDirectoryPreferences); + if (firstExistingFileDir.isPresent()) { + Path targetFile = firstExistingFileDir.get().resolve(file.getFileName()); + if (FileUtil.copyFile(file, targetFile, false)) { + return Optional.ofNullable(targetFile); + } + } + return Optional.empty(); + } + + public void renameLinkedFilesToPattern(BibEntry entry) { + renameFilesCleanup.cleanup(entry); + } + + public void moveLinkedFilesToFileDir(BibEntry entry) { + moveFilesCleanup.cleanup(entry); + } + + public void addFileToEntry(BibEntry entry, Path file) { + addFilesToEntry(entry, Arrays.asList(file)); + } + + public void addFilesToEntry(BibEntry entry, List files) { + + for (Path file : files) { + FileUtil.getFileExtension(file).ifPresent(ext -> { + + ExternalFileType type = externalFileTypes.getExternalFileTypeByExt(ext) + .orElse(new UnknownExternalFileType(ext)); + Path relativePath = FileUtil.shortenFileName(file, bibDatabaseContext.getFileDirectoriesAsPaths(fileDirectoryPreferences)); + LinkedFile linkedfile = new LinkedFile("", relativePath.toString(), type.getName()); + entry.addFile(linkedfile); + }); + + } + + } +} diff --git a/src/main/java/org/jabref/gui/externalfiles/NewDroppedFileHandler.java b/src/main/java/org/jabref/gui/externalfiles/NewDroppedFileHandler.java new file mode 100644 index 00000000000..58ab13155e5 --- /dev/null +++ b/src/main/java/org/jabref/gui/externalfiles/NewDroppedFileHandler.java @@ -0,0 +1,154 @@ +package org.jabref.gui.externalfiles; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import javafx.scene.control.ButtonBar.ButtonData; +import javafx.scene.control.ButtonType; +import javafx.scene.control.DialogPane; +import javafx.scene.control.TextArea; +import javafx.scene.layout.VBox; +import javafx.scene.text.Text; + +import org.jabref.Globals; +import org.jabref.gui.DialogService; +import org.jabref.gui.externalfiletype.ExternalFileTypes; +import org.jabref.logic.externalfiles.ExternalFilesContentImporter; +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.UpdateField; +import org.jabref.logic.util.UpdateFieldPreferences; +import org.jabref.logic.util.io.FileUtil; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.metadata.FileDirectoryPreferences; +import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.JabRefPreferences; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class NewDroppedFileHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(NewDroppedFileHandler.class); + + private final BibDatabaseContext bibDatabaseContext; + private final UpdateFieldPreferences updateFieldPreferences; + private final DialogService dialogService; + private final FileUpdateMonitor fileUpdateMonitor; + private final ExternalFilesEntryLinker linker; + private final ExternalFilesContentImporter contentImporter; + + public NewDroppedFileHandler(DialogService dialogService, + BibDatabaseContext bibDatabaseContext, + ExternalFileTypes externalFileTypes, + FileDirectoryPreferences fileDirectoryPreferences, + String fileDirPattern, + ImportFormatPreferences importFormatPreferences, + UpdateFieldPreferences updateFieldPreferences, + FileUpdateMonitor fileupdateMonitor, + String fileNamePattern) { + + this.dialogService = dialogService; + this.bibDatabaseContext = bibDatabaseContext; + this.updateFieldPreferences = updateFieldPreferences; + this.fileUpdateMonitor = fileupdateMonitor; + + this.linker = new ExternalFilesEntryLinker(externalFileTypes, fileDirectoryPreferences, fileDirPattern, bibDatabaseContext, fileNamePattern); + this.contentImporter = new ExternalFilesContentImporter(importFormatPreferences); + } + + public void addNewEntryFromXMPorPDFContent(BibEntry entry, List files) { + + for (Path file : files) { + + if (FileUtil.getFileExtension(file).filter(ext -> "pdf".equals(ext)).isPresent()) { + + try { + List pdfResult = contentImporter.importPDFContent(file); + //FIXME: Show merge dialog if working again properly + List xmpEntriesInFile = contentImporter.importXMPContent(file); + + //First try xmp import, if empty try pdf import, otherwise show dialog + if (xmpEntriesInFile.isEmpty()) { + if (pdfResult.isEmpty()) { + addToEntryRenameAndMoveToFileDir(entry, files); + } else { + showImportOrLinkFileDialog(pdfResult, file, entry); + } + } else { + showImportOrLinkFileDialog(xmpEntriesInFile, file, entry); + } + + } catch (IOException e) { + LOGGER.warn("Problem reading XMP", e); + } + + } else { + addToEntryRenameAndMoveToFileDir(entry, files); + } + } + } + + public void importEntriesFromDroppedBibFiles(Path bibFile) { + + List entriesToImport = contentImporter.importFromBibFile(bibFile, fileUpdateMonitor); + bibDatabaseContext.getDatabase().insertEntries(entriesToImport); + + if (Globals.prefs.getBoolean(JabRefPreferences.USE_OWNER)) { + // Set owner field to default value + UpdateField.setAutomaticFields(entriesToImport, true, true, updateFieldPreferences); + } + } + + private void showImportOrLinkFileDialog(List entriesToImport, Path fileName, BibEntry entryToLink) { + + DialogPane pane = new DialogPane(); + + VBox vbox = new VBox(); + Text text = new Text(Localization.lang("The PDF contains one or several BibTeX-records.") + + "\n" + Localization.lang("Do you want to import these as new entries into the current library or do you want to link the file to the entry?")); + vbox.getChildren().add(text); + + entriesToImport.forEach(entry -> { + TextArea textArea = new TextArea(entry.toString()); + textArea.setEditable(false); + vbox.getChildren().add(textArea); + }); + pane.setContent(vbox); + + ButtonType importButton = new ButtonType("Import into library", ButtonData.OK_DONE); + ButtonType linkToEntry = new ButtonType("Link file to entry", ButtonData.OTHER); + + Optional buttonPressed = dialogService.showCustomDialogAndWait(Localization.lang("XMP-metadata found in PDF: %0", fileName.getFileName().toString()), pane, importButton, linkToEntry, ButtonType.CANCEL); + + if (buttonPressed.equals(Optional.of(importButton))) { + bibDatabaseContext.getDatabase().insertEntries(entriesToImport); + } + if (buttonPressed.equals(Optional.of(linkToEntry))) { + addToEntryRenameAndMoveToFileDir(entryToLink, Arrays.asList(fileName)); + } + } + + public void addToEntryRenameAndMoveToFileDir(BibEntry entry, List files) { + linker.addFilesToEntry(entry, files); + linker.moveLinkedFilesToFileDir(entry); + linker.renameLinkedFilesToPattern(entry); + } + + public void copyFilesToFileDirAndAddToEntry(BibEntry entry, List files) { + for (Path file : files) { + linker.copyFileToFileDir(file).ifPresent(copiedFile -> { + linker.addFilesToEntry(entry, files); + }); + } + } + + public void addToEntry(BibEntry entry, List files) { + linker.addFilesToEntry(entry, files); + } + +} diff --git a/src/main/java/org/jabref/gui/fieldeditors/FileListEditorTransferHandler.java b/src/main/java/org/jabref/gui/fieldeditors/FileListEditorTransferHandler.java deleted file mode 100644 index eb798b16f71..00000000000 --- a/src/main/java/org/jabref/gui/fieldeditors/FileListEditorTransferHandler.java +++ /dev/null @@ -1,161 +0,0 @@ -package org.jabref.gui.fieldeditors; - -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.awt.dnd.DnDConstants; -import java.io.File; -import java.io.IOException; -import java.net.URL; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import javax.swing.JComponent; -import javax.swing.SwingUtilities; -import javax.swing.TransferHandler; - -import org.jabref.gui.EntryContainer; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.externalfiles.DroppedFileHandler; -import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.groups.EntryTableTransferHandler; -import org.jabref.model.util.FileHelper; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -class FileListEditorTransferHandler extends TransferHandler { - - private static final Logger LOGGER = LoggerFactory.getLogger(FileListEditorTransferHandler.class); - - private final DataFlavor URL_FLAVOR; - private final JabRefFrame frame; - private final EntryContainer entryContainer; - private final TransferHandler textTransferHandler; - - private DroppedFileHandler droppedFileHandler; - - - /** - * @param textTransferHandler is an instance of javax.swing.plaf.basic.BasicTextUI.TextTransferHandler. That class - * is not visible. Therefore, we have to "cheat" - */ - public FileListEditorTransferHandler(JabRefFrame frame, EntryContainer entryContainer, - TransferHandler textTransferHandler) { - this.frame = frame; - this.entryContainer = entryContainer; - this.textTransferHandler = textTransferHandler; - URL_FLAVOR = getUrlFlavor(); - } - - private DataFlavor getUrlFlavor() { - DataFlavor urlFlavor; - try { - urlFlavor = new DataFlavor("application/x-java-url; class=java.net.URL"); - } catch (ClassNotFoundException e) { - LOGGER.info("Unable to configure drag and drop for file link table", e); - urlFlavor = null; - } - return urlFlavor; - } - - /** - * Overridden to indicate which types of drags are supported (only LINK + COPY). COPY is supported as no support - * disables CTRL+C (copy of text) - */ - @Override - public int getSourceActions(JComponent c) { - return DnDConstants.ACTION_LINK | DnDConstants.ACTION_COPY; - } - - @Override - public void exportToClipboard(JComponent comp, Clipboard clip, int action) { - if (this.textTransferHandler != null) { - this.textTransferHandler.exportToClipboard(comp, clip, action); - } - } - - @Override - public boolean importData(JComponent comp, Transferable transferable) { - // If the drop target is the main table, we want to record which - // row the item was dropped on, to identify the entry if needed: - - try { - List files = new ArrayList<>(); - // This flavor is used for dragged file links in Windows: - if (transferable.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { - // javaFileListFlavor returns a list of java.io.File (as the string *File* in File indicates) and not a list of java.nio.file - // There is no DataFlavor.javaPathListFlavor, so we have to deal with java.io.File - @SuppressWarnings("unchecked") - List transferedFiles = (List) transferable.getTransferData(DataFlavor.javaFileListFlavor); - files.addAll(transferedFiles.stream().map(File::toPath).collect(Collectors.toList())); - } else if (transferable.isDataFlavorSupported(URL_FLAVOR)) { - URL dropLink = (URL) transferable.getTransferData(URL_FLAVOR); - LOGGER.warn("Dropped URL, which is currently not implemented " + dropLink); - } else if (transferable.isDataFlavorSupported(DataFlavor.stringFlavor)) { - // This is used when one or more files are pasted from the file manager - // under Gnome. The data consists of the file paths, one file per line: - String dropStr = (String) transferable.getTransferData(DataFlavor.stringFlavor); - files.addAll(EntryTableTransferHandler.getFilesFromDraggedFilesString(dropStr)); - } else { - LOGGER.warn("Dropped something, which we currently cannot handle"); - } - - SwingUtilities.invokeLater(() -> { - for (Path file : files) { - // Find the file's extension, if any: - String name = file.toAbsolutePath().toString(); - FileHelper.getFileExtension(name).ifPresent(extension -> ExternalFileTypes.getInstance() - .getExternalFileTypeByExt(extension).ifPresent(fileType -> { - if (droppedFileHandler == null) { - droppedFileHandler = new DroppedFileHandler(frame, frame.getCurrentBasePanel()); - } - droppedFileHandler.handleDroppedfile(name, fileType, entryContainer.getEntry()); - })); - } - }); - if (!files.isEmpty()) { - // Found some files, return - return true; - } - } catch (IOException ioe) { - LOGGER.warn("Failed to read dropped data. ", ioe); - } catch (UnsupportedFlavorException | ClassCastException ufe) { - LOGGER.warn("Drop type error. ", ufe); - } - - // all supported flavors failed - // log the flavors to support debugging - LOGGER.warn("Cannot transfer input: " + dataFlavorsToString(transferable.getTransferDataFlavors())); - return false; - } - - private String dataFlavorsToString(DataFlavor[] transferFlavors) { - return Arrays.stream(transferFlavors) - .map(dataFlavor -> dataFlavor.toString()) - .collect(Collectors.joining(" ")); - } - - /** - * This method is called to query whether the transfer can be imported. - * - * @return true for urls, strings, javaFileLists, false otherwise - */ - @Override - public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { - // accept this if any input flavor matches any of our supported flavors - for (DataFlavor inflav : transferFlavors) { - if (inflav.match(URL_FLAVOR) || inflav.match(DataFlavor.stringFlavor) || inflav.match(DataFlavor.javaFileListFlavor)) { - return true; - } - } - - // nope, never heard of this type - LOGGER.debug("Unknown data transfer flavor: " + dataFlavorsToString(transferFlavors)); - return false; - } -} diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index d72f99ae657..8c4a94d3517 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -146,6 +146,7 @@ private void handleOnDragDropped(LinkedFileViewModel originalItem, DragEvent eve if (dragboard.hasFiles()) { List linkedFiles = dragboard.getFiles().stream().map(File::toPath).map(viewModel::fromFile).collect(Collectors.toList()); items.addAll(linkedFiles); + success = true; } event.setDropCompleted(success); event.consume(); diff --git a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java b/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java deleted file mode 100644 index 70c0bfaec3d..00000000000 --- a/src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java +++ /dev/null @@ -1,385 +0,0 @@ -package org.jabref.gui.groups; - -import java.awt.datatransfer.Clipboard; -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.awt.dnd.DnDConstants; -import java.awt.event.InputEvent; -import java.awt.event.MouseEvent; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import javax.swing.JComponent; -import javax.swing.JTable; -import javax.swing.TransferHandler; - -import org.jabref.JabRefExecutorService; -import org.jabref.gui.BasePanel; -import org.jabref.gui.JabRefFrame; -import org.jabref.gui.externalfiles.DroppedFileHandler; -import org.jabref.gui.externalfiles.TransferableFileLinkSelection; -import org.jabref.gui.externalfiletype.ExternalFileType; -import org.jabref.gui.externalfiletype.ExternalFileTypes; -import org.jabref.gui.importer.ImportAction; -import org.jabref.gui.importer.actions.OpenDatabaseAction; -import org.jabref.gui.maintable.MainTable; -import org.jabref.logic.net.URLDownload; -import org.jabref.model.util.FileHelper; -import org.jabref.pdfimport.PdfImporter; -import org.jabref.pdfimport.PdfImporter.ImportPdfFilesResult; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class EntryTableTransferHandler extends TransferHandler { - - private static final boolean DROP_ALLOWED = true; - private static final Logger LOGGER = LoggerFactory.getLogger(EntryTableTransferHandler.class); - private final MainTable entryTable; - private final JabRefFrame frame; - private final BasePanel panel; - private DataFlavor urlFlavor; - private final DataFlavor stringFlavor; - private boolean draggingFile; - - /** - * Construct the transfer handler. - * - * @param entryTable The table this transfer handler should operate on. This argument is allowed to equal null, in - * which case the transfer handler can assume that it works for a JabRef instance with no databases open, - * attached to the empty tabbed pane. - * @param frame The JabRefFrame instance. - * @param panel The BasePanel this transferhandler works for. - */ - public EntryTableTransferHandler(MainTable entryTable, JabRefFrame frame, BasePanel panel) { - this.entryTable = entryTable; - this.frame = frame; - this.panel = panel; - stringFlavor = DataFlavor.stringFlavor; - try { - urlFlavor = new DataFlavor("application/x-java-url; class=java.net.URL"); - } catch (ClassNotFoundException e) { - LOGGER.info("Unable to configure drag and drop for main table", e); - } - } - - /** - * Overridden to indicate which types of drags are supported (only LINK). - */ - @Override - public int getSourceActions(JComponent c) { - return DnDConstants.ACTION_LINK; - } - - /** - * This method is called when dragging stuff *from* the table. - */ - @Override - public Transferable createTransferable(JComponent c) { - if (draggingFile) { - draggingFile = false; - return new TransferableFileLinkSelection(panel, entryTable.getSelectedEntries());//.getTransferable(); - } else { - /* so we can assume it will never be called if entryTable==null: */ - return new TransferableEntrySelection(entryTable.getSelectedEntries()); - } - } - - /** - * This method is called when stuff is drag to the component. - * - * Imports the dropped URL or plain text as a new entry in the current library. - * - */ - @Override - public boolean importData(JComponent comp, Transferable t) { - - // If the drop target is the main table, we want to record which - // row the item was dropped on, to identify the entry if needed: - int dropRow = -1; - if (comp instanceof JTable) { - dropRow = ((JTable) comp).getSelectedRow(); - } - - try { - - // This flavor is used for dragged file links in Windows: - if (t.isDataFlavorSupported(DataFlavor.javaFileListFlavor)) { - // JOptionPane.showMessageDialog(null, "Received - // javaFileListFlavor"); - @SuppressWarnings("unchecked") - List files = ((List) t.getTransferData(DataFlavor.javaFileListFlavor)).stream() - .map(File::toPath).collect(Collectors.toList()); - return handleDraggedFiles(files, dropRow); - } else if (t.isDataFlavorSupported(urlFlavor)) { - URL dropLink = (URL) t.getTransferData(urlFlavor); - return handleDropTransfer(dropLink); - } else if (t.isDataFlavorSupported(stringFlavor)) { - String dropStr = (String) t.getTransferData(stringFlavor); - LOGGER.debug("Received stringFlavor: " + dropStr); - return handleDropTransfer(dropStr, dropRow); - } - } catch (IOException ioe) { - LOGGER.error("Failed to read dropped data", ioe); - } catch (UnsupportedFlavorException | ClassCastException ufe) { - LOGGER.error("Drop type error", ufe); - } - - // all supported flavors failed - LOGGER.info("Can't transfer input: "); - DataFlavor[] inflavs = t.getTransferDataFlavors(); - for (DataFlavor inflav : inflavs) { - LOGGER.info(" " + inflav); - } - - return false; - } - - /** - * This method is called to query whether the transfer can be imported. - * - * Will return true for urls, strings, javaFileLists - */ - @Override - public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) { - if (!EntryTableTransferHandler.DROP_ALLOWED) { - return false; - } - - // accept this if any input flavor matches any of our supported flavors - for (DataFlavor inflav : transferFlavors) { - if (inflav.match(urlFlavor) || inflav.match(stringFlavor) || inflav.match(DataFlavor.javaFileListFlavor)) { - return true; - } - } - - // System.out.println("drop type forbidden"); - // nope, never heard of this type - return false; - } - - @Override - public void exportAsDrag(JComponent comp, InputEvent e, int action) { - if (e instanceof MouseEvent) { - // TODO: Reimplement drag & drop - /* - int columnIndex = entryTable.columnAtPoint(((MouseEvent) e).getPoint()); - int modelIndex = entryTable.getColumnModel().getColumn(columnIndex).getModelIndex(); - if (entryTable.isFileColumn(modelIndex)) { - LOGGER.info("Dragging file"); - draggingFile = true; - } - */ - } - super.exportAsDrag(comp, e, DnDConstants.ACTION_LINK); - } - - @Override - protected void exportDone(JComponent source, Transferable data, int action) { - // default implementation is OK - super.exportDone(source, data, action); - } - - @Override - public void exportToClipboard(JComponent comp, Clipboard clip, int action) { - // default implementation is OK - super.exportToClipboard(comp, clip, action); - } - - // add-ons ----------------------- - - private boolean handleDropTransfer(String dropStr, final int dropRow) throws IOException { - if (dropStr.startsWith("file:")) { - // This appears to be a dragged file link and not a reference - // format. Check if we can map this to a set of files: - if (handleDraggedFilenames(dropStr, dropRow)) { - return true; - // If not, handle it in the normal way... - } - } else if (dropStr.startsWith("http:")) { - // This is the way URL links are received on OS X and KDE (Gnome?): - URL url = new URL(dropStr); - // JOptionPane.showMessageDialog(null, "Making URL: - // "+url.toString()); - return handleDropTransfer(url); - } - File tmpfile = File.createTempFile("jabrefimport", ""); - tmpfile.deleteOnExit(); - try (FileWriter fw = new FileWriter(tmpfile)) { - fw.write(dropStr); - } - - // System.out.println("importing from " + tmpfile.getAbsolutePath()); - - ImportAction importer = new ImportAction(frame, false); - importer.automatedImport(Collections.singletonList(tmpfile.getAbsolutePath())); - - return true; - } - - /** - * Translate a String describing a set of files or URLs dragged into JabRef into a List of File objects, taking care - * of URL special characters. - * - * @param s String describing a set of files or URLs dragged into JabRef - * @return a List containing the individual file objects. - * - */ - public static List getFilesFromDraggedFilesString(String s) { - // Split into lines: - String[] lines = s.replace("\r", "").split("\n"); - List files = new ArrayList<>(); - for (String line1 : lines) { - String line = line1; - - // Try to use url.toURI() to translate URL specific sequences like %20 into - // standard characters: - File fl = null; - try { - URL url = new URL(line); - fl = new File(url.toURI()); - } catch (MalformedURLException | URISyntaxException e) { - LOGGER.warn("Could not get file", e); - } - - // Unless an exception was thrown, we should have the sanitized path: - if (fl != null) { - line = fl.getPath(); - } else if (line.startsWith("file:")) { - line = line.substring(5); - } else { - continue; - } - // Under Gnome, the link is given as file:///...., so we - // need to strip the extra slashes: - if (line.startsWith("//")) { - line = line.substring(2); - } - - File f = new File(line); - if (f.exists()) { - files.add(f.toPath()); - } - } - return files; - } - - /** - * Handle a String describing a set of files or URLs dragged into JabRef. - * - * @param s String describing a set of files or URLs dragged into JabRef - * @param dropRow The row in the table where the files were dragged. - * @return success status for the operation - * - */ - private boolean handleDraggedFilenames(String s, final int dropRow) { - - return handleDraggedFiles(EntryTableTransferHandler.getFilesFromDraggedFilesString(s), dropRow); - - } - - /** - * Handle a List containing File objects for a set of files to import. - * - * @param files A List containing File instances pointing to files. - * @param dropRow @param dropRow The row in the table where the files were dragged. - * @return success status for the operation - */ - private boolean handleDraggedFiles(List files, final int dropRow) { - final List fileNames = new ArrayList<>(); - for (Path file : files) { - fileNames.add(file.toAbsolutePath().toString()); - } - // Try to load BIB files normally, and import the rest into the current - // database. - // This process must be spun off into a background thread: - JabRefExecutorService.INSTANCE.execute(() -> { - final ImportPdfFilesResult importRes = new PdfImporter(frame, panel, entryTable, dropRow) - .importPdfFiles(fileNames); - if (!importRes.getNoPdfFiles().isEmpty()) { - loadOrImportFiles(importRes.getNoPdfFiles(), dropRow); - } - }); - - return true; - } - - /** - * Take a set of filenames. Those with names indicating BIB files are opened as such if possible. All other files we - * will attempt to import into the current library. - * - * @param fileNames The names of the files to open. - * @param dropRow success status for the operation - */ - private void loadOrImportFiles(List fileNames, int dropRow) { - - OpenDatabaseAction openAction = new OpenDatabaseAction(frame); - List notBibFiles = new ArrayList<>(); - List bibFiles = new ArrayList<>(); - for (String fileName : fileNames) { - // Find the file's extension, if any: - Optional extension = FileHelper.getFileExtension(fileName); - Optional fileType; - - if (extension.isPresent() && "bib".equals(extension.get())) { - // we assume that it is a BibTeX file. - // When a user wants to import something with file extension "bib", but which is not a BibTeX file, he should use "file -> import" - bibFiles.add(fileName); - continue; - } - - fileType = ExternalFileTypes.getInstance().getExternalFileTypeByExt(extension.orElse("")); - /* - * This is a linkable file. If the user dropped it on an entry, we - * should offer options for autolinking to this files: - * - * TODO we should offer an option to highlight the row the user is on too. - */ - if ((fileType.isPresent()) && (dropRow >= 0)) { - - /* - * TODO: make this an instance variable? - */ - DroppedFileHandler dfh = new DroppedFileHandler(frame, panel); - dfh.handleDroppedfile(fileName, fileType.get(), entryTable, dropRow); - - continue; - } - notBibFiles.add(fileName); - } - - openAction.openFilesAsStringList(bibFiles, true); - - if (!notBibFiles.isEmpty()) { - // Import into new if entryTable==null, otherwise into current - // database: - ImportAction importer = new ImportAction(frame, entryTable == null); - importer.automatedImport(notBibFiles); - } - } - - private boolean handleDropTransfer(URL dropLink) throws IOException { - File tmpfile = File.createTempFile("jabrefimport", ""); - tmpfile.deleteOnExit(); - - new URLDownload(dropLink).toFile(tmpfile.toPath()); - - // Import into new if entryTable==null, otherwise into current library: - ImportAction importer = new ImportAction(frame, entryTable == null); - importer.automatedImport(Collections.singletonList(tmpfile.getAbsolutePath())); - - return true; - } - -} diff --git a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java index 951b5339f41..2699df22cb6 100644 --- a/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java @@ -22,6 +22,7 @@ import org.jabref.gui.icon.JabRefIcon; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.BindingsHelper; +import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.groups.DefaultGroupsFactory; import org.jabref.logic.layout.format.LatexToUnicodeFormatter; @@ -54,12 +55,14 @@ public class GroupNodeViewModel { private final BooleanBinding anySelectedEntriesMatched; private final BooleanBinding allSelectedEntriesMatched; private final TaskExecutor taskExecutor; + private final CustomLocalDragboard localDragBoard; - public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, GroupTreeNode groupNode) { + public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, GroupTreeNode groupNode, CustomLocalDragboard localDragBoard) { this.databaseContext = Objects.requireNonNull(databaseContext); this.taskExecutor = Objects.requireNonNull(taskExecutor); this.stateManager = Objects.requireNonNull(stateManager); this.groupNode = Objects.requireNonNull(groupNode); + this.localDragBoard = Objects.requireNonNull(localDragBoard); LatexToUnicodeFormatter formatter = new LatexToUnicodeFormatter(); displayName = formatter.format(groupNode.getName()); @@ -89,16 +92,16 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state allSelectedEntriesMatched = BindingsHelper.all(selectedEntriesMatchStatus, matched -> matched); } - public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, AbstractGroup group) { - this(databaseContext, stateManager, taskExecutor, new GroupTreeNode(group)); + public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, TaskExecutor taskExecutor, AbstractGroup group, CustomLocalDragboard localDragboard) { + this(databaseContext, stateManager, taskExecutor, new GroupTreeNode(group), localDragboard); } - static GroupNodeViewModel getAllEntriesGroup(BibDatabaseContext newDatabase, StateManager stateManager, TaskExecutor taskExecutor) { - return new GroupNodeViewModel(newDatabase, stateManager, taskExecutor, DefaultGroupsFactory.getAllEntriesGroup()); + static GroupNodeViewModel getAllEntriesGroup(BibDatabaseContext newDatabase, StateManager stateManager, TaskExecutor taskExecutor, CustomLocalDragboard localDragBoard) { + return new GroupNodeViewModel(newDatabase, stateManager, taskExecutor, DefaultGroupsFactory.getAllEntriesGroup(), localDragBoard); } private GroupNodeViewModel toViewModel(GroupTreeNode child) { - return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, child); + return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, child, localDragBoard); } public List addEntriesToGroup(List entries) { @@ -259,7 +262,7 @@ public Optional getChildByPath(String pathToSource) { public boolean acceptableDrop(Dragboard dragboard) { // TODO: we should also check isNodeDescendant boolean canDropOtherGroup = dragboard.hasContent(DragAndDropDataFormats.GROUP); - boolean canDropEntries = dragboard.hasContent(DragAndDropDataFormats.ENTRIES) + boolean canDropEntries = localDragBoard.hasType(DragAndDropDataFormats.BIBENTRY_LIST_CLASS) && (groupNode.getGroup() instanceof GroupEntryChanger); return canDropOtherGroup || canDropEntries; } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeView.java b/src/main/java/org/jabref/gui/groups/GroupTreeView.java index 2c4f1e2d8b9..4e4aee8051e 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeView.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeView.java @@ -36,12 +36,15 @@ import org.jabref.gui.DialogService; import org.jabref.gui.DragAndDropDataFormats; +import org.jabref.gui.GUIGlobals; import org.jabref.gui.StateManager; import org.jabref.gui.util.BindingsHelper; +import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.RecursiveTreeItem; import org.jabref.gui.util.TaskExecutor; import org.jabref.gui.util.ViewModelTreeTableCellFactory; import org.jabref.logic.l10n.Localization; +import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.AllEntriesGroup; import org.controlsfx.control.textfield.CustomTextField; @@ -66,6 +69,7 @@ public class GroupTreeView { @Inject private DialogService dialogService; @Inject private TaskExecutor taskExecutor; private GroupTreeViewModel viewModel; + private CustomLocalDragboard localDragboard; private static void removePseudoClasses(TreeTableRow row, PseudoClass... pseudoClasses) { for (PseudoClass pseudoClass : pseudoClasses) { @@ -75,7 +79,8 @@ private static void removePseudoClasses(TreeTableRow row, Ps @FXML public void initialize() { - viewModel = new GroupTreeViewModel(stateManager, dialogService, taskExecutor); + this.localDragboard = GUIGlobals.localDragboard; + viewModel = new GroupTreeViewModel(stateManager, dialogService, taskExecutor, localDragboard); // Set-up groups tree groupTree.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); @@ -207,9 +212,12 @@ public void initialize() { }); row.setOnDragOver(event -> { Dragboard dragboard = event.getDragboard(); - if ((event.getGestureSource() != row) && row.getItem().acceptableDrop(dragboard)) { + if ((event.getGestureSource() != row) && (row.getItem() != null) && row.getItem().acceptableDrop(dragboard)) { event.acceptTransferModes(TransferMode.MOVE, TransferMode.LINK); + //expand node and all children on drag over + row.getTreeItem().setExpanded(true); + removePseudoClasses(row, dragOverBottom, dragOverCenter, dragOverTop); switch (getDroppingMouseLocation(row, event)) { case BOTTOM: @@ -241,11 +249,10 @@ public void initialize() { success = true; } } - if (dragboard.hasContent(DragAndDropDataFormats.ENTRIES)) { - TransferableEntrySelection entrySelection = (TransferableEntrySelection) dragboard - .getContent(DragAndDropDataFormats.ENTRIES); - row.getItem().addEntriesToGroup(entrySelection.getSelection()); + if (localDragboard.hasType(DragAndDropDataFormats.BIBENTRY_LIST_CLASS)) { + List entries = localDragboard.getBibEntries(); + row.getItem().addEntriesToGroup(entries); success = true; } event.setDropCompleted(success); @@ -260,12 +267,12 @@ public void initialize() { } private void updateSelection(List> newSelectedGroups) { - if (newSelectedGroups == null || newSelectedGroups.isEmpty()) { + if ((newSelectedGroups == null) || newSelectedGroups.isEmpty()) { viewModel.selectedGroupsProperty().clear(); } else { List list = new ArrayList<>(); for (TreeItem model : newSelectedGroups) { - if (model != null && model.getValue() != null && !(model.getValue().getGroupNode().getGroup() instanceof AllEntriesGroup)) { + if ((model != null) && (model.getValue() != null) && !(model.getValue().getGroupNode().getGroup() instanceof AllEntriesGroup)) { list.add(model.getValue()); } } diff --git a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java index 14874771dd8..5ff2106d2ed 100644 --- a/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java +++ b/src/main/java/org/jabref/gui/groups/GroupTreeViewModel.java @@ -23,6 +23,7 @@ import org.jabref.gui.AbstractViewModel; import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; +import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.l10n.Localization; import org.jabref.model.database.BibDatabaseContext; @@ -41,6 +42,7 @@ public class GroupTreeViewModel extends AbstractViewModel { private final StateManager stateManager; private final DialogService dialogService; private final TaskExecutor taskExecutor; + private final CustomLocalDragboard localDragboard; private final ObjectProperty> filterPredicate = new SimpleObjectProperty<>(); private final StringProperty filterText = new SimpleStringProperty(); private final Comparator compAlphabetIgnoreCase = (GroupTreeNode v1, GroupTreeNode v2) -> v1 @@ -48,11 +50,11 @@ public class GroupTreeViewModel extends AbstractViewModel { .compareToIgnoreCase(v2.getName()); private Optional currentDatabase; - public GroupTreeViewModel(StateManager stateManager, DialogService dialogService, TaskExecutor taskExecutor) { + public GroupTreeViewModel(StateManager stateManager, DialogService dialogService, TaskExecutor taskExecutor, CustomLocalDragboard localDragboard) { this.stateManager = Objects.requireNonNull(stateManager); this.dialogService = Objects.requireNonNull(dialogService); this.taskExecutor = Objects.requireNonNull(taskExecutor); - + this.localDragboard = Objects.requireNonNull(localDragboard); // Register listener EasyBind.subscribe(stateManager.activeDatabaseProperty(), this::onActiveDatabaseChanged); EasyBind.subscribe(selectedGroups, this::onSelectedGroupChanged); @@ -92,7 +94,7 @@ private void onSelectedGroupChanged(ObservableList newValue) } currentDatabase.ifPresent(database -> { - if (newValue == null || newValue.isEmpty()) { + if ((newValue == null) || newValue.isEmpty()) { stateManager.clearSelectedGroups(database); } else { stateManager.setSelectedGroups(database, newValue.stream().map(GroupNodeViewModel::getGroupNode).collect(Collectors.toList())); @@ -116,16 +118,16 @@ private void onActiveDatabaseChanged(Optional newDatabase) { GroupNodeViewModel newRoot = newDatabase .map(BibDatabaseContext::getMetaData) .flatMap(MetaData::getGroups) - .map(root -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, root)) - .orElse(GroupNodeViewModel.getAllEntriesGroup(newDatabase.get(), stateManager, taskExecutor)); + .map(root -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, root, localDragboard)) + .orElse(GroupNodeViewModel.getAllEntriesGroup(newDatabase.get(), stateManager, taskExecutor, localDragboard)); rootGroup.setValue(newRoot); this.selectedGroups.setAll( stateManager.getSelectedGroup(newDatabase.get()).stream() - .map(selectedGroup -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, selectedGroup)) + .map(selectedGroup -> new GroupNodeViewModel(newDatabase.get(), stateManager, taskExecutor, selectedGroup, localDragboard)) .collect(Collectors.toList())); } else { - rootGroup.setValue(GroupNodeViewModel.getAllEntriesGroup(new BibDatabaseContext(), stateManager, taskExecutor)); + rootGroup.setValue(GroupNodeViewModel.getAllEntriesGroup(new BibDatabaseContext(), stateManager, taskExecutor, localDragboard)); } currentDatabase = newDatabase; diff --git a/src/main/java/org/jabref/gui/groups/TransferableEntrySelection.java b/src/main/java/org/jabref/gui/groups/TransferableEntrySelection.java deleted file mode 100644 index 84b5fee8b5d..00000000000 --- a/src/main/java/org/jabref/gui/groups/TransferableEntrySelection.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.jabref.gui.groups; - -import java.awt.datatransfer.DataFlavor; -import java.awt.datatransfer.Transferable; -import java.awt.datatransfer.UnsupportedFlavorException; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Optional; -import java.util.stream.Collectors; - -import org.jabref.model.entry.BibEntry; - -public class TransferableEntrySelection implements Transferable { - - public static final DataFlavor FLAVOR_INTERNAL; - private static final DataFlavor FLAVOR_EXTERNAL; - private static final DataFlavor[] FLAVORS; - - static { - DataFlavor df1 = null; - DataFlavor df2 = null; - try { - df1 = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType - + ";class=org.jabref.gui.groups.TransferableEntrySelection"); - df2 = DataFlavor.getTextPlainUnicodeFlavor(); - } catch (ClassNotFoundException e) { - // never happens - } - FLAVOR_INTERNAL = df1; - FLAVOR_EXTERNAL = df2; - FLAVORS = new DataFlavor[] {TransferableEntrySelection.FLAVOR_INTERNAL, - TransferableEntrySelection.FLAVOR_EXTERNAL}; - } - - private final List selectedEntries; - private final String selectedEntriesCiteKeys; - private boolean includeCiteKeyword; - - public TransferableEntrySelection(List list) { - this.selectedEntries = list; - selectedEntriesCiteKeys = String.join(",", - this.selectedEntries.stream().map(BibEntry::getCiteKeyOptional).filter(Optional::isPresent) - .map(Optional::get).collect(Collectors.toList())); - } - - @Override - public DataFlavor[] getTransferDataFlavors() { - return TransferableEntrySelection.FLAVORS; - } - - @Override - public boolean isDataFlavorSupported(DataFlavor someFlavor) { - return someFlavor.equals(TransferableEntrySelection.FLAVOR_INTERNAL) - || someFlavor.equals(TransferableEntrySelection.FLAVOR_EXTERNAL); - } - - @Override - public Object getTransferData(DataFlavor someFlavor) - throws UnsupportedFlavorException, IOException { - - String s = includeCiteKeyword ? "\\cite{" + selectedEntriesCiteKeys + "}" : selectedEntriesCiteKeys; - - if (someFlavor.equals(TransferableEntrySelection.FLAVOR_INTERNAL)) { - return this; - } - - else if (someFlavor.equals(DataFlavor.getTextPlainUnicodeFlavor())) { - - String charsetName = TransferableEntrySelection.FLAVOR_EXTERNAL.getParameter("charset"); - if (charsetName == null) { - charsetName = ""; - } - Charset charset = Charset.forName(charsetName.trim()); - return new ByteArrayInputStream(s.getBytes(charset)); - } - - //The text/plain DataFormat of javafx uses the String.class directly as representative class and no longer an InputStream - return s; - } - - public List getSelection() { - return selectedEntries; - } - - public void setIncludeCiteKeyword(boolean includeCiteKeyword) { - this.includeCiteKeyword = includeCiteKeyword; - } - -} diff --git a/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java b/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java index 84fcef65d67..ae3dc7c5b4b 100644 --- a/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java +++ b/src/main/java/org/jabref/gui/importer/ImportInspectionDialog.java @@ -198,7 +198,7 @@ public ImportInspectionDialog(JabRefFrame frame, BasePanel panel, String undoNam this.undoName = undoName; this.newDatabase = newDatabase; setIconImages(IconTheme.getLogoSet()); - preview = DefaultTaskExecutor.runInJavaFXThread(() -> new PreviewPanel(panel, bibDatabaseContext, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), frame.getDialogService())); + preview = DefaultTaskExecutor.runInJavaFXThread(() -> new PreviewPanel(panel, bibDatabaseContext, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), frame.getDialogService(), ExternalFileTypes.getInstance())); duplLabel.setToolTipText(Localization.lang("Possible duplicate of existing entry. Click to resolve.")); diff --git a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java index a4275922bdf..47aeec972f0 100644 --- a/src/main/java/org/jabref/gui/keyboard/KeyBinding.java +++ b/src/main/java/org/jabref/gui/keyboard/KeyBinding.java @@ -86,7 +86,8 @@ public enum KeyBinding { UNABBREVIATE("Unabbreviate", Localization.lang("Unabbreviate"), "ctrl+alt+shift+A", KeyBindingCategory.TOOLS), UNDO("Undo", Localization.lang("Undo"), "ctrl+Z", KeyBindingCategory.EDIT), WEB_SEARCH("Web search", Localization.lang("Web search"), "alt+4", KeyBindingCategory.SEARCH), - WRITE_XMP("Write XMP", Localization.lang("Write XMP"), "F6", KeyBindingCategory.TOOLS); + WRITE_XMP("Write XMP", Localization.lang("Write XMP"), "F6", KeyBindingCategory.TOOLS), + CLEAR_SEARCH("Clear search", Localization.lang("Clear search"), "ESCAPE", KeyBindingCategory.SEARCH); private final String constant; private final String localization; diff --git a/src/main/java/org/jabref/gui/maintable/MainTable.java b/src/main/java/org/jabref/gui/maintable/MainTable.java index 663a046ba4e..04f4aa435b1 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTable.java +++ b/src/main/java/org/jabref/gui/maintable/MainTable.java @@ -1,6 +1,8 @@ package org.jabref.gui.maintable; +import java.io.File; import java.io.IOException; +import java.nio.file.Path; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -13,22 +15,32 @@ import javafx.scene.control.SelectionMode; import javafx.scene.control.TableRow; import javafx.scene.control.TableView; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.DataFormat; +import javafx.scene.input.DragEvent; +import javafx.scene.input.Dragboard; import javafx.scene.input.KeyCode; import javafx.scene.input.KeyEvent; import javafx.scene.input.MouseDragEvent; import javafx.scene.input.MouseEvent; +import javafx.scene.input.TransferMode; import org.jabref.Globals; import org.jabref.gui.BasePanel; +import org.jabref.gui.DragAndDropDataFormats; +import org.jabref.gui.GUIGlobals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.externalfiles.NewDroppedFileHandler; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableInsertEntry; +import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.ViewModelTableRowFactory; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.UpdateField; +import org.jabref.logic.util.io.FileUtil; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; import org.jabref.preferences.JabRefPreferences; @@ -47,32 +59,49 @@ public class MainTable extends TableView { private final UndoManager undoManager; private final MainTableDataModel model; + private final NewDroppedFileHandler fileHandler; + private final CustomLocalDragboard localDragboard = GUIGlobals.localDragboard; + public MainTable(MainTableDataModel model, JabRefFrame frame, - BasePanel panel, BibDatabaseContext database, MainTablePreferences preferences, ExternalFileTypes externalFileTypes, KeyBindingRepository keyBindingRepository) { + BasePanel panel, BibDatabaseContext database, + MainTablePreferences preferences, ExternalFileTypes externalFileTypes, KeyBindingRepository keyBindingRepository) { super(); + this.model = model; this.database = Objects.requireNonNull(database); this.undoManager = panel.getUndoManager(); + fileHandler = new NewDroppedFileHandler(frame.getDialogService(), database, externalFileTypes, + Globals.prefs.getFileDirectoryPreferences(), + Globals.prefs.getCleanupPreferences(Globals.journalAbbreviationLoader).getFileDirPattern(), + Globals.prefs.getImportFormatPreferences(), + Globals.prefs.getUpdateFieldPreferences(), + Globals.getFileUpdateMonitor(), + Globals.prefs.get(JabRefPreferences.IMPORT_FILENAMEPATTERN) + + ); + this.getColumns().addAll(new MainTableColumnFactory(database, preferences.getColumnPreferences(), externalFileTypes, panel.getUndoManager(), frame.getDialogService()).createColumns()); new ViewModelTableRowFactory() - .withOnMouseClickedEvent((entry, event) -> { - if (event.getClickCount() == 2) { - panel.showAndEdit(entry.getEntry()); - } - }) - .withContextMenu(entry -> RightClickMenu.create(entry, keyBindingRepository, panel, Globals.getKeyPrefs(), frame.getDialogService())) - .setOnDragDetected(this::handleOnDragDetected) - .setOnMouseDragEntered(this::handleOnDragEntered) - .install(this); + .withOnMouseClickedEvent((entry, event) -> { + if (event.getClickCount() == 2) { + panel.showAndEdit(entry.getEntry()); + } + }) + .withContextMenu(entry -> RightClickMenu.create(entry, keyBindingRepository, panel, Globals.getKeyPrefs(), frame.getDialogService())) + .setOnDragDetected(this::handleOnDragDetected) + .setOnDragDropped(this::handleOnDragDropped) + .setOnDragOver(this::handleOnDragOver) + .setOnMouseDragEntered(this::handleOnDragEntered) + .install(this); if (preferences.resizeColumnsToFit()) { this.setColumnResizePolicy(new SmartConstrainedResizePolicy()); } this.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); - this.setItems(model.getEntriesFiltered()); + this.setItems(model.getEntriesFilteredAndSorted()); // Enable sorting model.bindComparator(this.comparatorProperty()); @@ -101,12 +130,11 @@ public MainTable(MainTableDataModel model, JabRefFrame frame, } public void clearAndSelect(BibEntry bibEntry) { - findEntry(bibEntry) - .ifPresent(entry -> { - getSelectionModel().clearSelection(); - getSelectionModel().select(entry); - scrollTo(entry); - }); + findEntry(bibEntry).ifPresent(entry -> { + getSelectionModel().clearSelection(); + getSelectionModel().select(entry); + scrollTo(entry); + }); } public void copy() { @@ -210,6 +238,18 @@ public void paste() { } } + private void handleOnDragOver(BibEntryTableViewModel originalItem, DragEvent event) { + if ((event.getGestureSource() != originalItem) && localDragboard.hasType(DragAndDropDataFormats.BIBENTRY_LIST_CLASS)) { + event.acceptTransferModes(TransferMode.MOVE); + + } + if (event.getDragboard().hasFiles() && (event.getSource() instanceof TableRow)) { + event.acceptTransferModes(TransferMode.COPY, TransferMode.MOVE, TransferMode.LINK); + } + event.consume(); //need to consume it here to stop the DnDTabPane from getting the event + + } + private void handleOnDragEntered(TableRow row, BibEntryTableViewModel entry, MouseDragEvent event) { // Support the following gesture to select entries: click on one row -> hold mouse button -> move over other rows // We need to select all items between the starting row and the row where the user currently hovers the mouse over @@ -222,6 +262,68 @@ private void handleOnDragEntered(TableRow row, BibEntryT private void handleOnDragDetected(TableRow row, BibEntryTableViewModel entry, MouseEvent event) { // Start drag'n'drop row.startFullDrag(); + + List entries = getSelectionModel().getSelectedItems().stream().map(BibEntryTableViewModel::getEntry).collect(Collectors.toList()); + + //The following is necesary to initiate the drag and drop in javafx, although we don't need the contents + //It doesn't work without + ClipboardContent content = new ClipboardContent(); + Dragboard dragboard = startDragAndDrop(TransferMode.MOVE); + content.put(DragAndDropDataFormats.ENTRIES, ""); + dragboard.setContent(content); + + if (!entries.isEmpty()) { + localDragboard.putBibEntries(entries); + } + + event.consume(); + } + + private void handleOnDragDropped(BibEntryTableViewModel originalItem, DragEvent event) { + + boolean success = false; + + if (event.getDragboard().hasContent(DataFormat.FILES)) { + + List files = event.getDragboard().getFiles().stream().map(File::toPath).collect(Collectors.toList()); + + List bibFiles = files.stream().filter(FileUtil::isBibFile).collect(Collectors.toList()); + + if (!bibFiles.isEmpty()) { + for (Path file : bibFiles) { + fileHandler.importEntriesFromDroppedBibFiles(file); + } + success = true; + + } + if (event.getGestureTarget() instanceof TableRow) { + + BibEntry entry = originalItem.getEntry(); + + if ((event.getTransferMode() == TransferMode.MOVE)) { + + LOGGER.debug("Mode MOVE"); //shift on win or no modifier + fileHandler.addNewEntryFromXMPorPDFContent(entry, files); + success = true; + } + + if (event.getTransferMode() == TransferMode.LINK) { + LOGGER.debug("LINK"); //alt on win + fileHandler.addToEntryRenameAndMoveToFileDir(entry, files); + success = true; + + } + if (event.getTransferMode() == TransferMode.COPY) { + LOGGER.debug("Mode Copy"); //ctrl on win + fileHandler.copyFilesToFileDirAndAddToEntry(entry, files); + success = true; + } + } + } + + event.setDropCompleted(success); + event.consume(); + } public void addSelectionListener(ListChangeListener listener) { @@ -237,14 +339,15 @@ public MainTableDataModel getTableModel() { } public BibEntry getEntryAt(int row) { - return model.getEntriesFiltered().get(row).getEntry(); + return model.getEntriesFilteredAndSorted().get(row).getEntry(); } public List getSelectedEntries() { return getSelectionModel() - .getSelectedItems().stream() - .map(BibEntryTableViewModel::getEntry) - .collect(Collectors.toList()); + .getSelectedItems() + .stream() + .map(BibEntryTableViewModel::getEntry) + .collect(Collectors.toList()); } /** @@ -326,9 +429,10 @@ private void setupComparatorChooser() { } private Optional findEntry(BibEntry entry) { - return model.getEntriesFiltered().stream() - .filter(viewModel -> viewModel.getEntry().equals(entry)) - .findFirst(); + return model.getEntriesFilteredAndSorted() + .stream() + .filter(viewModel -> viewModel.getEntry().equals(entry)) + .findFirst(); } /** diff --git a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java index ee7f7f507a8..f55f981266c 100644 --- a/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java +++ b/src/main/java/org/jabref/gui/maintable/MainTableDataModel.java @@ -68,7 +68,7 @@ private Optional createGroupMatcher(List selectedGrou return Optional.of(searchRules); } - public ObservableList getEntriesFiltered() { + public ObservableList getEntriesFilteredAndSorted() { return entriesSorted; } diff --git a/src/main/java/org/jabref/gui/mergeentries/MergeEntries.java b/src/main/java/org/jabref/gui/mergeentries/MergeEntries.java index 661f5ae8004..1a49ed7f9c1 100644 --- a/src/main/java/org/jabref/gui/mergeentries/MergeEntries.java +++ b/src/main/java/org/jabref/gui/mergeentries/MergeEntries.java @@ -34,7 +34,7 @@ import org.jabref.gui.FXDialogService; import org.jabref.gui.PreviewPanel; import org.jabref.gui.customjfx.CustomJFXPanel; -import org.jabref.gui.util.DefaultTaskExecutor; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.component.DiffHighlightingTextPane; import org.jabref.logic.bibtex.BibEntryWriter; import org.jabref.logic.bibtex.LatexFieldFormatter; @@ -179,12 +179,10 @@ private void initialize() { // Setup a PreviewPanel and a Bibtex source box for the merged entry mainPanel.add(boldFontLabel(Localization.lang("Merged entry")), CELL_CONSTRAINTS.xyw(1, 6, 6)); - DefaultTaskExecutor.runInJavaFXThread(() -> { - entryPreview = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService()); - entryPreview.setEntry(mergedEntry); - JFXPanel container = CustomJFXPanel.wrap(new Scene(entryPreview)); - mainPanel.add(container, CELL_CONSTRAINTS.xyw(1, 8, 6)); - }); + entryPreview = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), new FXDialogService(), ExternalFileTypes.getInstance()); + entryPreview.setEntry(mergedEntry); + JFXPanel container = CustomJFXPanel.wrap(new Scene(entryPreview)); + mainPanel.add(container, CELL_CONSTRAINTS.xyw(1, 8, 6)); mainPanel.add(boldFontLabel(Localization.lang("Merged BibTeX source code")), CELL_CONSTRAINTS.xyw(8, 6, 4)); diff --git a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialog.java b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialog.java index 8e9e6cfcf2c..ca73fefff1d 100644 --- a/src/main/java/org/jabref/gui/openoffice/StyleSelectDialog.java +++ b/src/main/java/org/jabref/gui/openoffice/StyleSelectDialog.java @@ -134,7 +134,7 @@ private void init() { // Create a preview panel for previewing styles // Must be done before creating the table to avoid NPEs DefaultTaskExecutor.runInJavaFXThread(() -> { - preview = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), dialogService); + preview = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), dialogService, ExternalFileTypes.getInstance()); // Use the test entry from the Preview settings tab in Preferences: preview.setEntry(prevEntry); }); @@ -166,7 +166,7 @@ private void init() { public void actionPerformed(ActionEvent event) { if ((table.getRowCount() == 0) || (table.getSelectedRowCount() == 0)) { dialogService.showErrorDialogAndWait(Localization.lang("Style selection"), - Localization.lang("You must select a valid style file.")); + Localization.lang("You must select a valid style file.")); return; } okPressed = true; @@ -269,9 +269,9 @@ private void setupPopupMenu() { removeAction = actionEvent -> getSelectedStyle().ifPresent(style -> { if (!style.isFromResource() && dialogService.showConfirmationDialogAndWait(Localization.lang("Remove style"), - Localization.lang("Are you sure you want to remove the style?"), - Localization.lang("Remove style"), - Localization.lang("Cancel"))) { + Localization.lang("Are you sure you want to remove the style?"), + Localization.lang("Remove style"), + Localization.lang("Cancel"))) { if (!loader.removeStyle(style)) { LOGGER.info("Problem removing style"); } @@ -469,14 +469,14 @@ public AddFileDialog() { JButton browse = new JButton(Localization.lang("Browse")); FileDialogConfiguration fileDialogConfiguration = new FileDialogConfiguration.Builder() - .addExtensionFilter(Localization.lang("Style file"), StandardFileType.JSTYLE) - .withDefaultExtension(Localization.lang("Style file"), StandardFileType.JSTYLE) - .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) - .build(); + .addExtensionFilter(Localization.lang("Style file"), StandardFileType.JSTYLE) + .withDefaultExtension(Localization.lang("Style file"), StandardFileType.JSTYLE) + .withInitialDirectory(Globals.prefs.get(JabRefPreferences.WORKING_DIRECTORY)) + .build(); browse.addActionListener(e -> { Optional file = DefaultTaskExecutor - .runInJavaFXThread(() -> dialogService.showFileOpenDialog(fileDialogConfiguration)); + .runInJavaFXThread(() -> dialogService.showFileOpenDialog(fileDialogConfiguration)); file.ifPresent(f -> newFile.setText(f.toAbsolutePath().toString())); }); diff --git a/src/main/java/org/jabref/gui/preftabs/PreferencesDialog.java b/src/main/java/org/jabref/gui/preftabs/PreferencesDialog.java index 5596f47920a..60e754ef6d8 100644 --- a/src/main/java/org/jabref/gui/preftabs/PreferencesDialog.java +++ b/src/main/java/org/jabref/gui/preftabs/PreferencesDialog.java @@ -23,6 +23,7 @@ import org.jabref.gui.DialogService; import org.jabref.gui.GUIGlobals; import org.jabref.gui.JabRefFrame; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.DefaultTaskExecutor; @@ -104,7 +105,7 @@ public PreferencesDialog(JabRefFrame parent) { importSettingsTab = new ImportSettingsTab(prefs); nameFormatterTab = new NameFormatterTab(prefs); networkTab = new NetworkTab(dialogService,prefs); - previewPrefsTab = new PreviewPrefsTab(dialogService); + previewPrefsTab = new PreviewPrefsTab(dialogService, ExternalFileTypes.getInstance()); tableColumnsTab = new TableColumnsTab(prefs,frame); tablePrefsTab = new TablePrefsTab(prefs); xmpPrefsTab = new XmpPrefsTab(prefs); diff --git a/src/main/java/org/jabref/gui/preftabs/PreviewPrefsTab.java b/src/main/java/org/jabref/gui/preftabs/PreviewPrefsTab.java index b639c4a952f..89d310c8ebd 100644 --- a/src/main/java/org/jabref/gui/preftabs/PreviewPrefsTab.java +++ b/src/main/java/org/jabref/gui/preftabs/PreviewPrefsTab.java @@ -27,6 +27,7 @@ import org.jabref.gui.BasePanel; import org.jabref.gui.DialogService; import org.jabref.gui.PreviewPanel; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.citationstyle.CitationStyle; import org.jabref.logic.l10n.Localization; @@ -59,9 +60,11 @@ public class PreviewPrefsTab extends JPanel implements PrefsTab { private final Button btnDefault = new Button(Localization.lang("Default")); private final ScrollPane scrollPane = new ScrollPane(layout); private final DialogService dialogService; + private final ExternalFileTypes externalFileTypes; - public PreviewPrefsTab(DialogService dialogService) { + public PreviewPrefsTab(DialogService dialogService, ExternalFileTypes externalFileTypes) { this.dialogService = dialogService; + this.externalFileTypes = externalFileTypes; setupLogic(); setupGui(); } @@ -125,7 +128,7 @@ private void setupLogic() { btnTest.setOnAction(event -> { try { DefaultTaskExecutor.runInJavaFXThread(() -> { - PreviewPanel testPane = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), dialogService); + PreviewPanel testPane = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), dialogService, externalFileTypes); if (chosen.getSelectionModel().getSelectedItems().isEmpty()) { testPane.setFixedLayout(layout.getText()); testPane.setEntry(TestEntry.getTestEntry()); @@ -135,7 +138,7 @@ private void setupLogic() { PreviewPreferences preferences = Globals.prefs.getPreviewPreferences(); preferences = new PreviewPreferences(preferences.getPreviewCycle(),indexStyle,preferences.getPreviewPanelDividerPosition(),preferences.isPreviewPanelEnabled(), preferences.getPreviewStyle(),preferences.getPreviewStyleDefault()); - testPane = new PreviewPanel(JabRefGUI.getMainFrame().getCurrentBasePanel(), new BibDatabaseContext(), Globals.getKeyPrefs(), preferences, dialogService); + testPane = new PreviewPanel(JabRefGUI.getMainFrame().getCurrentBasePanel(), new BibDatabaseContext(), Globals.getKeyPrefs(), preferences, dialogService, externalFileTypes); testPane.setEntry(TestEntry.getTestEntry()); testPane.updateLayout(preferences); } diff --git a/src/main/java/org/jabref/gui/search/SearchResultFrame.java b/src/main/java/org/jabref/gui/search/SearchResultFrame.java index 5be6cc368aa..9977866efad 100644 --- a/src/main/java/org/jabref/gui/search/SearchResultFrame.java +++ b/src/main/java/org/jabref/gui/search/SearchResultFrame.java @@ -43,6 +43,7 @@ import org.jabref.gui.customjfx.CustomJFXPanel; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.externalfiletype.ExternalFileMenuItem; +import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.filelist.FileListEntry; import org.jabref.gui.filelist.FileListTableModel; import org.jabref.gui.icon.IconTheme; @@ -81,9 +82,7 @@ */ public class SearchResultFrame { - private static final String[] FIELDS = new String[] { - FieldName.AUTHOR, FieldName.TITLE, FieldName.YEAR, FieldName.JOURNAL - }; + private static final String[] FIELDS = new String[] {FieldName.AUTHOR, FieldName.TITLE, FieldName.YEAR, FieldName.JOURNAL}; private static final int DATABASE_COL = 0; private static final int FILE_COL = 1; private static final int URL_COL = 2; @@ -110,7 +109,6 @@ public class SearchResultFrame { private final SearchQuery searchQuery; private final boolean globalSearch; - public SearchResultFrame(JabRefFrame frame, String title, SearchQuery searchQuery, boolean globalSearch) { this.frame = Objects.requireNonNull(frame); this.searchQuery = searchQuery; @@ -124,25 +122,24 @@ private void init(String title) { searchResultFrame.setTitle(title); searchResultFrame.setIconImages(IconTheme.getLogoSet()); - preview = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), frame.getDialogService()); + preview = new PreviewPanel(null, null, Globals.getKeyPrefs(), Globals.prefs.getPreviewPreferences(), frame.getDialogService(), ExternalFileTypes.getInstance()); sortedEntries = new SortedList<>(entries, new EntryComparator(false, true, FieldName.AUTHOR)); model = (DefaultEventTableModel) GlazedListsSwing.eventTableModelWithThreadProxyList(sortedEntries, - new EntryTableFormat()); + new EntryTableFormat()); entryTable = new JTable(model); GeneralRenderer renderer = new GeneralRenderer(Color.white); entryTable.setDefaultRenderer(JLabel.class, renderer); entryTable.setDefaultRenderer(String.class, renderer); setWidths(); - TableComparatorChooser tableSorter = - TableComparatorChooser.install(entryTable, sortedEntries, - AbstractTableComparatorChooser.MULTIPLE_COLUMN_KEYBOARD); + TableComparatorChooser tableSorter = TableComparatorChooser.install(entryTable, sortedEntries, + AbstractTableComparatorChooser.MULTIPLE_COLUMN_KEYBOARD); setupComparatorChooser(tableSorter); JScrollPane sp = new JScrollPane(entryTable); final DefaultEventSelectionModel selectionModel = (DefaultEventSelectionModel) GlazedListsSwing - .eventSelectionModelWithThreadProxyList(sortedEntries); + .eventSelectionModelWithThreadProxyList(sortedEntries); entryTable.setSelectionModel(selectionModel); selectionModel.getSelected().addListEventListener(new EntrySelectionListener()); entryTable.addMouseListener(new TableClickListener()); @@ -154,6 +151,7 @@ private void init(String title) { // Key bindings: AbstractAction closeAction = new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { dispose(); @@ -170,24 +168,28 @@ public void actionPerformed(ActionEvent e) { inputMap = entryTable.getInputMap(); //Override 'selectNextColumnCell' and 'selectPreviousColumnCell' to move rows instead of cells on TAB actionMap.put("selectNextColumnCell", new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { selectNextEntry(); } }); actionMap.put("selectPreviousColumnCell", new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { selectPreviousEntry(); } }); actionMap.put("selectNextRow", new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { selectNextEntry(); } }); actionMap.put("selectPreviousRow", new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { selectPreviousEntry(); @@ -197,6 +199,7 @@ public void actionPerformed(ActionEvent e) { String selectFirst = "selectFirst"; inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.SELECT_FIRST_ENTRY), selectFirst); actionMap.put(selectFirst, new AbstractAction() { + @Override public void actionPerformed(ActionEvent event) { selectFirstEntry(); @@ -206,6 +209,7 @@ public void actionPerformed(ActionEvent event) { String selectLast = "selectLast"; inputMap.put(Globals.getKeyPrefs().getKey(KeyBinding.SELECT_LAST_ENTRY), selectLast); actionMap.put(selectLast, new AbstractAction() { + @Override public void actionPerformed(ActionEvent event) { selectLastEntry(); @@ -213,6 +217,7 @@ public void actionPerformed(ActionEvent event) { }); actionMap.put("copy", new AbstractAction() { + @Override public void actionPerformed(ActionEvent e) { if (!selectionModel.getSelected().isEmpty()) { @@ -232,6 +237,7 @@ public void actionPerformed(ActionEvent e) { // override standard enter-action; enter opens the selected entry entryTable.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT).put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "Enter"); actionMap.put("Enter", new AbstractAction() { + @Override public void actionPerformed(ActionEvent ae) { BibEntry entry = sortedEntries.get(entryTable.getSelectedRow()); @@ -240,6 +246,7 @@ public void actionPerformed(ActionEvent ae) { }); searchResultFrame.addWindowListener(new WindowAdapter() { + @Override public void windowOpened(WindowEvent e) { contentPane.setDividerLocation(0.5f); @@ -259,6 +266,7 @@ public void windowClosing(WindowEvent event) { searchResultFrame.setLocation(searchPreferences.getSearchDialogPosX(), searchPreferences.getSearchDialogPosY()); searchResultFrame.addComponentListener(new ComponentAdapter() { + @Override public void componentResized(ComponentEvent e) { new SearchPreferences(Globals.prefs) @@ -269,8 +277,8 @@ public void componentResized(ComponentEvent e) { @Override public void componentMoved(ComponentEvent e) { new SearchPreferences(Globals.prefs) - .setSearchDialogPosX(searchResultFrame.getLocation().x) - .setSearchDialogPosY(searchResultFrame.getLocation().y); + .setSearchDialogPosX(searchResultFrame.getLocation().x) + .setSearchDialogPosY(searchResultFrame.getLocation().y); } }); } @@ -518,7 +526,7 @@ public void processPopupTrigger(MouseEvent e) { description = flEntry.getLink(); } menu.add(new ExternalFileMenuItem(p.frame(), description, flEntry.getLink(), - flEntry.getType().get().getIcon().getSmallIcon(), p.getBibDatabaseContext(), flEntry.getType())); + flEntry.getType().get().getIcon().getSmallIcon(), p.getBibDatabaseContext(), flEntry.getType())); count++; } @@ -579,37 +587,36 @@ public String getColumnName(int column) { public Object getColumnValue(BibEntry entry, int column) { if (column < PAD) { switch (column) { - case DATABASE_COL: - return entryHome.get(entry).getTabTitle(); - case FILE_COL: - if (entry.hasField(FieldName.FILE)) { - FileListTableModel tmpModel = new FileListTableModel(); - entry.getField(FieldName.FILE).ifPresent(tmpModel::setContent); - fileLabel.setToolTipText(tmpModel.getToolTipHTMLRepresentation()); - if (tmpModel.getRowCount() > 0) { - if (tmpModel.getEntry(0).getType().isPresent()) { - fileLabel.setIcon(tmpModel.getEntry(0).getType().get().getIcon().getSmallIcon()); - } else { - fileLabel.setIcon(IconTheme.JabRefIcons.FILE.getSmallIcon()); + case DATABASE_COL: + return entryHome.get(entry).getTabTitle(); + case FILE_COL: + if (entry.hasField(FieldName.FILE)) { + FileListTableModel tmpModel = new FileListTableModel(); + entry.getField(FieldName.FILE).ifPresent(tmpModel::setContent); + fileLabel.setToolTipText(tmpModel.getToolTipHTMLRepresentation()); + if (tmpModel.getRowCount() > 0) { + if (tmpModel.getEntry(0).getType().isPresent()) { + fileLabel.setIcon(tmpModel.getEntry(0).getType().get().getIcon().getSmallIcon()); + } else { + fileLabel.setIcon(IconTheme.JabRefIcons.FILE.getSmallIcon()); + } } + return fileLabel; + } else { + return null; + } + case URL_COL: { + Optional urlField = entry.getField(FieldName.URL); + if (urlField.isPresent()) { + urlLabel.setToolTipText(urlField.get()); + return urlLabel; } - return fileLabel; - } else { return null; } - case URL_COL: { - Optional urlField = entry.getField(FieldName.URL); - if (urlField.isPresent()) { - urlLabel.setToolTipText(urlField.get()); - return urlLabel; - } - return null; - } - default: - return null; + default: + return null; } - } - else { + } else { String field = FIELDS[column - PAD]; String fieldContent = entry.getLatexFreeField(field).orElse(""); diff --git a/src/main/java/org/jabref/gui/util/CustomLocalDragboard.java b/src/main/java/org/jabref/gui/util/CustomLocalDragboard.java new file mode 100644 index 00000000000..d8af9e34948 --- /dev/null +++ b/src/main/java/org/jabref/gui/util/CustomLocalDragboard.java @@ -0,0 +1,66 @@ +package org.jabref.gui.util; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.jabref.gui.DragAndDropDataFormats; +import org.jabref.gui.GUIGlobals; +import org.jabref.model.entry.BibEntry; + +/** + * Placeholder class for a custom generic type safe dragboard to be used in drag and drop, does not depend on serialization + * Don't use this class directly. Use the instance provided in {@link GUIGlobals#localDragboard} + */ +public class CustomLocalDragboard { + + private final Map, Object> contents = new HashMap<>(); + + /** + * Puts the value of the concrete class in a map. All previous content stored in the map is removed + * @param type The Type of the class + * @param value The value to store + */ + public void putValue(Class type, T value) { + clearAll(); + contents.put(type, type.cast(value)); + } + + public T getValue(Class type) { + return type.cast(contents.get(type)); + } + + public boolean hasType(Class type) { + return contents.keySet().contains(type); + } + + public void clear(Class type) { + contents.remove(type); + } + + public void clearAll() { + contents.clear(); + } + + /** + * Puts A List of {@link BibEntry} in the map + * All previous content is cleared + * @param entries The list to put + */ + public void putBibEntries(List entries) { + putValue(DragAndDropDataFormats.BIBENTRY_LIST_CLASS, entries); + } + + /** + * Get a List of {@link BibEntry} from the dragboard + * @return List of BibEntry or empty list if no entries are avaiable + */ + public List getBibEntries() { + if (hasType(DragAndDropDataFormats.BIBENTRY_LIST_CLASS)) { + return getValue(DragAndDropDataFormats.BIBENTRY_LIST_CLASS); + } + return Collections.emptyList(); + } + +} \ No newline at end of file diff --git a/src/main/java/org/jabref/logic/cleanup/CleanupWorker.java b/src/main/java/org/jabref/logic/cleanup/CleanupWorker.java index 1c7e651182b..737a16a0157 100644 --- a/src/main/java/org/jabref/logic/cleanup/CleanupWorker.java +++ b/src/main/java/org/jabref/logic/cleanup/CleanupWorker.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Objects; -import org.jabref.logic.layout.LayoutFormatterPreferences; import org.jabref.model.FieldChange; import org.jabref.model.cleanup.CleanupJob; import org.jabref.model.database.BibDatabaseContext; @@ -16,7 +15,6 @@ public class CleanupWorker { private final BibDatabaseContext databaseContext; private final String fileNamePattern; private final String fileDirPattern; - private final LayoutFormatterPreferences layoutPrefs; private final FileDirectoryPreferences fileDirectoryPreferences; private int unsuccessfulRenames; @@ -25,7 +23,6 @@ public CleanupWorker(BibDatabaseContext databaseContext, CleanupPreferences clea this.databaseContext = databaseContext; this.fileNamePattern = cleanupPreferences.getFileNamePattern(); this.fileDirPattern = cleanupPreferences.getFileDirPattern(); - this.layoutPrefs = cleanupPreferences.getLayoutFormatterPreferences(); this.fileDirectoryPreferences = cleanupPreferences.getFileDirectoryPreferences(); } diff --git a/src/main/java/org/jabref/logic/cleanup/MoveFilesCleanup.java b/src/main/java/org/jabref/logic/cleanup/MoveFilesCleanup.java index b919576a0dd..463dd0aba53 100644 --- a/src/main/java/org/jabref/logic/cleanup/MoveFilesCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/MoveFilesCleanup.java @@ -71,7 +71,7 @@ public List cleanup(BibEntry entry) { fileList = Arrays.asList(singleFileFieldCleanup); //Add all other except the current selected file newFileList = entry.getFiles().stream().filter(name -> !name.equals(singleFileFieldCleanup)) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } else { newFileList = new ArrayList<>(); fileList = entry.getFiles(); @@ -113,7 +113,7 @@ public List cleanup(BibEntry entry) { LinkedFile newFileEntry = fileEntry; if (!oldFileName.equals(newTargetFile.toString())) { newFileEntry = new LinkedFile(fileEntry.getDescription(), newEntryFilePath, - fileEntry.getFileType()); + fileEntry.getFileType()); changed = true; } newFileList.add(newFileEntry); diff --git a/src/main/java/org/jabref/logic/cleanup/RenamePdfCleanup.java b/src/main/java/org/jabref/logic/cleanup/RenamePdfCleanup.java index e5bf8e77802..12061c6600b 100644 --- a/src/main/java/org/jabref/logic/cleanup/RenamePdfCleanup.java +++ b/src/main/java/org/jabref/logic/cleanup/RenamePdfCleanup.java @@ -68,7 +68,7 @@ public List cleanupWithException(BibEntry entry) throws IOException oldFileList = Collections.singletonList(singleFieldCleanup); newFileList = entry.getFiles().stream().filter(x -> !x.equals(singleFieldCleanup)) - .collect(Collectors.toList()); + .collect(Collectors.toList()); } else { newFileList = new ArrayList<>(); oldFileList = entry.getFiles(); @@ -101,7 +101,7 @@ public List cleanupWithException(BibEntry entry) throws IOException String expandedOldFilePath = expandedOldFile.get().toString(); boolean pathsDifferOnlyByCase = newPath.toString().equalsIgnoreCase(expandedOldFilePath) - && !newPath.toString().equals(expandedOldFilePath); + && !newPath.toString().equals(expandedOldFilePath); if (Files.exists(newPath) && !pathsDifferOnlyByCase) { // we do not overwrite files @@ -154,10 +154,9 @@ public List cleanupWithException(BibEntry entry) throws IOException public String getTargetFileName(LinkedFile flEntry, BibEntry entry) { String realOldFilename = flEntry.getLink(); - String targetFileName = FileUtil.createFileNameFromPattern( - databaseContext.getDatabase(), entry, fileNamePattern).trim() - + '.' - + FileHelper.getFileExtension(realOldFilename).orElse("pdf"); + String targetFileName = FileUtil.createFileNameFromPattern(databaseContext.getDatabase(), entry, fileNamePattern).trim() + + '.' + + FileHelper.getFileExtension(realOldFilename).orElse("pdf"); // Only create valid file names return FileUtil.getValidFileName(targetFileName); @@ -176,16 +175,15 @@ public int getUnsuccessfulRenames() { public Optional findExistingFile(LinkedFile flEntry, BibEntry entry) { String targetFileName = getTargetFileName(flEntry, entry); // The .get() is legal without check because the method will always return a value. - Path targetFilePath = flEntry.findIn(databaseContext, - fileDirectoryPreferences).get().getParent().resolve(targetFileName); + Path targetFilePath = flEntry.findIn(databaseContext, fileDirectoryPreferences) + .get().getParent().resolve(targetFileName); Path oldFilePath = flEntry.findIn(databaseContext, fileDirectoryPreferences).get(); //Check if file already exists in directory with different case. //This is necessary because other entries may have such a file. Optional matchedByDiffCase = Optional.empty(); try (Stream stream = Files.list(oldFilePath.getParent())) { - matchedByDiffCase = stream - .filter(name -> name.toString().equalsIgnoreCase(targetFilePath.toString())) - .findFirst(); + matchedByDiffCase = stream.filter(name -> name.toString().equalsIgnoreCase(targetFilePath.toString())) + .findFirst(); } catch (IOException e) { LOGGER.error("Could not get the list of files in target directory", e); } diff --git a/src/main/java/org/jabref/logic/externalfiles/ExternalFilesContentImporter.java b/src/main/java/org/jabref/logic/externalfiles/ExternalFilesContentImporter.java new file mode 100644 index 00000000000..bdfe5970639 --- /dev/null +++ b/src/main/java/org/jabref/logic/externalfiles/ExternalFilesContentImporter.java @@ -0,0 +1,40 @@ +package org.jabref.logic.externalfiles; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.List; + +import org.jabref.logic.importer.ImportFormatPreferences; +import org.jabref.logic.importer.OpenDatabase; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.importer.fileformat.PdfContentImporter; +import org.jabref.logic.xmp.XmpUtilReader; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.util.FileUpdateMonitor; + +public class ExternalFilesContentImporter { + + private final PdfContentImporter pdfImporter; + private final ImportFormatPreferences importFormatPreferences; + + public ExternalFilesContentImporter(ImportFormatPreferences importFormatPreferences) { + pdfImporter = new PdfContentImporter(importFormatPreferences); + this.importFormatPreferences = importFormatPreferences; + } + + public List importPDFContent(Path file) { + return pdfImporter.importDatabase(file, StandardCharsets.UTF_8).getDatabase().getEntries(); + + } + + public List importXMPContent(Path file) throws IOException { + return XmpUtilReader.readXmp(file, importFormatPreferences.getXmpPreferences()); + + } + + public List importFromBibFile(Path bibFile, FileUpdateMonitor fileUpdateMonitor) { + ParserResult parserResult = OpenDatabase.loadDatabase(bibFile.toString(), importFormatPreferences, fileUpdateMonitor); + return parserResult.getDatabaseContext().getEntries(); + } +} diff --git a/src/main/java/org/jabref/logic/importer/ImportFormatPreferences.java b/src/main/java/org/jabref/logic/importer/ImportFormatPreferences.java index 5f155356ad4..2bb90b382fc 100644 --- a/src/main/java/org/jabref/logic/importer/ImportFormatPreferences.java +++ b/src/main/java/org/jabref/logic/importer/ImportFormatPreferences.java @@ -6,6 +6,7 @@ import org.jabref.logic.bibtex.FieldContentParserPreferences; import org.jabref.logic.bibtexkeypattern.BibtexKeyPatternPreferences; import org.jabref.logic.importer.fileformat.CustomImporter; +import org.jabref.logic.xmp.XmpPreferences; public class ImportFormatPreferences { @@ -14,16 +15,18 @@ public class ImportFormatPreferences { private final Character keywordSeparator; private final BibtexKeyPatternPreferences bibtexKeyPatternPreferences; private final FieldContentParserPreferences fieldContentParserPreferences; + private final XmpPreferences xmpPreferences; private final boolean keywordSyncEnabled; public ImportFormatPreferences(Set customImportList, Charset encoding, Character keywordSeparator, BibtexKeyPatternPreferences bibtexKeyPatternPreferences, - FieldContentParserPreferences fieldContentParserPreferences, boolean keywordSyncEnabled) { + FieldContentParserPreferences fieldContentParserPreferences, XmpPreferences xmpPreferences, boolean keywordSyncEnabled) { this.customImportList = customImportList; this.encoding = encoding; this.keywordSeparator = keywordSeparator; this.bibtexKeyPatternPreferences = bibtexKeyPatternPreferences; this.fieldContentParserPreferences = fieldContentParserPreferences; + this.xmpPreferences = xmpPreferences; this.keywordSyncEnabled = keywordSyncEnabled; } @@ -53,7 +56,7 @@ public FieldContentParserPreferences getFieldContentParserPreferences() { public ImportFormatPreferences withEncoding(Charset newEncoding) { return new ImportFormatPreferences(customImportList, newEncoding, keywordSeparator, bibtexKeyPatternPreferences, - fieldContentParserPreferences, keywordSyncEnabled); + fieldContentParserPreferences, xmpPreferences, keywordSyncEnabled); } /** @@ -63,4 +66,8 @@ public ImportFormatPreferences withEncoding(Charset newEncoding) { public boolean isKeywordSyncEnabled() { return keywordSyncEnabled; } + + public XmpPreferences getXmpPreferences() { + return xmpPreferences; + } } diff --git a/src/main/java/org/jabref/logic/importer/OpenDatabase.java b/src/main/java/org/jabref/logic/importer/OpenDatabase.java index f8c04f8d870..ed777ef8211 100644 --- a/src/main/java/org/jabref/logic/importer/OpenDatabase.java +++ b/src/main/java/org/jabref/logic/importer/OpenDatabase.java @@ -46,7 +46,7 @@ public static ParserResult loadDatabase(String name, ImportFormatPreferences imp try { if (!FileBasedLock.waitForFileLock(file.toPath())) { LOGGER.error(Localization.lang("Error opening file") + " '" + name + "'. " - + "File is locked by another JabRef instance."); + + "File is locked by another JabRef instance."); return new ParserResult(); } @@ -70,7 +70,7 @@ public static ParserResult loadDatabase(String name, ImportFormatPreferences imp * Opens a new database. */ public static ParserResult loadDatabase(File fileToOpen, ImportFormatPreferences importFormatPreferences, FileUpdateMonitor fileMonitor) - throws IOException { + throws IOException { ParserResult result = new BibtexImporter(importFormatPreferences, fileMonitor).importDatabase(fileToOpen.toPath(), importFormatPreferences.getEncoding()); diff --git a/src/main/java/org/jabref/logic/importer/ParserResult.java b/src/main/java/org/jabref/logic/importer/ParserResult.java index a19b5020a2d..998a710d3d5 100644 --- a/src/main/java/org/jabref/logic/importer/ParserResult.java +++ b/src/main/java/org/jabref/logic/importer/ParserResult.java @@ -1,6 +1,7 @@ package org.jabref.logic.importer; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -24,7 +25,7 @@ public class ParserResult { private final List duplicateKeys = new ArrayList<>(); private BibDatabase database; private MetaData metaData = new MetaData(); - private File file; + private Path file; private boolean invalid; private boolean toOpenTab; private boolean changedOnMigration = false; @@ -94,11 +95,11 @@ public Map getEntryTypes() { } public Optional getFile() { - return Optional.ofNullable(file); + return Optional.ofNullable(file).map(Path::toFile); } public void setFile(File f) { - file = f; + file = f.toPath(); } /** @@ -167,14 +168,14 @@ public String getErrorMessage() { } public BibDatabaseContext getDatabaseContext() { - return new BibDatabaseContext(database, metaData, file); + return new BibDatabaseContext(database, metaData, file.toFile()); } public void setDatabaseContext(BibDatabaseContext bibDatabaseContext) { Objects.requireNonNull(bibDatabaseContext); database = bibDatabaseContext.getDatabase(); metaData = bibDatabaseContext.getMetaData(); - file = bibDatabaseContext.getDatabaseFile().orElse(null); + file = bibDatabaseContext.getDatabasePath().orElse(null); } public boolean isEmpty() { diff --git a/src/main/java/org/jabref/logic/util/io/FileUtil.java b/src/main/java/org/jabref/logic/util/io/FileUtil.java index db2c860f1c7..8a759739528 100644 --- a/src/main/java/org/jabref/logic/util/io/FileUtil.java +++ b/src/main/java/org/jabref/logic/util/io/FileUtil.java @@ -52,13 +52,14 @@ public static Optional getFileExtension(String fileName) { } } + /** * Returns the extension of a file or Optional.empty() if the file does not have one (no . in name). * * @return The extension, trimmed and in lowercase. */ - public static Optional getFileExtension(File file) { - return getFileExtension(file.getName()); + public static Optional getFileExtension(Path file) { + return getFileExtension(file.getFileName().toString()); } /** @@ -155,7 +156,7 @@ public static boolean copyFile(Path pathToSourceFile, Path pathToDestinationFile return false; } if (Files.exists(pathToDestinationFile) && !replaceExisting) { - LOGGER.error("Path to the destination file is not exists and the file shouldn't be replace."); + LOGGER.error("Path to the destination file exists but the file shouldn't be replaced."); return false; } try { @@ -329,4 +330,14 @@ public static String toPortableString(Path path) { return path.toString() .replace('\\', '/'); } + + /** + * Test if the file is a bib file by simply checking the extension to be ".bib" + * @param file The file to check + * @return True if file extension is ".bib", false otherwise + */ + public static boolean isBibFile(Path file) + { + return getFileExtension(file).filter(type -> "bib".equals(type)).isPresent(); + } } diff --git a/src/main/java/org/jabref/logic/xmp/XmpUtilReader.java b/src/main/java/org/jabref/logic/xmp/XmpUtilReader.java index 29d17ba9f88..0eb04675789 100644 --- a/src/main/java/org/jabref/logic/xmp/XmpUtilReader.java +++ b/src/main/java/org/jabref/logic/xmp/XmpUtilReader.java @@ -26,6 +26,8 @@ public class XmpUtilReader { private static final String END_TAG = ""; private XmpUtilReader() { + //See: https://pdfbox.apache.org/2.0/getting-started.html + System.setProperty("sun.java2d.cmm", "sun.java2d.cmm.kcms.KcmsServiceProvider"); //To get higher rendering speed on java 8 oder 9 for images } /** diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java index e2a02520e14..370b3d655df 100644 --- a/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/src/main/java/org/jabref/model/entry/BibEntry.java @@ -59,12 +59,13 @@ public class BibEntry implements Cloneable { private final Map latexFreeFields = new ConcurrentHashMap<>(); private final EventBus eventBus = new EventBus(); private String id; - private StringProperty type = new SimpleStringProperty(); + private final StringProperty type = new SimpleStringProperty(); + private ObservableMap fields = FXCollections.observableMap(new ConcurrentHashMap<>()); // Search and grouping status is stored in boolean fields for quick reference: private boolean searchHit; private boolean groupHit; - private String parsedSerialization; + private String parsedSerialization = ""; private String commentsBeforeEntry = ""; /** * Marks whether the complete serialization, which was read from file, should be used. @@ -78,6 +79,7 @@ public class BibEntry implements Cloneable { */ public BibEntry() { this(IdGenerator.next(), DEFAULT_TYPE); + } /** @@ -582,7 +584,7 @@ public void setGroupHit(boolean groupHit) { */ public String getAuthorTitleYear(int maxCharacters) { String[] s = new String[] {getField(FieldName.AUTHOR).orElse("N/A"), getField(FieldName.TITLE).orElse("N/A"), - getField(FieldName.YEAR).orElse("N/A")}; + getField(FieldName.YEAR).orElse("N/A")}; String text = s[0] + ": \"" + s[1] + "\" (" + s[2] + ')'; if ((maxCharacters <= 0) || (text.length() <= maxCharacters)) { @@ -702,7 +704,7 @@ public Optional removeKeywords(KeywordList keywordsToRemove, Charac } public Optional replaceKeywords(KeywordList keywordsToReplace, Keyword newValue, - Character keywordDelimiter) { + Character keywordDelimiter) { KeywordList keywordList = getKeywords(keywordDelimiter); keywordList.replaceAll(keywordsToReplace, newValue); @@ -876,10 +878,11 @@ public ObservableMap getFieldsObservable() { * Returns a list of observables that represent the data of the entry. */ public Observable[] getObservables() { - return new Observable[]{fields}; + return new Observable[] {fields}; } private interface GetFieldInterface { Optional getValueForField(String fieldName); } + } diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index a6b4b65d800..69a11d02469 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -1413,8 +1413,8 @@ public boolean isKeywordSyncEnabled() { public ImportFormatPreferences getImportFormatPreferences() { return new ImportFormatPreferences(customImports, getDefaultEncoding(), getKeywordDelimiter(), - getBibtexKeyPatternPreferences(), getFieldContentParserPreferences(), - isKeywordSyncEnabled()); + getBibtexKeyPatternPreferences(), getFieldContentParserPreferences(), getXMPPreferences(), + isKeywordSyncEnabled()); } public SavePreferences loadForExportFromPreferences() { diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 6c7e4ca7907..0b3bfa33c0a 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -1735,8 +1735,6 @@ Ill-formed\ entrytype\ comment\ in\ BIB\ file=Ill-formed entrytype comment in BI Move\ linked\ files\ to\ default\ file\ directory\ %0=Move linked files to default file directory %0 -Clipboard=Clipboard -Could\ not\ paste\ entry\ as\ text\:=Could not paste entry as text: Do\ you\ still\ want\ to\ continue?=Do you still want to continue? This\ action\ will\ modify\ the\ following\ field(s)\ in\ at\ least\ one\ entry\ each\:=This action will modify the following field(s) in at least one entry each: This\ could\ cause\ undesired\ changes\ to\ your\ entries.=This could cause undesired changes to your entries. @@ -2240,5 +2238,8 @@ View\ change\ log=View change log View\ event\ log=View event log Website=Website Write\ XMP-metadata\ to\ PDFs=Write XMP-metadata to PDFs +Do\ you\ want\ to\ import\ these\ as\ new\ entries\ into\ the\ current\ library\ or\ do\ you\ want\ to\ link\ the\ file\ to\ the\ entry?=Do you want to import these as new entries into the current library or do you want to link the file to the entry? + Font\ size\:=Font size\: Override\ default\ font\ settings=Override default font settings +Clear\ search=Clear search \ No newline at end of file diff --git a/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java index 4ed4d465885..05941f3a4ab 100644 --- a/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java +++ b/src/test/java/org/jabref/gui/groups/GroupNodeViewModelTest.java @@ -7,6 +7,7 @@ import org.jabref.gui.StateManager; import org.jabref.gui.util.CurrentThreadTaskExecutor; +import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -157,7 +158,7 @@ public void entriesAreAddedCorrectly() { BibEntry entry = new BibEntry(); databaseContext.getDatabase().insertEntry(entry); - GroupNodeViewModel model = new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group); + GroupNodeViewModel model = new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group, new CustomLocalDragboard()); model.addEntriesToGroup(databaseContext.getEntries()); assertEquals(databaseContext.getEntries(), model.getGroupNode().getEntriesInGroup(databaseContext.getEntries())); @@ -165,10 +166,10 @@ public void entriesAreAddedCorrectly() { } private GroupNodeViewModel getViewModelForGroup(AbstractGroup group) { - return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group); + return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group, new CustomLocalDragboard()); } private GroupNodeViewModel getViewModelForGroup(GroupTreeNode group) { - return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group); + return new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group, new CustomLocalDragboard()); } } diff --git a/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java b/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java index 4a594140bd6..afa4ad53eba 100644 --- a/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java +++ b/src/test/java/org/jabref/gui/groups/GroupTreeViewModelTest.java @@ -5,6 +5,7 @@ import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; import org.jabref.gui.util.CurrentThreadTaskExecutor; +import org.jabref.gui.util.CustomLocalDragboard; import org.jabref.gui.util.TaskExecutor; import org.jabref.model.database.BibDatabaseContext; import org.jabref.model.entry.BibEntry; @@ -32,13 +33,13 @@ public void setUp() throws Exception { stateManager = new StateManager(); stateManager.activeDatabaseProperty().setValue(Optional.of(databaseContext)); taskExecutor = new CurrentThreadTaskExecutor(); - groupTree = new GroupTreeViewModel(stateManager, mock(DialogService.class), taskExecutor); + groupTree = new GroupTreeViewModel(stateManager, mock(DialogService.class), taskExecutor, new CustomLocalDragboard()); } @Test public void rootGroupIsAllEntriesByDefault() throws Exception { AllEntriesGroup allEntriesGroup = new AllEntriesGroup("All entries"); - assertEquals(new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, allEntriesGroup), groupTree.rootGroupProperty().getValue()); + assertEquals(new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, allEntriesGroup, new CustomLocalDragboard()), groupTree.rootGroupProperty().getValue()); } @Test @@ -47,7 +48,7 @@ public void explicitGroupsAreRemovedFromEntriesOnDelete() { BibEntry entry = new BibEntry(); databaseContext.getDatabase().insertEntry(entry); - GroupNodeViewModel model = new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group); + GroupNodeViewModel model = new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group, new CustomLocalDragboard()); model.addEntriesToGroup(databaseContext.getEntries()); groupTree.removeGroupsAndSubGroupsFromEntries(model); @@ -61,7 +62,7 @@ public void keywordGroupsAreNotRemovedFromEntriesOnDelete() { BibEntry entry = new BibEntry(); databaseContext.getDatabase().insertEntry(entry); - GroupNodeViewModel model = new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group); + GroupNodeViewModel model = new GroupNodeViewModel(databaseContext, stateManager, taskExecutor, group, new CustomLocalDragboard()); model.addEntriesToGroup(databaseContext.getEntries()); groupTree.removeGroupsAndSubGroupsFromEntries(model); diff --git a/src/test/java/org/jabref/logic/util/io/FileUtilTest.java b/src/test/java/org/jabref/logic/util/io/FileUtilTest.java index 28c764a024f..7073e65415e 100644 --- a/src/test/java/org/jabref/logic/util/io/FileUtilTest.java +++ b/src/test/java/org/jabref/logic/util/io/FileUtilTest.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.nio.file.*; -import java.nio.file.attribute.BasicFileAttributes; import java.util.Arrays; import java.util.List; import java.util.Optional; @@ -14,7 +13,6 @@ import org.jabref.model.entry.BibEntry; import org.jabref.model.util.FileHelper; -import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -364,4 +362,17 @@ void testGetLinkedDirNameDefaultFullTitle() { assertEquals("PDF/1998/Åuthör/1234 - mytitle", FileUtil.createDirNameFromPattern(null, entry, fileNamePattern)); } + @Test + public void testIsBibFile() throws IOException + { + Path bibFile = Files.createFile(rootDir.resolve("test.bib")); + + assertTrue(FileUtil.isBibFile(bibFile)); + } + + @Test + public void testIsNotBibFile() throws IOException { + Path bibFile = Files.createFile(rootDir.resolve("test.pdf")); + assertFalse(FileUtil.isBibFile(bibFile)); + } } diff --git a/src/test/java/org/jabref/model/entry/BibEntryTest.java b/src/test/java/org/jabref/model/entry/BibEntryTest.java index 6e62eff5c77..886439451a0 100644 --- a/src/test/java/org/jabref/model/entry/BibEntryTest.java +++ b/src/test/java/org/jabref/model/entry/BibEntryTest.java @@ -132,4 +132,5 @@ public void testGetResolvedKeywords() { assertEquals(new KeywordList(new Keyword("kw"), new Keyword("kw2"), new Keyword("kw3")), actual); } + }