diff --git a/src/main/java/org/jabref/gui/Dialog.java b/src/main/java/org/jabref/gui/Dialog.java deleted file mode 100644 index edb73ae9599..00000000000 --- a/src/main/java/org/jabref/gui/Dialog.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.jabref.gui; - -import java.util.Optional; - -public interface Dialog { - Optional showAndWait(); -} diff --git a/src/main/java/org/jabref/gui/DragAndDropHelper.java b/src/main/java/org/jabref/gui/DragAndDropHelper.java deleted file mode 100644 index 92e229a5c7f..00000000000 --- a/src/main/java/org/jabref/gui/DragAndDropHelper.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.jabref.gui; - -import java.io.File; -import java.nio.file.Path; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - -import javafx.scene.input.Dragboard; - -import org.jabref.logic.util.io.FileUtil; - -public class DragAndDropHelper { - - public static boolean hasBibFiles(Dragboard dragboard) { - return !getBibFiles(dragboard).isEmpty(); - } - - public static List getBibFiles(Dragboard dragboard) { - if (!dragboard.hasFiles()) { - return Collections.emptyList(); - } else { - return dragboard.getFiles().stream().map(File::toPath).filter(FileUtil::isBibFile).collect(Collectors.toList()); - } - } - - public static boolean hasGroups(Dragboard dragboard) { - return !getGroups(dragboard).isEmpty(); - } - - public static List getGroups(Dragboard dragboard) { - if (!dragboard.hasContent(DragAndDropDataFormats.GROUP)) { - return Collections.emptyList(); - } else { - return (List) dragboard.getContent(DragAndDropDataFormats.GROUP); - } - } -} diff --git a/src/main/java/org/jabref/gui/Globals.java b/src/main/java/org/jabref/gui/Globals.java index c95ceed3153..4bbb5dbb2d8 100644 --- a/src/main/java/org/jabref/gui/Globals.java +++ b/src/main/java/org/jabref/gui/Globals.java @@ -3,6 +3,7 @@ import org.jabref.architecture.AllowedToUseAwt; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.remote.CLIMessageHandler; +import org.jabref.gui.telemetry.Telemetry; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.util.DefaultFileUpdateMonitor; import org.jabref.gui.util.DefaultTaskExecutor; diff --git a/src/main/java/org/jabref/gui/JabRefGUI.java b/src/main/java/org/jabref/gui/JabRefGUI.java index 6ef229f9cb0..a2805111e0e 100644 --- a/src/main/java/org/jabref/gui/JabRefGUI.java +++ b/src/main/java/org/jabref/gui/JabRefGUI.java @@ -11,10 +11,12 @@ import javafx.stage.Stage; import javafx.stage.WindowEvent; +import org.jabref.gui.frame.JabRefFrame; import org.jabref.gui.help.VersionWorker; import org.jabref.gui.icon.IconTheme; import org.jabref.gui.keyboard.TextInputKeyBindings; import org.jabref.gui.openoffice.OOBibBaseConnect; +import org.jabref.gui.telemetry.Telemetry; import org.jabref.gui.theme.ThemeManager; import org.jabref.logic.UiCommand; import org.jabref.logic.l10n.Localization; @@ -26,7 +28,6 @@ import org.jabref.preferences.JabRefPreferences; import com.tobiasdiez.easybind.EasyBind; -import impl.org.controlsfx.skin.DecorationPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -73,7 +74,11 @@ public void start(Stage stage) { mainStage, dialogService, fileUpdateMonitor, - preferencesService); + preferencesService, + Globals.stateManager, + Globals.undoManager, + Globals.entryTypesManager, + Globals.TASK_EXECUTOR); openWindow(); @@ -154,12 +159,7 @@ private void openWindow() { } debugLogWindowState(mainStage); - // We create a decoration pane ourselves for performance reasons - // (otherwise it has to be injected later, leading to a complete redraw/relayout of the complete scene) - DecorationPane root = new DecorationPane(); - root.getChildren().add(JabRefGUI.mainFrame); - - Scene scene = new Scene(root, 800, 800); + Scene scene = new Scene(JabRefGUI.mainFrame); themeManager.installCss(scene); // Handle TextEditor key bindings @@ -177,11 +177,15 @@ private void openWindow() { } public void onShowing(WindowEvent event) { + Platform.runLater(() -> mainFrame.updateDividerPosition()); + // Open last edited databases if (uiCommands.stream().noneMatch(UiCommand.BlankWorkspace.class::isInstance) && preferencesService.getWorkspacePreferences().shouldOpenLastEdited()) { mainFrame.openLastEditedDatabases(); } + + Telemetry.initTrackingNotification(dialogService, preferencesService.getTelemetryPreferences()); } public void onCloseRequest(WindowEvent event) { diff --git a/src/main/java/org/jabref/gui/LibraryTabContainer.java b/src/main/java/org/jabref/gui/LibraryTabContainer.java index 25c1ac468e1..139653c87ca 100644 --- a/src/main/java/org/jabref/gui/LibraryTabContainer.java +++ b/src/main/java/org/jabref/gui/LibraryTabContainer.java @@ -4,6 +4,7 @@ import org.jabref.model.database.BibDatabaseContext; +import org.jspecify.annotations.NonNull; import org.jspecify.annotations.NullMarked; import org.jspecify.annotations.Nullable; @@ -16,24 +17,19 @@ public interface LibraryTabContainer { void showLibraryTab(LibraryTab libraryTab); - void addTab(LibraryTab libraryTab, boolean raisePanel); - void addTab(BibDatabaseContext bibDatabaseContext, boolean raisePanel); + void addTab(LibraryTab libraryTab, boolean raisePanel); + /** * Closes a designated libraryTab * - * @param libraryTab to be closed. + * @param tab to be closed. * @return true if closing the tab was successful */ - boolean closeTab(LibraryTab libraryTab); + boolean closeTab(@Nullable LibraryTab tab); - /** - * Closes the currently viewed libraryTab - * - * @return true if closing the tab was successful - */ - boolean closeCurrentTab(); + boolean closeTabs(@NonNull List tabs); /** * Refreshes the ui after changes to the preferences diff --git a/src/main/java/org/jabref/gui/actions/JabRefAction.java b/src/main/java/org/jabref/gui/actions/JabRefAction.java index e20b7650d90..90bf466735c 100644 --- a/src/main/java/org/jabref/gui/actions/JabRefAction.java +++ b/src/main/java/org/jabref/gui/actions/JabRefAction.java @@ -4,8 +4,8 @@ import javafx.beans.binding.Bindings; -import org.jabref.gui.Telemetry; import org.jabref.gui.keyboard.KeyBindingRepository; +import org.jabref.gui.telemetry.Telemetry; import de.saxsys.mvvmfx.utils.commands.Command; diff --git a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java index 7e58dff849e..1e76c707a81 100644 --- a/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java +++ b/src/main/java/org/jabref/gui/bibtexextractor/BibtexExtractorViewModel.java @@ -11,8 +11,8 @@ import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; -import org.jabref.gui.Telemetry; import org.jabref.gui.externalfiles.ImportHandler; +import org.jabref.gui.telemetry.Telemetry; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.importer.FetcherException; diff --git a/src/main/java/org/jabref/gui/EntryType.fxml b/src/main/java/org/jabref/gui/entrytype/EntryType.fxml similarity index 97% rename from src/main/java/org/jabref/gui/EntryType.fxml rename to src/main/java/org/jabref/gui/entrytype/EntryType.fxml index ac4e7f922ec..bb7601b7438 100644 --- a/src/main/java/org/jabref/gui/EntryType.fxml +++ b/src/main/java/org/jabref/gui/entrytype/EntryType.fxml @@ -13,7 +13,7 @@ + fx:controller="org.jabref.gui.entrytype.EntryTypeView"> diff --git a/src/main/java/org/jabref/gui/EntryTypeView.java b/src/main/java/org/jabref/gui/entrytype/EntryTypeView.java similarity index 98% rename from src/main/java/org/jabref/gui/EntryTypeView.java rename to src/main/java/org/jabref/gui/entrytype/EntryTypeView.java index 3b10205a547..4640c8aa06e 100644 --- a/src/main/java/org/jabref/gui/EntryTypeView.java +++ b/src/main/java/org/jabref/gui/entrytype/EntryTypeView.java @@ -1,4 +1,4 @@ -package org.jabref.gui; +package org.jabref.gui.entrytype; import java.util.Collection; import java.util.List; @@ -17,6 +17,10 @@ import javafx.scene.layout.FlowPane; import javafx.stage.Screen; +import org.jabref.gui.DialogService; +import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.StateManager; import org.jabref.gui.util.BaseDialog; import org.jabref.gui.util.ControlHelper; import org.jabref.gui.util.IconValidationDecorator; diff --git a/src/main/java/org/jabref/gui/EntryTypeViewModel.java b/src/main/java/org/jabref/gui/entrytype/EntryTypeViewModel.java similarity index 98% rename from src/main/java/org/jabref/gui/EntryTypeViewModel.java rename to src/main/java/org/jabref/gui/entrytype/EntryTypeViewModel.java index b63718edeaf..0700094dc9a 100644 --- a/src/main/java/org/jabref/gui/EntryTypeViewModel.java +++ b/src/main/java/org/jabref/gui/entrytype/EntryTypeViewModel.java @@ -1,4 +1,4 @@ -package org.jabref.gui; +package org.jabref.gui.entrytype; import java.util.Optional; @@ -14,6 +14,9 @@ import javafx.concurrent.Task; import javafx.concurrent.Worker; +import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.StateManager; import org.jabref.gui.externalfiles.ImportHandler; import org.jabref.gui.importer.NewEntryAction; import org.jabref.gui.util.TaskExecutor; diff --git a/src/main/java/org/jabref/gui/menus/FileHistoryMenu.java b/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java similarity index 99% rename from src/main/java/org/jabref/gui/menus/FileHistoryMenu.java rename to src/main/java/org/jabref/gui/frame/FileHistoryMenu.java index ab580b5fcd6..529ef9ed224 100644 --- a/src/main/java/org/jabref/gui/menus/FileHistoryMenu.java +++ b/src/main/java/org/jabref/gui/frame/FileHistoryMenu.java @@ -1,4 +1,4 @@ -package org.jabref.gui.menus; +package org.jabref.gui.frame; import java.nio.file.Files; import java.nio.file.Path; diff --git a/src/main/java/org/jabref/gui/FrameDndHandler.java b/src/main/java/org/jabref/gui/frame/FrameDndHandler.java similarity index 81% rename from src/main/java/org/jabref/gui/FrameDndHandler.java rename to src/main/java/org/jabref/gui/frame/FrameDndHandler.java index 9d3124e6ab6..75fd0aebb77 100644 --- a/src/main/java/org/jabref/gui/FrameDndHandler.java +++ b/src/main/java/org/jabref/gui/frame/FrameDndHandler.java @@ -1,8 +1,11 @@ -package org.jabref.gui; +package org.jabref.gui.frame; +import java.io.File; import java.nio.file.Path; +import java.util.Collections; import java.util.List; import java.util.function.Supplier; +import java.util.stream.Collectors; import javafx.scene.Node; import javafx.scene.Scene; @@ -13,8 +16,12 @@ import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; +import org.jabref.gui.DragAndDropDataFormats; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.StateManager; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.logic.l10n.Localization; +import org.jabref.logic.util.io.FileUtil; import org.jabref.model.entry.BibEntry; import org.jabref.model.groups.GroupTreeNode; @@ -53,7 +60,7 @@ void initDragAndDrop() { // drag'n'drop on tabs covered dnd on tabbedPane, so dnd on tabs should contain all dnds on tabbedPane for (Node destinationTabNode : tabPane.lookupAll(".tab")) { destinationTabNode.setOnDragOver(tabDragEvent -> onTabDragOver(event, tabDragEvent, dndIndicator)); - destinationTabNode.setOnDragExited(event1 -> tabPane.getTabs().remove(dndIndicator)); + destinationTabNode.setOnDragExited(tabDragEvent -> tabPane.getTabs().remove(dndIndicator)); destinationTabNode.setOnDragDropped(tabDragEvent -> onTabDragDropped(destinationTabNode, tabDragEvent, dndIndicator)); } event.consume(); @@ -63,12 +70,12 @@ void initDragAndDrop() { }); } - void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, Tab dndIndicator) { + private void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, Tab dndIndicator) { Dragboard dragboard = tabDragEvent.getDragboard(); - if (DragAndDropHelper.hasBibFiles(dragboard)) { + if (hasBibFiles(dragboard)) { tabPane.getTabs().remove(dndIndicator); - List bibFiles = DragAndDropHelper.getBibFiles(dragboard); + List bibFiles = getBibFiles(dragboard); OpenDatabaseAction openDatabaseAction = this.openDatabaseAction.get(); openDatabaseAction.openFiles(bibFiles); tabDragEvent.setDropCompleted(true); @@ -83,8 +90,8 @@ void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, Tab dndIn if (libraryTab.getId().equals(destinationTabNode.getId()) && !tabPane.getSelectionModel().getSelectedItem().equals(libraryTab)) { LibraryTab destinationLibraryTab = (LibraryTab) libraryTab; - if (DragAndDropHelper.hasGroups(dragboard)) { - List groupPathToSources = DragAndDropHelper.getGroups(dragboard); + if (hasGroups(dragboard)) { + List groupPathToSources = getGroups(dragboard); copyRootNode(destinationLibraryTab); @@ -113,8 +120,8 @@ void onTabDragDropped(Node destinationTabNode, DragEvent tabDragEvent, Tab dndIn } } - void onTabDragOver(DragEvent event, DragEvent tabDragEvent, Tab dndIndicator) { - if (DragAndDropHelper.hasBibFiles(tabDragEvent.getDragboard()) || DragAndDropHelper.hasGroups(tabDragEvent.getDragboard())) { + private void onTabDragOver(DragEvent event, DragEvent tabDragEvent, Tab dndIndicator) { + if (hasBibFiles(tabDragEvent.getDragboard()) || hasGroups(tabDragEvent.getDragboard())) { tabDragEvent.acceptTransferModes(TransferMode.ANY); if (!tabPane.getTabs().contains(dndIndicator)) { tabPane.getTabs().add(dndIndicator); @@ -130,8 +137,8 @@ void onTabDragOver(DragEvent event, DragEvent tabDragEvent, Tab dndIndicator) { } } - void onSceneDragOver(DragEvent event, Tab dndIndicator) { - if (DragAndDropHelper.hasBibFiles(event.getDragboard())) { + private void onSceneDragOver(DragEvent event, Tab dndIndicator) { + if (hasBibFiles(event.getDragboard())) { event.acceptTransferModes(TransferMode.ANY); if (!tabPane.getTabs().contains(dndIndicator)) { tabPane.getTabs().add(dndIndicator); @@ -149,7 +156,7 @@ void onSceneDragOver(DragEvent event, Tab dndIndicator) { private void onSceneDragDropped(DragEvent event, Tab dndIndicator) { tabPane.getTabs().remove(dndIndicator); - List bibFiles = DragAndDropHelper.getBibFiles(event.getDragboard()); + List bibFiles = getBibFiles(event.getDragboard()); OpenDatabaseAction openDatabaseAction = this.openDatabaseAction.get(); openDatabaseAction.openFiles(bibFiles); event.setDropCompleted(true); @@ -195,4 +202,28 @@ private void copyGroupTreeNode(LibraryTab destinationLibraryTab, GroupTreeNode p } } } + + private boolean hasBibFiles(Dragboard dragboard) { + return !getBibFiles(dragboard).isEmpty(); + } + + private List getBibFiles(Dragboard dragboard) { + if (!dragboard.hasFiles()) { + return Collections.emptyList(); + } else { + return dragboard.getFiles().stream().map(File::toPath).filter(FileUtil::isBibFile).collect(Collectors.toList()); + } + } + + private boolean hasGroups(Dragboard dragboard) { + return !getGroups(dragboard).isEmpty(); + } + + private List getGroups(Dragboard dragboard) { + if (!dragboard.hasContent(DragAndDropDataFormats.GROUP)) { + return Collections.emptyList(); + } else { + return (List) dragboard.getContent(DragAndDropDataFormats.GROUP); + } + } } diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/frame/JabRefFrame.java similarity index 56% rename from src/main/java/org/jabref/gui/JabRefFrame.java rename to src/main/java/org/jabref/gui/frame/JabRefFrame.java index 210e978d5e5..84a34f53966 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/frame/JabRefFrame.java @@ -1,28 +1,18 @@ -package org.jabref.gui; +package org.jabref.gui.frame; import java.io.IOException; import java.nio.file.Path; -import java.sql.SQLException; -import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; import java.util.List; import java.util.Objects; import java.util.Optional; -import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; -import java.util.stream.Collectors; import javafx.application.Platform; -import javafx.beans.InvalidationListener; -import javafx.beans.Observable; import javafx.beans.binding.Bindings; import javafx.beans.binding.StringBinding; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableBooleanValue; import javafx.collections.transformation.FilteredList; import javafx.event.Event; -import javafx.scene.control.ButtonType; import javafx.scene.control.ContextMenu; import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SplitPane; @@ -33,40 +23,34 @@ import javafx.scene.layout.VBox; import javafx.stage.Stage; +import org.jabref.gui.DialogService; +import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.LibraryTabContainer; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.desktop.JabRefDesktop; -import org.jabref.gui.importer.ImportEntriesDialog; import org.jabref.gui.importer.NewEntryAction; -import org.jabref.gui.importer.ParserResultWarningDialog; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.libraryproperties.LibraryPropertiesAction; -import org.jabref.gui.menus.FileHistoryMenu; import org.jabref.gui.push.PushToApplicationCommand; import org.jabref.gui.search.GlobalSearchBar; import org.jabref.gui.search.SearchType; import org.jabref.gui.sidepane.SidePane; import org.jabref.gui.sidepane.SidePaneType; import org.jabref.gui.undo.CountingUndoManager; -import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.UiCommand; -import org.jabref.logic.importer.ImportCleanup; -import org.jabref.logic.importer.ParserResult; -import org.jabref.logic.l10n.Localization; -import org.jabref.logic.shared.DatabaseNotSupportedException; -import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; -import org.jabref.logic.shared.exception.NotASharedDatabaseException; import org.jabref.logic.undo.AddUndoableActionEvent; import org.jabref.logic.undo.UndoChangeEvent; import org.jabref.logic.undo.UndoRedoEvent; import org.jabref.logic.util.OS; import org.jabref.model.database.BibDatabaseContext; -import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.BibEntryTypesManager; import org.jabref.model.entry.types.StandardEntryType; import org.jabref.model.util.FileUpdateMonitor; @@ -105,34 +89,80 @@ public class JabRefFrame extends BorderPane implements LibraryTabContainer { private final DialogService dialogService; private final FileUpdateMonitor fileUpdateMonitor; private final BibEntryTypesManager entryTypesManager; + private final TaskExecutor taskExecutor; + + private final JabRefFrameViewModel viewModel; private final PushToApplicationCommand pushToApplicationCommand; - private SidePane sidePane; + private final SidePane sidePane; private final TabPane tabbedPane = new TabPane(); private Subscription dividerSubscription; - private final TaskExecutor taskExecutor; - public JabRefFrame(Stage mainStage, DialogService dialogService, FileUpdateMonitor fileUpdateMonitor, - PreferencesService preferencesService) { + PreferencesService preferencesService, + StateManager stateManager, + CountingUndoManager undoManager, + BibEntryTypesManager entryTypesManager, + TaskExecutor taskExecutor) { this.mainStage = mainStage; this.dialogService = dialogService; this.fileUpdateMonitor = fileUpdateMonitor; this.prefs = preferencesService; + this.stateManager = stateManager; + this.undoManager = undoManager; + this.entryTypesManager = entryTypesManager; + this.taskExecutor = taskExecutor; - this.stateManager = Globals.stateManager; - this.undoManager = Globals.undoManager; - this.entryTypesManager = Globals.entryTypesManager; - this.taskExecutor = Globals.TASK_EXECUTOR; + setId("frame"); - this.frameDndHandler = new FrameDndHandler(tabbedPane, mainStage::getScene, this::getOpenDatabaseAction, stateManager); + // Create components + this.viewModel = new JabRefFrameViewModel( + preferencesService, + stateManager, + dialogService, + this, + entryTypesManager, + fileUpdateMonitor, + undoManager, + taskExecutor); + + this.frameDndHandler = new FrameDndHandler( + tabbedPane, + mainStage::getScene, + this::getOpenDatabaseAction, + stateManager); + + this.globalSearchBar = new GlobalSearchBar( + this, + stateManager, + prefs, + undoManager, + dialogService, + SearchType.NORMAL_SEARCH); + + this.sidePane = new SidePane( + this, + prefs, + Globals.journalAbbreviationRepository, + taskExecutor, + dialogService, + stateManager, + fileUpdateMonitor, + entryTypesManager, + undoManager); - this.globalSearchBar = new GlobalSearchBar(this, stateManager, prefs, undoManager, dialogService, SearchType.NORMAL_SEARCH); - this.pushToApplicationCommand = new PushToApplicationCommand(stateManager, dialogService, prefs, taskExecutor); - this.fileHistory = new FileHistoryMenu(prefs.getGuiPreferences().getFileHistory(), dialogService, getOpenDatabaseAction()); + this.pushToApplicationCommand = new PushToApplicationCommand( + stateManager, + dialogService, + prefs, + taskExecutor); + this.fileHistory = new FileHistoryMenu( + prefs.getGuiPreferences().getFileHistory(), + dialogService, + getOpenDatabaseAction()); this.setOnKeyTyped(key -> { if (this.fileHistory.isShowing()) { if (this.fileHistory.openFileByKey(key)) { @@ -141,7 +171,70 @@ public JabRefFrame(Stage mainStage, } }); - init(); + initLayout(); + initKeyBindings(); + frameDndHandler.initDragAndDrop(); + initBindings(); + } + + private void initLayout() { + MainToolBar mainToolBar = new MainToolBar( + this, + pushToApplicationCommand, + globalSearchBar, + dialogService, + stateManager, + prefs, + fileUpdateMonitor, + taskExecutor, + entryTypesManager, + undoManager); + + MainMenu mainMenu = new MainMenu( + this, + fileHistory, + sidePane, + pushToApplicationCommand, + prefs, + stateManager, + fileUpdateMonitor, + taskExecutor, + dialogService, + Globals.journalAbbreviationRepository, + entryTypesManager, + undoManager, + Globals.getClipboardManager(), + this::getOpenDatabaseAction); + + VBox head = new VBox(mainMenu, mainToolBar); + head.setSpacing(0d); + setTop(head); + + splitPane.getItems().addAll(tabbedPane); + SplitPane.setResizableWithParent(sidePane, false); + EasyBind.subscribe(sidePane.widthProperty(), c -> updateSidePane()); + setCenter(splitPane); + } + + private void updateSidePane() { + if (sidePane.getChildren().isEmpty()) { + if (dividerSubscription != null) { + dividerSubscription.unsubscribe(); + } + splitPane.getItems().remove(sidePane); + } else { + if (!splitPane.getItems().contains(sidePane)) { + splitPane.getItems().addFirst(sidePane); + updateDividerPosition(); + } + } + } + + public void updateDividerPosition() { + if (mainStage.isShowing() && !sidePane.getChildren().isEmpty()) { + splitPane.setDividerPositions(prefs.getGuiPreferences().getSidePaneWidth() / splitPane.getWidth()); + dividerSubscription = EasyBind.listen(sidePane.widthProperty(), (obs, old, newVal) -> prefs.getGuiPreferences().setSidePaneWidth(newVal.doubleValue())); + } } private void initKeyBindings() { @@ -211,167 +304,7 @@ private void initKeyBindings() { }); } - private void storeLastOpenedFiles(List filenames, Path focusedDatabase) { - if (prefs.getWorkspacePreferences().shouldOpenLastEdited()) { - // Here we store the names of all current files. If there is no current file, we remove any - // previously stored filename. - if (filenames.isEmpty()) { - prefs.getGuiPreferences().getLastFilesOpened().clear(); - } else { - prefs.getGuiPreferences().setLastFilesOpened(filenames); - prefs.getGuiPreferences().setLastFocusedFile(focusedDatabase); - } - } - } - - /** - * Quit JabRef - * - * @return true if the user chose to quit; false otherwise - */ - public boolean close() { - // Ask if the user really wants to close, if there are still background tasks running - // The background tasks may make changes themselves that need saving. - if (stateManager.getAnyTasksThatWillNotBeRecoveredRunning().getValue()) { - Optional shouldClose = dialogService.showBackgroundProgressDialogAndWait( - Localization.lang("Please wait..."), - Localization.lang("Waiting for background tasks to finish. Quit anyway?"), - stateManager); - if (!(shouldClose.isPresent() && (shouldClose.get() == ButtonType.YES))) { - return false; - } - } - - // Read the opened and focused databases before closing them - List openedLibraries = getLibraryTabs().stream() - .map(LibraryTab::getBibDatabaseContext) - .map(BibDatabaseContext::getDatabasePath) - .flatMap(Optional::stream) - .toList(); - Path focusedLibraries = Optional.ofNullable(getCurrentLibraryTab()) - .map(LibraryTab::getBibDatabaseContext) - .flatMap(BibDatabaseContext::getDatabasePath) - .orElse(null); - - // Then ask if the user really wants to close, if the library has not been saved since last save. - if (!closeTabs()) { - return false; - } - - storeLastOpenedFiles(openedLibraries, focusedLibraries); // store only if successfully having closed the libraries - - ProcessingLibraryDialog processingLibraryDialog = new ProcessingLibraryDialog(dialogService); - processingLibraryDialog.showAndWait(getLibraryTabs()); - - return true; - } - - private void initLayout() { - setId("frame"); - - sidePane = new SidePane( - this, - prefs, - Globals.journalAbbreviationRepository, - taskExecutor, - dialogService, - stateManager, - fileUpdateMonitor, - entryTypesManager, - undoManager); - - MainToolBar mainToolBar = new MainToolBar( - this, - pushToApplicationCommand, - globalSearchBar, - dialogService, - stateManager, - prefs, - fileUpdateMonitor, - taskExecutor, - entryTypesManager, - undoManager); - - MainMenu mainMenu = new MainMenu( - this, - sidePane, - pushToApplicationCommand, - prefs, - stateManager, - fileUpdateMonitor, - taskExecutor, - dialogService, - Globals.journalAbbreviationRepository, - entryTypesManager, - undoManager, - Globals.getClipboardManager()); - - VBox head = new VBox(mainMenu, mainToolBar); - head.setSpacing(0d); - setTop(head); - - splitPane.getItems().addAll(tabbedPane); - SplitPane.setResizableWithParent(sidePane, false); - - sidePane.getChildren().addListener((InvalidationListener) c -> updateSidePane()); - updateSidePane(); - - // We need to wait with setting the divider since it gets reset a few times during the initial set-up - mainStage.showingProperty().addListener(new InvalidationListener() { - @Override - public void invalidated(Observable observable) { - if (mainStage.isShowing()) { - Platform.runLater(() -> { - setDividerPosition(); - observable.removeListener(this); - }); - } - } - }); - - setCenter(splitPane); - } - - private void updateSidePane() { - if (sidePane.getChildren().isEmpty()) { - if (dividerSubscription != null) { - dividerSubscription.unsubscribe(); - } - splitPane.getItems().remove(sidePane); - } else { - if (!splitPane.getItems().contains(sidePane)) { - splitPane.getItems().addFirst(sidePane); - setDividerPosition(); - } - } - } - - private void setDividerPosition() { - if (mainStage.isShowing() && !sidePane.getChildren().isEmpty()) { - splitPane.setDividerPositions(prefs.getGuiPreferences().getSidePaneWidth() / splitPane.getWidth()); - dividerSubscription = EasyBind.subscribe(sidePane.widthProperty(), width -> prefs.getGuiPreferences().setSidePaneWidth(width.doubleValue())); - } - } - - /** - * Returns a list of all LibraryTabs in this frame. - */ - public @NonNull List getLibraryTabs() { - return tabbedPane.getTabs().stream() - .filter(LibraryTab.class::isInstance) - .map(LibraryTab.class::cast) - .toList(); - } - - public void showLibraryTab(@NonNull LibraryTab libraryTab) { - tabbedPane.getSelectionModel().select(libraryTab); - } - - public void init() { - initLayout(); - initKeyBindings(); - frameDndHandler.initDragAndDrop(); - + private void initBindings() { // Bind global state FilteredList filteredTabs = new FilteredList<>(tabbedPane.getTabs()); filteredTabs.setPredicate(LibraryTab.class::isInstance); @@ -447,12 +380,26 @@ public void init() { libraryTab.textProperty()); mainStage.titleProperty().bind(windowTitle); }); + } - Telemetry.initTrackingNotification(dialogService, prefs.getTelemetryPreferences()); + /* ************************************************************************ + * + * Public API + * + **************************************************************************/ + + /** + * Returns a list of all LibraryTabs in this frame. + */ + public @NonNull List getLibraryTabs() { + return tabbedPane.getTabs().stream() + .filter(LibraryTab.class::isInstance) + .map(LibraryTab.class::cast) + .toList(); } /** - * Returns the currently viewed BasePanel. + * Returns the currently viewed LibraryTab. */ public LibraryTab getCurrentLibraryTab() { if (tabbedPane.getSelectionModel().getSelectedItem() == null) { @@ -461,32 +408,8 @@ public LibraryTab getCurrentLibraryTab() { return (LibraryTab) tabbedPane.getSelectionModel().getSelectedItem(); } - private ContextMenu createTabContextMenuFor(LibraryTab tab, KeyBindingRepository keyBindingRepository) { - ContextMenu contextMenu = new ContextMenu(); - ActionFactory factory = new ActionFactory(keyBindingRepository); - - contextMenu.getItems().addAll( - factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, new LibraryPropertiesAction(tab::getBibDatabaseContext, stateManager)), - factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, new OpenDatabaseFolder(tab::getBibDatabaseContext)), - factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(tab::getBibDatabaseContext, stateManager, prefs, dialogService)), - new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction(this, tab, stateManager)), - factory.createMenuItem(StandardActions.CLOSE_OTHER_LIBRARIES, new CloseOthersDatabaseAction(tab)), - factory.createMenuItem(StandardActions.CLOSE_ALL_LIBRARIES, new CloseAllDatabaseAction())); - - return contextMenu; - } - - public void addTab(@NonNull LibraryTab libraryTab, boolean raisePanel) { - tabbedPane.getTabs().add(libraryTab); - if (raisePanel) { - tabbedPane.getSelectionModel().select(libraryTab); - tabbedPane.requestFocus(); - } - - libraryTab.setContextMenu(createTabContextMenuFor(libraryTab, Globals.getKeyPrefs())); - - libraryTab.getUndoManager().registerListener(new UndoRedoEventManager()); + public void showLibraryTab(@NonNull LibraryTab libraryTab) { + tabbedPane.getSelectionModel().select(libraryTab); } /** @@ -509,84 +432,70 @@ public void addTab(@NonNull BibDatabaseContext databaseContext, boolean raisePan addTab(libraryTab, raisePanel); } - /** - * Should be called when a user asks JabRef at the command line - * i) to import a file or - * ii) to open a .bib file - */ - public void addTab(ParserResult parserResult, boolean raisePanel) { - if (parserResult.toOpenTab()) { - LOGGER.trace("Adding the entries to the open tab."); - LibraryTab libraryTab = getCurrentLibraryTab(); - if (libraryTab == null) { - LOGGER.debug("No open tab found to add entries to. Creating a new tab."); - addTab(parserResult.getDatabaseContext(), raisePanel); - } else { - addImportedEntries(libraryTab, parserResult); - } - } else { - // only add tab if library is not already open - Optional libraryTab = getLibraryTabs().stream() - .filter(p -> p.getBibDatabaseContext() - .getDatabasePath() - .equals(parserResult.getPath())) - .findFirst(); - - if (libraryTab.isPresent()) { - tabbedPane.getSelectionModel().select(libraryTab.get()); - } else { - // On this place, a tab is added after loading using the command line - // This takes a different execution path than loading a library using the GUI - addTab(parserResult.getDatabaseContext(), raisePanel); - } + public void addTab(@NonNull LibraryTab libraryTab, boolean raisePanel) { + tabbedPane.getTabs().add(libraryTab); + if (raisePanel) { + tabbedPane.getSelectionModel().select(libraryTab); + tabbedPane.requestFocus(); } + + libraryTab.setContextMenu(createTabContextMenuFor(libraryTab, Globals.getKeyPrefs())); + + libraryTab.getUndoManager().registerListener(new UndoRedoEventManager()); } - /** - * Opens the import inspection dialog to let the user decide which of the given entries to import. - * - * @param panel The BasePanel to add to. - * @param parserResult The entries to add. - */ - private void addImportedEntries(final LibraryTab panel, final ParserResult parserResult) { - BackgroundTask task = BackgroundTask.wrap(() -> parserResult); - ImportCleanup cleanup = ImportCleanup.targeting(panel.getBibDatabaseContext().getMode()); - cleanup.doPostCleanup(parserResult.getDatabase().getEntries()); - ImportEntriesDialog dialog = new ImportEntriesDialog(panel.getBibDatabaseContext(), task); - dialog.setTitle(Localization.lang("Import")); - dialogService.showCustomDialogAndWait(dialog); + private ContextMenu createTabContextMenuFor(LibraryTab tab, KeyBindingRepository keyBindingRepository) { + ContextMenu contextMenu = new ContextMenu(); + ActionFactory factory = new ActionFactory(keyBindingRepository); + + contextMenu.getItems().addAll( + factory.createMenuItem(StandardActions.LIBRARY_PROPERTIES, new LibraryPropertiesAction(tab::getBibDatabaseContext, stateManager)), + factory.createMenuItem(StandardActions.OPEN_DATABASE_FOLDER, new OpenDatabaseFolder(tab::getBibDatabaseContext)), + factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(tab::getBibDatabaseContext, stateManager, prefs, dialogService)), + new SeparatorMenuItem(), + factory.createMenuItem(StandardActions.CLOSE_LIBRARY, new CloseDatabaseAction(this, tab, stateManager)), + factory.createMenuItem(StandardActions.CLOSE_OTHER_LIBRARIES, new CloseOthersDatabaseAction(tab)), + factory.createMenuItem(StandardActions.CLOSE_ALL_LIBRARIES, new CloseAllDatabaseAction())); + + return contextMenu; } - public boolean closeTab(@NonNull LibraryTab libraryTab) { - if (libraryTab.requestClose()) { - tabbedPane.getTabs().remove(libraryTab); - Event.fireEvent(libraryTab, new Event(this, libraryTab, Tab.CLOSED_EVENT)); + public boolean close() { + return viewModel.close(); + } + + public boolean closeTab(LibraryTab tab) { + return closeTabs(List.of(tab)); + } + + public boolean closeTabs(@NonNull List tabs) { + // Only accept library tabs that are shown in the tab container + List toClose = tabs.stream() + .distinct() + .filter(getLibraryTabs()::contains) + .toList(); + + if (toClose.isEmpty()) { + // Nothing to do return true; } - return false; - } - private boolean closeTabs() { // Ask before closing any tab, if any tab has changes - for (LibraryTab libraryTab : getLibraryTabs()) { + for (LibraryTab libraryTab : toClose) { if (!libraryTab.requestClose()) { return false; } } // Close after checking for changes and saving all databases - for (LibraryTab libraryTab : getLibraryTabs()) { + for (LibraryTab libraryTab : toClose) { tabbedPane.getTabs().remove(libraryTab); Event.fireEvent(libraryTab, new Event(this, libraryTab, Tab.CLOSED_EVENT)); } return true; } - public boolean closeCurrentTab() { - return closeTab(getCurrentLibraryTab()); - } - - public OpenDatabaseAction getOpenDatabaseAction() { + private OpenDatabaseAction getOpenDatabaseAction() { return new OpenDatabaseAction( this, prefs, @@ -607,100 +516,6 @@ public void refresh() { getLibraryTabs().forEach(tab -> tab.getMainTable().getTableModel().resetFieldFormatter()); } - void openDatabases(List parserResults) { - final List failed = new ArrayList<>(); - final List toOpenTab = new ArrayList<>(); - - // Remove invalid databases - List invalidDatabases = parserResults.stream() - .filter(ParserResult::isInvalid) - .toList(); - failed.addAll(invalidDatabases); - parserResults.removeAll(invalidDatabases); - - // passed file (we take the first one) should be focused - Path focusedFile = parserResults.stream() - .findFirst() - .flatMap(ParserResult::getPath) - .orElse(prefs.getGuiPreferences() - .getLastFocusedFile()) - .toAbsolutePath(); - - // Add all bibDatabases databases to the frame: - boolean first = false; - for (ParserResult parserResult : parserResults) { - // Define focused tab - if (parserResult.getPath().filter(path -> path.toAbsolutePath().equals(focusedFile)).isPresent()) { - first = true; - } - - if (parserResult.getDatabase().isShared()) { - try { - OpenDatabaseAction.openSharedDatabase( - parserResult, - this, - dialogService, - prefs, - stateManager, - entryTypesManager, - fileUpdateMonitor, - undoManager, - taskExecutor); - } catch (SQLException | - DatabaseNotSupportedException | - InvalidDBMSConnectionPropertiesException | - NotASharedDatabaseException e) { - LOGGER.error("Connection error", e); - dialogService.showErrorDialogAndWait( - Localization.lang("Connection error"), - Localization.lang("A local copy will be opened."), - e); - toOpenTab.add(parserResult); - } - } else if (parserResult.toOpenTab()) { - // things to be appended to an opened tab should be done after opening all tabs - // add them to the list - toOpenTab.add(parserResult); - } else { - addTab(parserResult, first); - first = false; - } - } - - // finally add things to the currently opened tab - for (ParserResult parserResult : toOpenTab) { - addTab(parserResult, first); - first = false; - } - - for (ParserResult parserResult : failed) { - String message = Localization.lang("Error opening file '%0'", - parserResult.getPath().map(Path::toString).orElse("(File name unknown)")) + "\n" + - parserResult.getErrorMessage(); - dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), message); - } - - // Display warnings, if any - for (ParserResult parserResult : parserResults) { - if (parserResult.hasWarnings()) { - ParserResultWarningDialog.showParserResultWarningDialog(parserResult, dialogService); - getLibraryTabs().stream() - .filter(tab -> parserResult.getDatabase().equals(tab.getDatabase())) - .findAny() - .ifPresent(this::showLibraryTab); - } - } - - // After adding the databases, go through each and see if - // any post open actions need to be done. For instance, checking - // if we found new entry types that can be imported, or checking - // if the database contents should be modified due to new features - // in this version of JabRef. - parserResults.forEach(pr -> OpenDatabaseAction.performPostOpenActions(pr, dialogService)); - - LOGGER.debug("Finished adding panels"); - } - public void openLastEditedDatabases() { List lastFiles = prefs.getGuiPreferences().getLastFilesOpened(); if (lastFiles.isEmpty()) { @@ -710,122 +525,12 @@ public void openLastEditedDatabases() { getOpenDatabaseAction().openFiles(lastFiles); } - public FileHistoryMenu getFileHistory() { - return fileHistory; - } - public Stage getMainStage() { return mainStage; } - /** - * Handles commands submitted by the command line or by the remote host to be executed in the ui - * Needs to run in a certain order. E.g. databases have to be loaded before selecting an entry. - * - * @param uiCommands to be handled - */ public void handleUiCommands(List uiCommands) { - LOGGER.debug("Handling UI commands {}", uiCommands); - if (uiCommands.isEmpty()) { - return; - } - - // Handle blank workspace - boolean blank = uiCommands.stream().anyMatch(UiCommand.BlankWorkspace.class::isInstance); - - // Handle OpenDatabases - if (!blank) { - uiCommands.stream() - .filter(UiCommand.OpenDatabases.class::isInstance) - .map(UiCommand.OpenDatabases.class::cast) - .forEach(command -> openDatabases(command.parserResults())); - } - - // Handle jumpToEntry - uiCommands.stream() - .filter(UiCommand.JumpToEntryKey.class::isInstance) - .map(UiCommand.JumpToEntryKey.class::cast) - .map(UiCommand.JumpToEntryKey::citationKey) - .filter(Objects::nonNull) - .findAny().ifPresent(entryKey -> { - LOGGER.debug("Jump to entry {} requested", entryKey); - // tabs must be present and contents async loaded for an entry to be selected - waitForLoadingFinished(() -> jumpToEntry(entryKey)); - }); - } - - private void jumpToEntry(String entryKey) { - // check current library tab first - LibraryTab currentLibraryTab = getCurrentLibraryTab(); - List sortedTabs = getLibraryTabs().stream() - .sorted(Comparator.comparing(tab -> tab != currentLibraryTab)) - .toList(); - for (LibraryTab libraryTab : sortedTabs) { - Optional bibEntry = libraryTab.getDatabase() - .getEntries().stream() - .filter(entry -> entry.getCitationKey().orElse("") - .equals(entryKey)) - .findAny(); - if (bibEntry.isPresent()) { - LOGGER.debug("Found entry {} in library tab {}", entryKey, libraryTab); - libraryTab.clearAndSelect(bibEntry.get()); - showLibraryTab(libraryTab); - break; - } - } - - LOGGER.trace("End of loop"); - - if (stateManager.getSelectedEntries().isEmpty()) { - dialogService.notify(Localization.lang("Citation key '%0' to select not found in open libraries.", entryKey)); - } - } - - private void waitForLoadingFinished(Runnable runnable) { - LOGGER.trace("Waiting for all tabs being loaded"); - - CompletableFuture future = new CompletableFuture<>(); - - List loadings = getLibraryTabs().stream() - .map(LibraryTab::getLoading) - .collect(Collectors.toList()); - - // Create a listener for each observable - ChangeListener listener = (observable, oldValue, newValue) -> { - if (observable != null) { - loadings.remove(observable); - } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Count of loading tabs: {}", loadings.size()); - LOGGER.trace("Count of loading tabs really true: {}", loadings.stream().filter(ObservableBooleanValue::get).count()); - } - for (ObservableBooleanValue obs : loadings) { - if (obs.get()) { - // Exit the listener if any of the observables is still true - return; - } - } - // All observables are false, complete the future - LOGGER.trace("Future completed"); - future.complete(null); - }; - - for (ObservableBooleanValue obs : loadings) { - obs.addListener(listener); - } - - LOGGER.trace("Fire once"); - // Due to concurrency, it might be that the observables are already false, so we trigger one evaluation - listener.changed(null, null, false); - LOGGER.trace("Waiting for state changes..."); - - future.thenRun(() -> { - LOGGER.debug("All tabs loaded. Jumping to entry."); - for (ObservableBooleanValue obs : loadings) { - obs.removeListener(listener); - } - runnable.run(); - }); + viewModel.handleUiCommands(uiCommands); } /** @@ -841,7 +546,7 @@ public CloseAction(JabRefFrame frame) { @Override public void execute() { - if (frame.close()) { + if (frame.viewModel.close()) { frame.mainStage.close(); } } @@ -873,7 +578,7 @@ public void execute() { LOGGER.error("No library tab to close"); return; } - tabContainer.closeCurrentTab(); + tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); } else { tabContainer.closeTab(libraryTab); } diff --git a/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java b/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java new file mode 100644 index 00000000000..94d52b6617b --- /dev/null +++ b/src/main/java/org/jabref/gui/frame/JabRefFrameViewModel.java @@ -0,0 +1,381 @@ +package org.jabref.gui.frame; + +import java.nio.file.Path; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import javax.swing.undo.UndoManager; + +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableBooleanValue; +import javafx.scene.control.ButtonType; + +import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; +import org.jabref.gui.LibraryTabContainer; +import org.jabref.gui.StateManager; +import org.jabref.gui.importer.ImportEntriesDialog; +import org.jabref.gui.importer.ParserResultWarningDialog; +import org.jabref.gui.importer.actions.OpenDatabaseAction; +import org.jabref.gui.util.BackgroundTask; +import org.jabref.gui.util.TaskExecutor; +import org.jabref.logic.UiCommand; +import org.jabref.logic.importer.ImportCleanup; +import org.jabref.logic.importer.ParserResult; +import org.jabref.logic.l10n.Localization; +import org.jabref.logic.shared.DatabaseNotSupportedException; +import org.jabref.logic.shared.exception.InvalidDBMSConnectionPropertiesException; +import org.jabref.logic.shared.exception.NotASharedDatabaseException; +import org.jabref.model.database.BibDatabaseContext; +import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.BibEntryTypesManager; +import org.jabref.model.util.FileUpdateMonitor; +import org.jabref.preferences.PreferencesService; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class JabRefFrameViewModel { + private static final Logger LOGGER = LoggerFactory.getLogger(JabRefFrameViewModel.class); + + private final PreferencesService prefs; + private final StateManager stateManager; + private final DialogService dialogService; + private final LibraryTabContainer tabContainer; + private final BibEntryTypesManager entryTypesManager; + private final FileUpdateMonitor fileUpdateMonitor; + private final UndoManager undoManager; + private final TaskExecutor taskExecutor; + + public JabRefFrameViewModel(PreferencesService preferencesService, + StateManager stateManager, + DialogService dialogService, + LibraryTabContainer tabContainer, + BibEntryTypesManager entryTypesManager, + FileUpdateMonitor fileUpdateMonitor, + UndoManager undoManager, + TaskExecutor taskExecutor) { + this.prefs = preferencesService; + this.stateManager = stateManager; + this.dialogService = dialogService; + this.tabContainer = tabContainer; + this.entryTypesManager = entryTypesManager; + this.fileUpdateMonitor = fileUpdateMonitor; + this.undoManager = undoManager; + this.taskExecutor = taskExecutor; + } + + void storeLastOpenedFiles(List filenames, Path focusedDatabase) { + if (prefs.getWorkspacePreferences().shouldOpenLastEdited()) { + // Here we store the names of all current files. If there is no current file, we remove any + // previously stored filename. + if (filenames.isEmpty()) { + prefs.getGuiPreferences().getLastFilesOpened().clear(); + } else { + prefs.getGuiPreferences().setLastFilesOpened(filenames); + prefs.getGuiPreferences().setLastFocusedFile(focusedDatabase); + } + } + } + + /** + * Quit JabRef + * + * @return true if the user chose to quit; false otherwise + */ + public boolean close() { + // Ask if the user really wants to close, if there are still background tasks running + // The background tasks may make changes themselves that need saving. + if (stateManager.getAnyTasksThatWillNotBeRecoveredRunning().getValue()) { + Optional shouldClose = dialogService.showBackgroundProgressDialogAndWait( + Localization.lang("Please wait..."), + Localization.lang("Waiting for background tasks to finish. Quit anyway?"), + stateManager); + if (!(shouldClose.isPresent() && (shouldClose.get() == ButtonType.YES))) { + return false; + } + } + + // Read the opened and focused databases before closing them + List openedLibraries = tabContainer.getLibraryTabs().stream() + .map(LibraryTab::getBibDatabaseContext) + .map(BibDatabaseContext::getDatabasePath) + .flatMap(Optional::stream) + .toList(); + Path focusedLibraries = Optional.ofNullable(tabContainer.getCurrentLibraryTab()) + .map(LibraryTab::getBibDatabaseContext) + .flatMap(BibDatabaseContext::getDatabasePath) + .orElse(null); + + // Then ask if the user really wants to close, if the library has not been saved since last save. + if (!tabContainer.closeTabs(tabContainer.getLibraryTabs())) { + return false; + } + + storeLastOpenedFiles(openedLibraries, focusedLibraries); // store only if successfully having closed the libraries + + ProcessingLibraryDialog processingLibraryDialog = new ProcessingLibraryDialog(dialogService); + processingLibraryDialog.showAndWait(tabContainer.getLibraryTabs()); + + return true; + } + + /** + * Handles commands submitted by the command line or by the remote host to be executed in the ui + * Needs to run in a certain order. E.g. databases have to be loaded before selecting an entry. + * + * @param uiCommands to be handled + */ + public void handleUiCommands(List uiCommands) { + LOGGER.debug("Handling UI commands {}", uiCommands); + if (uiCommands.isEmpty()) { + return; + } + + // Handle blank workspace + boolean blank = uiCommands.stream().anyMatch(UiCommand.BlankWorkspace.class::isInstance); + + // Handle OpenDatabases + if (!blank) { + uiCommands.stream() + .filter(UiCommand.OpenDatabases.class::isInstance) + .map(UiCommand.OpenDatabases.class::cast) + .forEach(command -> openDatabases(command.parserResults())); + } + + // Handle jumpToEntry + uiCommands.stream() + .filter(UiCommand.JumpToEntryKey.class::isInstance) + .map(UiCommand.JumpToEntryKey.class::cast) + .map(UiCommand.JumpToEntryKey::citationKey) + .filter(Objects::nonNull) + .findAny().ifPresent(entryKey -> { + LOGGER.debug("Jump to entry {} requested", entryKey); + // tabs must be present and contents async loaded for an entry to be selected + waitForLoadingFinished(() -> jumpToEntry(entryKey)); + }); + } + + private void openDatabases(List parserResults) { + final List failed = new ArrayList<>(); + final List toOpenTab = new ArrayList<>(); + + // Remove invalid databases + List invalidDatabases = parserResults.stream() + .filter(ParserResult::isInvalid) + .toList(); + failed.addAll(invalidDatabases); + parserResults.removeAll(invalidDatabases); + + // passed file (we take the first one) should be focused + Path focusedFile = parserResults.stream() + .findFirst() + .flatMap(ParserResult::getPath) + .orElse(prefs.getGuiPreferences() + .getLastFocusedFile()) + .toAbsolutePath(); + + // Add all bibDatabases databases to the frame: + boolean first = false; + for (ParserResult parserResult : parserResults) { + // Define focused tab + if (parserResult.getPath().filter(path -> path.toAbsolutePath().equals(focusedFile)).isPresent()) { + first = true; + } + + if (parserResult.getDatabase().isShared()) { + try { + OpenDatabaseAction.openSharedDatabase( + parserResult, + tabContainer, + dialogService, + prefs, + stateManager, + entryTypesManager, + fileUpdateMonitor, + undoManager, + taskExecutor); + } catch ( + SQLException | + DatabaseNotSupportedException | + InvalidDBMSConnectionPropertiesException | + NotASharedDatabaseException e) { + LOGGER.error("Connection error", e); + dialogService.showErrorDialogAndWait( + Localization.lang("Connection error"), + Localization.lang("A local copy will be opened."), + e); + toOpenTab.add(parserResult); + } + } else if (parserResult.toOpenTab()) { + // things to be appended to an opened tab should be done after opening all tabs + // add them to the list + toOpenTab.add(parserResult); + } else { + addParserResult(parserResult, first); + first = false; + } + } + + // finally add things to the currently opened tab + for (ParserResult parserResult : toOpenTab) { + addParserResult(parserResult, first); + first = false; + } + + for (ParserResult parserResult : failed) { + String message = Localization.lang("Error opening file '%0'", + parserResult.getPath().map(Path::toString).orElse("(File name unknown)")) + "\n" + + parserResult.getErrorMessage(); + dialogService.showErrorDialogAndWait(Localization.lang("Error opening file"), message); + } + + // Display warnings, if any + for (ParserResult parserResult : parserResults) { + if (parserResult.hasWarnings()) { + ParserResultWarningDialog.showParserResultWarningDialog(parserResult, dialogService); + tabContainer.getLibraryTabs().stream() + .filter(tab -> parserResult.getDatabase().equals(tab.getDatabase())) + .findAny() + .ifPresent(tabContainer::showLibraryTab); + } + } + + // After adding the databases, go through each and see if + // any post open actions need to be done. For instance, checking + // if we found new entry types that can be imported, or checking + // if the database contents should be modified due to new features + // in this version of JabRef. + parserResults.forEach(pr -> OpenDatabaseAction.performPostOpenActions(pr, dialogService)); + + LOGGER.debug("Finished adding panels"); + } + + /** + * Should be called when a user asks JabRef at the command line + * i) to import a file or + * ii) to open a .bib file + */ + private void addParserResult(ParserResult parserResult, boolean raisePanel) { + if (parserResult.toOpenTab()) { + LOGGER.trace("Adding the entries to the open tab."); + LibraryTab libraryTab = tabContainer.getCurrentLibraryTab(); + if (libraryTab == null) { + LOGGER.debug("No open tab found to add entries to. Creating a new tab."); + tabContainer.addTab(parserResult.getDatabaseContext(), raisePanel); + } else { + addImportedEntries(libraryTab, parserResult); + } + } else { + // only add tab if library is not already open + Optional libraryTab = tabContainer.getLibraryTabs().stream() + .filter(p -> p.getBibDatabaseContext() + .getDatabasePath() + .equals(parserResult.getPath())) + .findFirst(); + + if (libraryTab.isPresent()) { + tabContainer.showLibraryTab(libraryTab.get()); + } else { + // On this place, a tab is added after loading using the command line + // This takes a different execution path than loading a library using the GUI + tabContainer.addTab(parserResult.getDatabaseContext(), raisePanel); + } + } + } + + private void waitForLoadingFinished(Runnable runnable) { + LOGGER.trace("Waiting for all tabs being loaded"); + + CompletableFuture future = new CompletableFuture<>(); + + List loadings = tabContainer.getLibraryTabs().stream() + .map(LibraryTab::getLoading) + .collect(Collectors.toList()); + + // Create a listener for each observable + ChangeListener listener = (observable, oldValue, newValue) -> { + if (observable != null) { + loadings.remove(observable); + } + if (LOGGER.isTraceEnabled()) { + LOGGER.trace("Count of loading tabs: {}", loadings.size()); + LOGGER.trace("Count of loading tabs really true: {}", loadings.stream().filter(ObservableBooleanValue::get).count()); + } + for (ObservableBooleanValue obs : loadings) { + if (obs.get()) { + // Exit the listener if any of the observables is still true + return; + } + } + // All observables are false, complete the future + LOGGER.trace("Future completed"); + future.complete(null); + }; + + for (ObservableBooleanValue obs : loadings) { + obs.addListener(listener); + } + + LOGGER.trace("Fire once"); + // Due to concurrency, it might be that the observables are already false, so we trigger one evaluation + listener.changed(null, null, false); + LOGGER.trace("Waiting for state changes..."); + + future.thenRun(() -> { + LOGGER.debug("All tabs loaded. Jumping to entry."); + for (ObservableBooleanValue obs : loadings) { + obs.removeListener(listener); + } + runnable.run(); + }); + } + + private void jumpToEntry(String entryKey) { + // check current library tab first + LibraryTab currentLibraryTab = tabContainer.getCurrentLibraryTab(); + List sortedTabs = tabContainer.getLibraryTabs().stream() + .sorted(Comparator.comparing(tab -> tab != currentLibraryTab)) + .toList(); + for (LibraryTab libraryTab : sortedTabs) { + Optional bibEntry = libraryTab.getDatabase() + .getEntries().stream() + .filter(entry -> entry.getCitationKey().orElse("") + .equals(entryKey)) + .findAny(); + if (bibEntry.isPresent()) { + LOGGER.debug("Found entry {} in library tab {}", entryKey, libraryTab); + libraryTab.clearAndSelect(bibEntry.get()); + tabContainer.showLibraryTab(libraryTab); + break; + } + } + + LOGGER.trace("End of loop"); + + if (stateManager.getSelectedEntries().isEmpty()) { + dialogService.notify(Localization.lang("Citation key '%0' to select not found in open libraries.", entryKey)); + } + } + + /** + * Opens the import inspection dialog to let the user decide which of the given entries to import. + * + * @param tab The LibraryTab to add to. + * @param parserResult The entries to add. + */ + void addImportedEntries(final LibraryTab tab, final ParserResult parserResult) { + BackgroundTask task = BackgroundTask.wrap(() -> parserResult); + ImportCleanup cleanup = ImportCleanup.targeting(tab.getBibDatabaseContext().getMode()); + cleanup.doPostCleanup(parserResult.getDatabase().getEntries()); + ImportEntriesDialog dialog = new ImportEntriesDialog(tab.getBibDatabaseContext(), task); + dialog.setTitle(Localization.lang("Import")); + dialogService.showCustomDialogAndWait(dialog); + } +} diff --git a/src/main/java/org/jabref/gui/MainMenu.java b/src/main/java/org/jabref/gui/frame/MainMenu.java similarity index 95% rename from src/main/java/org/jabref/gui/MainMenu.java rename to src/main/java/org/jabref/gui/frame/MainMenu.java index b145c678dfe..c27e45537d7 100644 --- a/src/main/java/org/jabref/gui/MainMenu.java +++ b/src/main/java/org/jabref/gui/frame/MainMenu.java @@ -1,4 +1,6 @@ -package org.jabref.gui; +package org.jabref.gui.frame; + +import java.util.function.Supplier; import javax.swing.undo.UndoManager; @@ -8,6 +10,10 @@ import javafx.scene.control.MenuItem; import javafx.scene.control.SeparatorMenuItem; +import org.jabref.gui.ClipBoardManager; +import org.jabref.gui.DialogService; +import org.jabref.gui.Globals; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.auximport.NewSubLibraryAction; @@ -40,6 +46,7 @@ import org.jabref.gui.importer.ImportCommand; import org.jabref.gui.importer.NewDatabaseAction; import org.jabref.gui.importer.NewEntryAction; +import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.gui.importer.fetcher.LookupIdentifierAction; import org.jabref.gui.integrity.IntegrityCheckAction; import org.jabref.gui.journals.AbbreviateAction; @@ -75,6 +82,7 @@ public class MainMenu extends MenuBar { private final JabRefFrame frame; + private final FileHistoryMenu fileHistoryMenu; private final SidePane sidePane; private final PushToApplicationCommand pushToApplicationCommand; private final PreferencesService preferencesService; @@ -86,8 +94,10 @@ public class MainMenu extends MenuBar { private final BibEntryTypesManager entryTypesManager; private final UndoManager undoManager; private final ClipBoardManager clipBoardManager; + private final Supplier openDatabaseActionSupplier; public MainMenu(JabRefFrame frame, + FileHistoryMenu fileHistoryMenu, SidePane sidePane, PushToApplicationCommand pushToApplicationCommand, PreferencesService preferencesService, @@ -98,8 +108,10 @@ public MainMenu(JabRefFrame frame, JournalAbbreviationRepository abbreviationRepository, BibEntryTypesManager entryTypesManager, UndoManager undoManager, - ClipBoardManager clipBoardManager) { + ClipBoardManager clipBoardManager, + Supplier openDatabaseActionSupplier) { this.frame = frame; + this.fileHistoryMenu = fileHistoryMenu; this.sidePane = sidePane; this.pushToApplicationCommand = pushToApplicationCommand; this.preferencesService = preferencesService; @@ -111,6 +123,7 @@ public MainMenu(JabRefFrame frame, this.entryTypesManager = entryTypesManager; this.undoManager = undoManager; this.clipBoardManager = clipBoardManager; + this.openDatabaseActionSupplier = openDatabaseActionSupplier; createMenu(); } @@ -128,8 +141,8 @@ private void createMenu() { file.getItems().addAll( factory.createMenuItem(StandardActions.NEW_LIBRARY, new NewDatabaseAction(frame, preferencesService)), - factory.createMenuItem(StandardActions.OPEN_LIBRARY, frame.getOpenDatabaseAction()), - frame.getFileHistory(), + factory.createMenuItem(StandardActions.OPEN_LIBRARY, openDatabaseActionSupplier.get()), + fileHistoryMenu, factory.createMenuItem(StandardActions.SAVE_LIBRARY, new SaveAction(SaveAction.SaveMethod.SAVE, frame::getCurrentLibraryTab, dialogService, preferencesService, stateManager)), factory.createMenuItem(StandardActions.SAVE_LIBRARY_AS, new SaveAction(SaveAction.SaveMethod.SAVE_AS, frame::getCurrentLibraryTab, dialogService, preferencesService, stateManager)), factory.createMenuItem(StandardActions.SAVE_ALL, new SaveAllAction(frame::getLibraryTabs, preferencesService, dialogService)), @@ -278,9 +291,9 @@ private void createMenu() { new SeparatorMenuItem(), // Systematic Literature Review (SLR) - factory.createMenuItem(StandardActions.START_NEW_STUDY, new StartNewStudyAction(frame, frame::getOpenDatabaseAction, fileUpdateMonitor, taskExecutor, preferencesService, stateManager, dialogService)), + factory.createMenuItem(StandardActions.START_NEW_STUDY, new StartNewStudyAction(frame, openDatabaseActionSupplier, fileUpdateMonitor, taskExecutor, preferencesService, stateManager, dialogService)), factory.createMenuItem(StandardActions.EDIT_EXISTING_STUDY, new EditExistingStudyAction(dialogService, stateManager)), - factory.createMenuItem(StandardActions.UPDATE_SEARCH_RESULTS_OF_STUDY, new ExistingStudySearchAction(frame, frame.getOpenDatabaseAction(), dialogService, fileUpdateMonitor, taskExecutor, preferencesService, stateManager)), + factory.createMenuItem(StandardActions.UPDATE_SEARCH_RESULTS_OF_STUDY, new ExistingStudySearchAction(frame, openDatabaseActionSupplier, dialogService, fileUpdateMonitor, taskExecutor, preferencesService, stateManager)), new SeparatorMenuItem(), diff --git a/src/main/java/org/jabref/gui/MainToolBar.java b/src/main/java/org/jabref/gui/frame/MainToolBar.java similarity index 98% rename from src/main/java/org/jabref/gui/MainToolBar.java rename to src/main/java/org/jabref/gui/frame/MainToolBar.java index 1882b6a4ecf..fabdf849137 100644 --- a/src/main/java/org/jabref/gui/MainToolBar.java +++ b/src/main/java/org/jabref/gui/frame/MainToolBar.java @@ -1,4 +1,4 @@ -package org.jabref.gui; +package org.jabref.gui.frame; import javafx.concurrent.Task; import javafx.geometry.Orientation; @@ -13,6 +13,10 @@ import javafx.scene.layout.Region; import javafx.scene.shape.Rectangle; +import org.jabref.gui.DialogService; +import org.jabref.gui.Globals; +import org.jabref.gui.LibraryTabContainer; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.StandardActions; diff --git a/src/main/java/org/jabref/gui/OpenConsoleAction.java b/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java similarity index 93% rename from src/main/java/org/jabref/gui/OpenConsoleAction.java rename to src/main/java/org/jabref/gui/frame/OpenConsoleAction.java index acada1d5629..e0864e688fc 100644 --- a/src/main/java/org/jabref/gui/OpenConsoleAction.java +++ b/src/main/java/org/jabref/gui/frame/OpenConsoleAction.java @@ -1,9 +1,11 @@ -package org.jabref.gui; +package org.jabref.gui.frame; import java.io.IOException; import java.util.Optional; import java.util.function.Supplier; +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.desktop.JabRefDesktop; @@ -24,7 +26,7 @@ public class OpenConsoleAction extends SimpleCommand { /** * Creates a command that opens the console at the path of the supplied database, * or defaults to the active database. Use - * {@link #OpenConsoleAction(StateManager, PreferencesService)} if not supplying + * {@link #OpenConsoleAction(StateManager, PreferencesService, DialogService)} if not supplying * another database. */ public OpenConsoleAction(Supplier databaseContext, StateManager stateManager, PreferencesService preferencesService, DialogService dialogService) { diff --git a/src/main/java/org/jabref/gui/ProcessingLibraryDialog.java b/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java similarity index 93% rename from src/main/java/org/jabref/gui/ProcessingLibraryDialog.java rename to src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java index 52ff8512d2c..72d0f7d9d54 100644 --- a/src/main/java/org/jabref/gui/ProcessingLibraryDialog.java +++ b/src/main/java/org/jabref/gui/frame/ProcessingLibraryDialog.java @@ -1,9 +1,11 @@ -package org.jabref.gui; +package org.jabref.gui.frame; import java.util.List; import javafx.concurrent.Task; +import org.jabref.gui.DialogService; +import org.jabref.gui.LibraryTab; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.l10n.Localization; diff --git a/src/main/java/org/jabref/gui/SendAsEMailAction.java b/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java similarity index 97% rename from src/main/java/org/jabref/gui/SendAsEMailAction.java rename to src/main/java/org/jabref/gui/frame/SendAsEMailAction.java index 66c39b64c0b..863f037a078 100644 --- a/src/main/java/org/jabref/gui/SendAsEMailAction.java +++ b/src/main/java/org/jabref/gui/frame/SendAsEMailAction.java @@ -1,4 +1,4 @@ -package org.jabref.gui; +package org.jabref.gui.frame; import java.awt.Desktop; import java.io.IOException; @@ -9,6 +9,8 @@ import java.util.List; import org.jabref.architecture.AllowedToUseAwt; +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.util.BackgroundTask; diff --git a/src/main/java/org/jabref/gui/SendAsKindleEmailAction.java b/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java similarity index 92% rename from src/main/java/org/jabref/gui/SendAsKindleEmailAction.java rename to src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java index f96944a4fa2..a29403442cf 100644 --- a/src/main/java/org/jabref/gui/SendAsKindleEmailAction.java +++ b/src/main/java/org/jabref/gui/frame/SendAsKindleEmailAction.java @@ -1,5 +1,7 @@ -package org.jabref.gui; +package org.jabref.gui.frame; +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.l10n.Localization; diff --git a/src/main/java/org/jabref/gui/SendAsStandardEmailAction.java b/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java similarity index 96% rename from src/main/java/org/jabref/gui/SendAsStandardEmailAction.java rename to src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java index a60fb256820..1a03fa7a04c 100644 --- a/src/main/java/org/jabref/gui/SendAsStandardEmailAction.java +++ b/src/main/java/org/jabref/gui/frame/SendAsStandardEmailAction.java @@ -1,9 +1,11 @@ -package org.jabref.gui; +package org.jabref.gui.frame; import java.io.IOException; import java.io.StringWriter; import java.util.List; +import org.jabref.gui.DialogService; +import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.util.TaskExecutor; import org.jabref.logic.bibtex.BibEntryWriter; diff --git a/src/main/java/org/jabref/gui/importer/NewEntryAction.java b/src/main/java/org/jabref/gui/importer/NewEntryAction.java index 4737211f7fb..39952699dfd 100644 --- a/src/main/java/org/jabref/gui/importer/NewEntryAction.java +++ b/src/main/java/org/jabref/gui/importer/NewEntryAction.java @@ -6,12 +6,12 @@ import java.util.function.Supplier; import org.jabref.gui.DialogService; -import org.jabref.gui.EntryTypeView; import org.jabref.gui.LibraryTab; import org.jabref.gui.StateManager; -import org.jabref.gui.Telemetry; import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; +import org.jabref.gui.entrytype.EntryTypeView; +import org.jabref.gui.telemetry.Telemetry; import org.jabref.model.entry.BibEntry; import org.jabref.model.entry.types.EntryType; import org.jabref.preferences.PreferencesService; diff --git a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java index f607f90a4ed..404612b13b4 100644 --- a/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java +++ b/src/main/java/org/jabref/gui/importer/actions/OpenDatabaseAction.java @@ -17,11 +17,11 @@ import org.jabref.gui.LibraryTab; import org.jabref.gui.LibraryTabContainer; import org.jabref.gui.StateManager; -import org.jabref.gui.Telemetry; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.autosaveandbackup.BackupManager; import org.jabref.gui.dialogs.BackupUIManager; import org.jabref.gui.shared.SharedDatabaseUIManager; +import org.jabref.gui.telemetry.Telemetry; import org.jabref.gui.undo.CountingUndoManager; import org.jabref.gui.util.BackgroundTask; import org.jabref.gui.util.DefaultTaskExecutor; @@ -180,7 +180,7 @@ public void openFiles(List filesToOpen) { /** * This is the real file opening. Should be called via {@link #openFile(Path)} * - * Similar method: {@link org.jabref.gui.JabRefFrame#addTab(org.jabref.model.database.BibDatabaseContext, boolean)}. + * Similar method: {@link org.jabref.gui.frame.JabRefFrame#addTab(org.jabref.model.database.BibDatabaseContext, boolean)}. * * @param file the file, may be NOT null, but may not be existing */ diff --git a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java index 985bede5b5a..3829c2b155b 100644 --- a/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java +++ b/src/main/java/org/jabref/gui/importer/fetcher/WebSearchPaneViewModel.java @@ -14,8 +14,8 @@ import org.jabref.gui.DialogService; import org.jabref.gui.StateManager; -import org.jabref.gui.Telemetry; import org.jabref.gui.importer.ImportEntriesDialog; +import org.jabref.gui.telemetry.Telemetry; import org.jabref.gui.util.BackgroundTask; import org.jabref.logic.importer.CompositeIdFetcher; import org.jabref.logic.importer.ParserResult; diff --git a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java index f19ad726454..2cf4d8cd1e1 100644 --- a/src/main/java/org/jabref/gui/maintable/RightClickMenu.java +++ b/src/main/java/org/jabref/gui/maintable/RightClickMenu.java @@ -10,14 +10,14 @@ import org.jabref.gui.ClipBoardManager; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTab; -import org.jabref.gui.SendAsKindleEmailAction; -import org.jabref.gui.SendAsStandardEmailAction; import org.jabref.gui.StateManager; import org.jabref.gui.actions.ActionFactory; import org.jabref.gui.actions.StandardActions; import org.jabref.gui.edit.CopyMoreAction; import org.jabref.gui.edit.EditAction; import org.jabref.gui.exporter.ExportToClipboardAction; +import org.jabref.gui.frame.SendAsKindleEmailAction; +import org.jabref.gui.frame.SendAsStandardEmailAction; import org.jabref.gui.keyboard.KeyBindingRepository; import org.jabref.gui.linkedfile.AttachFileAction; import org.jabref.gui.linkedfile.AttachFileFromURLAction; diff --git a/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java index 2b08e7b9ed3..8d7a70f3e9e 100644 --- a/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java +++ b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeAction.java @@ -4,9 +4,6 @@ import javax.swing.undo.UndoManager; -import javafx.beans.property.ReadOnlyStringWrapper; - -import org.jabref.gui.EntryTypeView; import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.undo.NamedCompound; import org.jabref.gui.undo.UndoableChangeType; @@ -19,13 +16,11 @@ public class ChangeEntryTypeAction extends SimpleCommand { private final EntryType type; private final List entries; private final UndoManager undoManager; - private final ReadOnlyStringWrapper statusMessageProperty; public ChangeEntryTypeAction(EntryType type, List entries, UndoManager undoManager) { this.type = type; this.entries = entries; this.undoManager = undoManager; - this.statusMessageProperty = new ReadOnlyStringWrapper(EntryTypeView.getDescription(type)); } @Override diff --git a/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java b/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java index a9a834071d8..63601bd4367 100644 --- a/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java +++ b/src/main/java/org/jabref/gui/preferences/general/GeneralTabViewModel.java @@ -24,6 +24,7 @@ import org.jabref.gui.desktop.JabRefDesktop; import org.jabref.gui.preferences.PreferenceTabViewModel; import org.jabref.gui.remote.CLIMessageHandler; +import org.jabref.gui.telemetry.TelemetryPreferences; import org.jabref.gui.theme.Theme; import org.jabref.gui.theme.ThemeTypes; import org.jabref.gui.util.DirectoryDialogConfiguration; @@ -43,7 +44,6 @@ import org.jabref.preferences.LibraryPreferences; import org.jabref.preferences.MergeDialogPreferences; import org.jabref.preferences.PreferencesService; -import org.jabref.preferences.TelemetryPreferences; import org.jabref.preferences.WorkspacePreferences; import de.saxsys.mvvmfx.utils.validation.CompositeValidator; diff --git a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java index 597c81de14d..f83426b0a9e 100644 --- a/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java +++ b/src/main/java/org/jabref/gui/shared/SharedDatabaseUIManager.java @@ -86,7 +86,7 @@ public void listen(ConnectionLostEvent connectionLostEvent) { if (answer.isPresent()) { if (answer.get().equals(reconnect)) { - tabContainer.closeCurrentTab(); + tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); dialogService.showCustomDialogAndWait(new SharedDatabaseLoginDialogView(tabContainer)); } else if (answer.get().equals(workOffline)) { connectionLostEvent.getBibDatabaseContext().convertToLocalDatabase(); @@ -94,7 +94,7 @@ public void listen(ConnectionLostEvent connectionLostEvent) { dialogService.notify(Localization.lang("Working offline.")); } } else { - tabContainer.closeCurrentTab(); + tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); } } diff --git a/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java b/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java index 2d174f724a3..a458c162c70 100644 --- a/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java +++ b/src/main/java/org/jabref/gui/slr/ExistingStudySearchAction.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.nio.file.Path; +import java.util.function.Supplier; import org.jabref.gui.DialogService; import org.jabref.gui.LibraryTabContainer; @@ -36,22 +37,22 @@ public class ExistingStudySearchAction extends SimpleCommand { private final FileUpdateMonitor fileUpdateMonitor; private final TaskExecutor taskExecutor; private final LibraryTabContainer tabContainer; - private final OpenDatabaseAction openDatabaseAction; + private final Supplier openDatabaseActionSupplier; /** * @param tabContainer Required to close the tab before the study is updated - * @param openDatabaseAction Required to open the tab after the study is exectued + * @param openDatabaseActionSupplier Required to open the tab after the study is executed */ public ExistingStudySearchAction( LibraryTabContainer tabContainer, - OpenDatabaseAction openDatabaseAction, + Supplier openDatabaseActionSupplier, DialogService dialogService, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, PreferencesService preferencesService, StateManager stateManager) { this(tabContainer, - openDatabaseAction, + openDatabaseActionSupplier, dialogService, fileUpdateMonitor, taskExecutor, @@ -62,7 +63,7 @@ public ExistingStudySearchAction( protected ExistingStudySearchAction( LibraryTabContainer tabContainer, - OpenDatabaseAction openDatabaseAction, + Supplier openDatabaseActionSupplier, DialogService dialogService, FileUpdateMonitor fileUpdateMonitor, TaskExecutor taskExecutor, @@ -70,7 +71,7 @@ protected ExistingStudySearchAction( StateManager stateManager, boolean isNew) { this.tabContainer = tabContainer; - this.openDatabaseAction = openDatabaseAction; + this.openDatabaseActionSupplier = openDatabaseActionSupplier; this.dialogService = dialogService; this.fileUpdateMonitor = fileUpdateMonitor; this.taskExecutor = taskExecutor; @@ -132,7 +133,7 @@ protected void crawl() { }) .onSuccess(unused -> { dialogService.notify(Localization.lang("Finished Searching")); - openDatabaseAction.openFile(Path.of(this.studyDirectory.toString(), Crawler.FILENAME_STUDY_RESULT_BIB)); + openDatabaseActionSupplier.get().openFile(Path.of(this.studyDirectory.toString(), Crawler.FILENAME_STUDY_RESULT_BIB)); }) .executeWith(taskExecutor); } @@ -146,6 +147,6 @@ protected void crawlPreparation(Path studyRepositoryRoot) throws IOException, Gi // The user focused an SLR // We hard close the tab // Future work: Properly close the tab (with saving, ...) - tabContainer.closeCurrentTab(); + tabContainer.closeTab(tabContainer.getCurrentLibraryTab()); } } diff --git a/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java b/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java index a356c144989..0b1fc16e6fb 100644 --- a/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java +++ b/src/main/java/org/jabref/gui/slr/StartNewStudyAction.java @@ -46,7 +46,7 @@ public StartNewStudyAction(LibraryTabContainer tabContainer, StateManager stateManager, DialogService dialogService) { super(tabContainer, - openDatabaseActionSupplier.get(), + openDatabaseActionSupplier, dialogService, fileUpdateMonitor, taskExecutor, diff --git a/src/main/java/org/jabref/gui/Telemetry.java b/src/main/java/org/jabref/gui/telemetry/Telemetry.java similarity index 89% rename from src/main/java/org/jabref/gui/Telemetry.java rename to src/main/java/org/jabref/gui/telemetry/Telemetry.java index 7af0b9d92f6..06a8759c745 100644 --- a/src/main/java/org/jabref/gui/Telemetry.java +++ b/src/main/java/org/jabref/gui/telemetry/Telemetry.java @@ -1,12 +1,13 @@ -package org.jabref.gui; +package org.jabref.gui.telemetry; import java.util.Optional; import java.util.TimerTask; +import org.jabref.gui.DialogService; +import org.jabref.gui.JabRefExecutorService; import org.jabref.gui.util.DefaultTaskExecutor; import org.jabref.logic.l10n.Localization; import org.jabref.logic.util.BuildInfo; -import org.jabref.preferences.TelemetryPreferences; public class Telemetry { private Telemetry() { @@ -24,7 +25,7 @@ public static void shutdown() { }); } - static void initTrackingNotification(DialogService dialogService, TelemetryPreferences preferences) { + public static void initTrackingNotification(DialogService dialogService, TelemetryPreferences preferences) { if (preferences.shouldAskToCollectTelemetry()) { JabRefExecutorService.INSTANCE.submit(new TimerTask() { @Override diff --git a/src/main/java/org/jabref/gui/TelemetryClient.java b/src/main/java/org/jabref/gui/telemetry/TelemetryClient.java similarity index 85% rename from src/main/java/org/jabref/gui/TelemetryClient.java rename to src/main/java/org/jabref/gui/telemetry/TelemetryClient.java index 077010f5ef6..18ee77da081 100644 --- a/src/main/java/org/jabref/gui/TelemetryClient.java +++ b/src/main/java/org/jabref/gui/telemetry/TelemetryClient.java @@ -1,4 +1,4 @@ -package org.jabref.gui; +package org.jabref.gui.telemetry; import java.util.Map; diff --git a/src/main/java/org/jabref/preferences/TelemetryPreferences.java b/src/main/java/org/jabref/gui/telemetry/TelemetryPreferences.java similarity index 97% rename from src/main/java/org/jabref/preferences/TelemetryPreferences.java rename to src/main/java/org/jabref/gui/telemetry/TelemetryPreferences.java index 4573aba3a4e..194e57616c8 100644 --- a/src/main/java/org/jabref/preferences/TelemetryPreferences.java +++ b/src/main/java/org/jabref/gui/telemetry/TelemetryPreferences.java @@ -1,4 +1,4 @@ -package org.jabref.preferences; +package org.jabref.gui.telemetry; import javafx.beans.property.BooleanProperty; import javafx.beans.property.SimpleBooleanProperty; diff --git a/src/main/java/org/jabref/gui/theme/StyleSheet.java b/src/main/java/org/jabref/gui/theme/StyleSheet.java index 4f833937d1c..19709f013ce 100644 --- a/src/main/java/org/jabref/gui/theme/StyleSheet.java +++ b/src/main/java/org/jabref/gui/theme/StyleSheet.java @@ -1,13 +1,15 @@ package org.jabref.gui.theme; import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.Optional; -import org.jabref.gui.JabRefFrame; +import org.jabref.gui.JabRefGUI; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -30,7 +32,7 @@ Path getWatchPath() { abstract void reload(); static Optional create(String name) { - Optional styleSheetUrl = Optional.ofNullable(JabRefFrame.class.getResource(name)); + Optional styleSheetUrl = Optional.ofNullable(JabRefGUI.class.getResource(name)); if (styleSheetUrl.isEmpty()) { try { @@ -44,8 +46,8 @@ static Optional create(String name) { if (styleSheetUrl.isEmpty()) { try { - return Optional.of(new StyleSheetDataUrl(new URL(EMPTY_WEBENGINE_CSS))); - } catch (MalformedURLException e) { + return Optional.of(new StyleSheetDataUrl(new URI(EMPTY_WEBENGINE_CSS).toURL())); + } catch (URISyntaxException | MalformedURLException e) { return Optional.empty(); } } else if ("file".equals(styleSheetUrl.get().getProtocol())) { diff --git a/src/main/java/org/jabref/gui/util/BaseDialog.java b/src/main/java/org/jabref/gui/util/BaseDialog.java index 721fcb377cf..df6acdee4b8 100644 --- a/src/main/java/org/jabref/gui/util/BaseDialog.java +++ b/src/main/java/org/jabref/gui/util/BaseDialog.java @@ -14,7 +14,7 @@ import org.jabref.gui.keyboard.KeyBinding; import org.jabref.gui.keyboard.KeyBindingRepository; -public class BaseDialog extends Dialog implements org.jabref.gui.Dialog { +public class BaseDialog extends Dialog { protected BaseDialog() { getDialogPane().getScene().setOnKeyPressed(event -> { diff --git a/src/main/java/org/jabref/preferences/JabRefPreferences.java b/src/main/java/org/jabref/preferences/JabRefPreferences.java index 749c77d7748..55de7b43b54 100644 --- a/src/main/java/org/jabref/preferences/JabRefPreferences.java +++ b/src/main/java/org/jabref/preferences/JabRefPreferences.java @@ -59,6 +59,7 @@ import org.jabref.gui.search.SearchDisplayMode; import org.jabref.gui.sidepane.SidePaneType; import org.jabref.gui.specialfields.SpecialFieldsPreferences; +import org.jabref.gui.telemetry.TelemetryPreferences; import org.jabref.gui.theme.Theme; import org.jabref.logic.JabRefException; import org.jabref.logic.bibtex.FieldPreferences; diff --git a/src/main/java/org/jabref/preferences/PreferencesService.java b/src/main/java/org/jabref/preferences/PreferencesService.java index 8e581f787dd..5c6f14b95f4 100644 --- a/src/main/java/org/jabref/preferences/PreferencesService.java +++ b/src/main/java/org/jabref/preferences/PreferencesService.java @@ -12,6 +12,7 @@ import org.jabref.gui.maintable.MainTablePreferences; import org.jabref.gui.maintable.NameDisplayPreferences; import org.jabref.gui.specialfields.SpecialFieldsPreferences; +import org.jabref.gui.telemetry.TelemetryPreferences; import org.jabref.logic.JabRefException; import org.jabref.logic.bibtex.FieldPreferences; import org.jabref.logic.citationkeypattern.CitationKeyPatternPreferences; diff --git a/src/test/java/org/jabref/gui/menus/FileHistoryMenuTest.java b/src/test/java/org/jabref/gui/menus/FileHistoryMenuTest.java index ee1529cc512..9f8e54b3213 100644 --- a/src/test/java/org/jabref/gui/menus/FileHistoryMenuTest.java +++ b/src/test/java/org/jabref/gui/menus/FileHistoryMenuTest.java @@ -3,6 +3,7 @@ import java.nio.file.Path; import org.jabref.gui.DialogService; +import org.jabref.gui.frame.FileHistoryMenu; import org.jabref.gui.importer.actions.OpenDatabaseAction; import org.jabref.logic.util.io.FileHistory; diff --git a/src/test/java/org/jabref/gui/util/OpenConsoleActionTest.java b/src/test/java/org/jabref/gui/util/OpenConsoleActionTest.java index 5ec37c2ee0e..c015f69c1e2 100644 --- a/src/test/java/org/jabref/gui/util/OpenConsoleActionTest.java +++ b/src/test/java/org/jabref/gui/util/OpenConsoleActionTest.java @@ -2,8 +2,8 @@ import java.util.Optional; -import org.jabref.gui.OpenConsoleAction; import org.jabref.gui.StateManager; +import org.jabref.gui.frame.OpenConsoleAction; import org.jabref.model.database.BibDatabaseContext; import org.jabref.preferences.PreferencesService;