diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java index 077e8388d95..5a122eb28a4 100644 --- a/src/main/java/org/jabref/gui/JabRefFrame.java +++ b/src/main/java/org/jabref/gui/JabRefFrame.java @@ -831,7 +831,7 @@ private MenuBar createMenu() { new SeparatorMenuItem(), - factory.createMenuItem(StandardActions.SHOW_PDF_VIEWER, new ShowDocumentViewerAction()), + factory.createMenuItem(StandardActions.SHOW_PDF_VIEWER, new ShowDocumentViewerAction(stateManager, prefs)), factory.createMenuItem(StandardActions.EDIT_ENTRY, new OpenEntryEditorAction(this, stateManager)), factory.createMenuItem(StandardActions.OPEN_CONSOLE, new OpenConsoleAction(stateManager)) ); diff --git a/src/main/java/org/jabref/gui/actions/ActionHelper.java b/src/main/java/org/jabref/gui/actions/ActionHelper.java index 01e23d67e22..06f294de5d2 100644 --- a/src/main/java/org/jabref/gui/actions/ActionHelper.java +++ b/src/main/java/org/jabref/gui/actions/ActionHelper.java @@ -1,14 +1,20 @@ package org.jabref.gui.actions; +import java.nio.file.Path; import java.util.Collections; import java.util.List; +import java.util.Optional; import javafx.beans.binding.Bindings; import javafx.beans.binding.BooleanExpression; import org.jabref.gui.StateManager; import org.jabref.model.entry.BibEntry; +import org.jabref.model.entry.LinkedFile; import org.jabref.model.entry.field.Field; +import org.jabref.model.entry.field.StandardField; +import org.jabref.model.util.FileHelper; +import org.jabref.preferences.PreferencesService; public class ActionHelper { public static BooleanExpression needsDatabase(StateManager stateManager) { @@ -36,4 +42,20 @@ public static BooleanExpression isAnyFieldSetForSelectedEntry(List fields entry.getFieldsObservable(), stateManager.getSelectedEntries()); } + + public static BooleanExpression isFilePresentForSelectedEntry(StateManager stateManager, PreferencesService preferencesService) { + return Bindings.createBooleanBinding(() -> { + List files = stateManager.getSelectedEntries().get(0).getFiles(); + if ((files.size() > 0) && stateManager.getActiveDatabase().isPresent()) { + Optional filename = FileHelper.expandFilename( + stateManager.getActiveDatabase().get(), + files.get(0).getLink(), + preferencesService.getFilePreferences()); + return filename.isPresent(); + } else { + return false; + } + }, stateManager.getSelectedEntries(), + stateManager.getSelectedEntries().get(0).getFieldBinding(StandardField.FILE)); + } } diff --git a/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java b/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java index 9c91ca7c472..444bf3840e9 100644 --- a/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java +++ b/src/main/java/org/jabref/gui/documentviewer/ShowDocumentViewerAction.java @@ -1,12 +1,18 @@ package org.jabref.gui.documentviewer; +import org.jabref.gui.StateManager; +import org.jabref.gui.actions.ActionHelper; import org.jabref.gui.actions.SimpleCommand; +import org.jabref.preferences.PreferencesService; public class ShowDocumentViewerAction extends SimpleCommand { + public ShowDocumentViewerAction(StateManager stateManager, PreferencesService preferences) { + this.executable.bind(ActionHelper.isFilePresentForSelectedEntry(stateManager, preferences)); + } + @Override public void execute() { new DocumentViewerView().show(); } - } diff --git a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css index e53c688a081..61981cde6cb 100644 --- a/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css +++ b/src/main/java/org/jabref/gui/entryeditor/EntryEditor.css @@ -86,6 +86,10 @@ -fx-background-color: -jr-error; } +.list-cell:invalid { + -fx-background-color: -jr-warn; +} + .code-area .context-menu { -fx-font-family: sans-serif; } diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java index e57513db0b7..c7e7a6d4cfe 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java @@ -46,8 +46,13 @@ import org.jabref.model.entry.LinkedFile; import org.jabref.model.metadata.FilePreferences; import org.jabref.model.strings.StringUtil; +import org.jabref.model.util.FileHelper; import org.jabref.model.util.OptionalUtil; +import de.saxsys.mvvmfx.utils.validation.FunctionBasedValidator; +import de.saxsys.mvvmfx.utils.validation.ValidationMessage; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; +import de.saxsys.mvvmfx.utils.validation.Validator; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -69,6 +74,8 @@ public class LinkedFileViewModel extends AbstractViewModel { private final LinkedFileHandler linkedFileHandler; private final ExternalFileTypes externalFileTypes; + private final Validator fileExistsValidator; + public LinkedFileViewModel(LinkedFile linkedFile, BibEntry entry, BibDatabaseContext databaseContext, @@ -88,6 +95,18 @@ public LinkedFileViewModel(LinkedFile linkedFile, this.externalFileTypes = externalFileTypes; this.xmpPreferences = xmpPreferences; + fileExistsValidator = new FunctionBasedValidator<>( + linkedFile.linkProperty(), + link -> { + if (linkedFile.isOnlineLink()) { + return true; + } else { + Optional path = FileHelper.expandFilename(databaseContext, link, filePreferences); + return path.isPresent() && Files.exists(path.get()); + } + }, + ValidationMessage.warning(Localization.lang("Could not find file '%0'.", linkedFile.getLink()))); + downloadOngoing.bind(downloadProgress.greaterThanOrEqualTo(0).and(downloadProgress.lessThan(1))); canWriteXMPMetadata.setValue(!linkedFile.isOnlineLink() && linkedFile.getFileType().equalsIgnoreCase("pdf")); } @@ -174,24 +193,19 @@ public void open() { public void openFolder() { try { - Path path = null; - // absolute path - if (Paths.get(linkedFile.getLink()).isAbsolute()) { - path = Paths.get(linkedFile.getLink()); - } else { - // relative to file folder - for (Path folder : databaseContext.getFileDirectoriesAsPaths(filePreferences)) { - Path file = folder.resolve(linkedFile.getLink()); - if (Files.exists(file)) { - path = file; - break; - } + if (!linkedFile.isOnlineLink()) { + Optional resolvedPath = FileHelper.expandFilename( + databaseContext, + linkedFile.getLink(), + filePreferences); + + if (resolvedPath.isPresent()) { + JabRefDesktop.openFolderAndSelectFile(resolvedPath.get()); + } else { + dialogService.showErrorDialogAndWait(Localization.lang("File not found")); } - } - if (path != null) { - JabRefDesktop.openFolderAndSelectFile(path); } else { - dialogService.showErrorDialogAndWait(Localization.lang("File not found")); + dialogService.showErrorDialogAndWait(Localization.lang("Cannot open folder as the file is an online link.")); } } catch (IOException ex) { LOGGER.debug("Cannot open folder", ex); @@ -253,7 +267,7 @@ public void moveToDefaultDirectory() { // Get target folder Optional fileDir = databaseContext.getFirstExistingFileDir(filePreferences); - if (!fileDir.isPresent()) { + if (fileDir.isEmpty()) { dialogService.showErrorDialogAndWait(Localization.lang("Move file"), Localization.lang("File directory is not set or does not exist!")); return; } @@ -322,7 +336,7 @@ public void moveToDefaultDirectoryAndRename() { public boolean delete() { Optional file = linkedFile.findIn(databaseContext, filePreferences); - if (!file.isPresent()) { + if (file.isEmpty()) { LOGGER.warn("Could not find file " + linkedFile.getLink()); return true; } @@ -367,7 +381,7 @@ public void writeXMPMetadata() { // Localization.lang("Writing XMP metadata...") BackgroundTask writeTask = BackgroundTask.wrap(() -> { Optional file = linkedFile.findIn(databaseContext, filePreferences); - if (!file.isPresent()) { + if (file.isEmpty()) { // TODO: Print error message // Localization.lang("PDF does not exist"); } else { @@ -457,4 +471,6 @@ private Optional inferFileTypeFromURL(String url) { public LinkedFile getFile() { return linkedFile; } + + public ValidationStatus fileExistsValidationStatus() { return fileExistsValidator.getValidationStatus(); } } diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java index 537a17fff08..7dbacc13bc8 100644 --- a/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java +++ b/src/main/java/org/jabref/gui/fieldeditors/LinkedFilesEditor.java @@ -72,7 +72,8 @@ public LinkedFilesEditor(Field field, DialogService dialogService, BibDatabaseCo .withOnMouseClickedEvent(this::handleItemMouseClick) .setOnDragDetected(this::handleOnDragDetected) .setOnDragDropped(this::handleOnDragDropped) - .setOnDragOver(this::handleOnDragOver); + .setOnDragOver(this::handleOnDragOver) + .withValidation(LinkedFileViewModel::fileExistsValidationStatus); listView.setCellFactory(cellFactory); diff --git a/src/main/java/org/jabref/gui/maintable/OpenExternalFileAction.java b/src/main/java/org/jabref/gui/maintable/OpenExternalFileAction.java index 6579eed3102..eb7658f1b28 100644 --- a/src/main/java/org/jabref/gui/maintable/OpenExternalFileAction.java +++ b/src/main/java/org/jabref/gui/maintable/OpenExternalFileAction.java @@ -11,7 +11,6 @@ import org.jabref.gui.fieldeditors.LinkedFileViewModel; import org.jabref.logic.l10n.Localization; import org.jabref.model.entry.BibEntry; -import org.jabref.model.entry.field.StandardField; import org.jabref.preferences.PreferencesService; public class OpenExternalFileAction extends SimpleCommand { @@ -25,7 +24,7 @@ public OpenExternalFileAction(DialogService dialogService, StateManager stateMan this.stateManager = stateManager; this.preferencesService = preferencesService; - this.executable.bind(ActionHelper.isFieldSetForSelectedEntry(StandardField.FILE, stateManager) + this.executable.bind(ActionHelper.isFilePresentForSelectedEntry(stateManager, preferencesService) .and(ActionHelper.needsEntriesSelected(1, stateManager))); } diff --git a/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java b/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java index a2c3c211785..d3afc796ad0 100644 --- a/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java +++ b/src/main/java/org/jabref/gui/maintable/OpenFolderAction.java @@ -7,7 +7,6 @@ import org.jabref.gui.actions.SimpleCommand; import org.jabref.gui.externalfiletype.ExternalFileTypes; import org.jabref.gui.fieldeditors.LinkedFileViewModel; -import org.jabref.model.entry.field.StandardField; import org.jabref.preferences.PreferencesService; public class OpenFolderAction extends SimpleCommand { @@ -21,7 +20,7 @@ public OpenFolderAction(DialogService dialogService, StateManager stateManager, this.stateManager = stateManager; this.preferencesService = preferencesService; - this.executable.bind(ActionHelper.isFieldSetForSelectedEntry(StandardField.FILE, stateManager)); + this.executable.bind(ActionHelper.isFilePresentForSelectedEntry(stateManager, preferencesService)); } @Override diff --git a/src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java b/src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java index 72188027e8a..59743c49248 100644 --- a/src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java +++ b/src/main/java/org/jabref/gui/util/ViewModelListCellFactory.java @@ -22,6 +22,7 @@ import org.jabref.gui.icon.JabRefIcon; import org.jabref.model.strings.StringUtil; +import de.saxsys.mvvmfx.utils.validation.ValidationStatus; import org.fxmisc.easybind.Subscription; /** @@ -31,6 +32,8 @@ */ public class ViewModelListCellFactory implements Callback, ListCell> { + private static final PseudoClass INVALID_PSEUDO_CLASS = PseudoClass.getPseudoClass("invalid"); + private Callback toText; private Callback toGraphic; private Callback toTooltip; @@ -43,6 +46,7 @@ public class ViewModelListCellFactory implements Callback, ListCe private BiConsumer toOnDragExited; private BiConsumer toOnDragOver; private Map>> pseudoClasses = new HashMap<>(); + private Callback validationStatusProperty; public ViewModelListCellFactory withText(Callback toText) { this.toText = toText; @@ -66,10 +70,7 @@ public ViewModelListCellFactory withIcon(Callback toIcon) { } public ViewModelListCellFactory withIcon(Callback toIcon, Callback toColor) { - this.toGraphic = viewModel -> { - - return toIcon.call(viewModel).withColor(toColor.call(viewModel)).getGraphicNode(); - }; + this.toGraphic = viewModel -> toIcon.call(viewModel).withColor(toColor.call(viewModel)).getGraphicNode(); return this; } @@ -134,6 +135,11 @@ public ViewModelListCellFactory withPseudoClass(PseudoClass pseudoClass, Call return this; } + public ViewModelListCellFactory withValidation(Callback validationStatusProperty) { + this.validationStatusProperty = validationStatusProperty; + return this; + } + public void install(ComboBox comboBox) { comboBox.setButtonCell(this.call(null)); comboBox.setCellFactory(this); @@ -146,7 +152,7 @@ public void install(ListView listView) { @Override public ListCell call(ListView param) { - return new ListCell() { + return new ListCell<>() { List subscriptions = new ArrayList<>(); @@ -203,6 +209,12 @@ protected void updateItem(T item, boolean empty) { Subscription subscription = BindingsHelper.includePseudoClassWhen(this, pseudoClassWithCondition.getKey(), condition); subscriptions.add(subscription); } + if (validationStatusProperty != null) { + validationStatusProperty.call(viewModel).getHighestMessage().ifPresent(message -> { + setTooltip(new Tooltip(message.getMessage())); + subscriptions.add(BindingsHelper.includePseudoClassWhen(this, INVALID_PSEUDO_CLASS, validationStatusProperty.call(viewModel).validProperty().not())); + }); + } } } }; diff --git a/src/main/java/org/jabref/model/entry/BibEntry.java b/src/main/java/org/jabref/model/entry/BibEntry.java index 2f9ffec5133..0bfe4f7b11b 100644 --- a/src/main/java/org/jabref/model/entry/BibEntry.java +++ b/src/main/java/org/jabref/model/entry/BibEntry.java @@ -918,7 +918,7 @@ public Optional setFiles(List files) { public List getFiles() { // Extract the path Optional oldValue = getField(StandardField.FILE); - if (!oldValue.isPresent()) { + if (oldValue.isEmpty()) { return new ArrayList<>(); // Return new ArrayList because emptyList is immutable } diff --git a/src/main/resources/l10n/JabRef_en.properties b/src/main/resources/l10n/JabRef_en.properties index 51c0637f60d..9708c86e595 100644 --- a/src/main/resources/l10n/JabRef_en.properties +++ b/src/main/resources/l10n/JabRef_en.properties @@ -102,6 +102,8 @@ Cannot\ create\ group=Cannot create group Cannot\ create\ group.\ Please\ create\ a\ library\ first.=Cannot create group. Please create a library first. +Cannot\ open\ folder\ as\ the\ file\ is\ an\ online\ link.=Cannot open folder as the file is an online link. + case\ insensitive=case insensitive case\ sensitive=case sensitive