diff --git a/.github/outdatedDependencies.md b/.github/outdatedDependencies.md
index c750c81fb06..62b87165631 100644
--- a/.github/outdatedDependencies.md
+++ b/.github/outdatedDependencies.md
@@ -1,5 +1,5 @@
---
title: Outdated dependencies
-labels: code-quality, dependencies
+labels: t: dependencies
---
[There are outdated dependencies!](https://github.com/JabRef/jabref/actions?query=is%3Afailure+workflow%3A%22Check+dependencies%22)
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 001004798d3..572c836fe90 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -205,7 +205,7 @@ jobs:
if: ${{ steps.checksecrets.outputs.secretspresent }}
run: xvfb-run --auto-servernum ./gradlew jacocoTestReport && bash <(curl -s https://codecov.io/bash);
env:
- CI: "false" # we pretend to run locally - even if tests fail on the CI, they count towards test coverage
+ CI: "true"
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
DBMS: "postgresql"
# This is https://github.com/marketplace/actions/gradle-wrapper-validation
diff --git a/CHANGELOG.md b/CHANGELOG.md
index bb224287a6e..97c5b199eb6 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -39,6 +39,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We fixed an issue where the INSPIRE fetcher was no longer working [#6229](https://github.com/JabRef/jabref/issues/6229)
- We fixed an issue where custom exports with an uppercase file extension could not be selected for "Copy...-> Export to Clipboard" [#6285](https://github.com/JabRef/jabref/issues/6285)
+- We fixed the display of icon both in the main table and linked file editor. [#6169](https://github.com/JabRef/jabref/issues/6169)
### Removed
@@ -87,6 +88,7 @@ Note that this project **does not** adhere to [Semantic Versioning](http://semve
- We fixed an issue where citation styles except the default "Preview" could not be used. [#56220](https://github.com/JabRef/jabref/issues/5622)
- We fixed an issue where a warning was displayed when the title content is made up of two sentences. [#5832](https://github.com/JabRef/jabref/issues/5832)
- We fixed an issue where an exception was thrown when adding a save action without a selected formatter in the library properties [#6069](https://github.com/JabRef/jabref/issues/6069)
+- We fixed an issue when an "Abstract field" was duplicating text, when importing from RIS file (Neurons) [#6065](https://github.com/JabRef/jabref/issues/6065)
### Removed
diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml
index d57f19c6853..2de4703981a 100644
--- a/config/checkstyle/checkstyle.xml
+++ b/config/checkstyle/checkstyle.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/docs/guidelines-for-setting-up-a-local-workspace.md b/docs/guidelines-for-setting-up-a-local-workspace.md
index 431d4aa410c..b04a13fb208 100644
--- a/docs/guidelines-for-setting-up-a-local-workspace.md
+++ b/docs/guidelines-for-setting-up-a-local-workspace.md
@@ -29,9 +29,9 @@ If you do not yet have a GitHub account, please [create one](https://github.com/
Proposals for account names:
-- Login similar to your university account. Example: `koppor`
-- Use your last name prefixed by the first letter of your first name. Example: `okopp`
-- Use `firstname.lastname`. Example: `oliver.kopp`
+* Login similar to your university account. Example: `koppor`
+* Use your last name prefixed by the first letter of your first name. Example: `okopp`
+* Use `firstname.lastname`. Example: `oliver.kopp`
You can hide your email adress by following the recommendations at .
@@ -41,9 +41,9 @@ That account then be used for develoment mailing lists, mail exchange with other
Examples:
-- Same login as in GitHub (see above). Example: `koppor@gmail.com`
-- "`it`" in the name. Example: `kopp.it@gmail.com`
-- Use the university login. Example: `st342435@stud.uni-stuttgart.de`
+* Same login as in GitHub (see above). Example: `koppor@gmail.com`
+* "`it`" in the name. Example: `kopp.it@gmail.com`
+* Use the university login. Example: `st342435@stud.uni-stuttgart.de`
### git
@@ -172,15 +172,18 @@ Contributions to JabRef's source code need to have a code formatting that is con
2. Close the settings afterwards and restart IntelliJ
* Go to **File \| Settings \| Editor \| Code Style**
* Click on the settings wheel \(next to the scheme chooser\), then click "Import Scheme"
-* Select the IntelliJ configuration file `config/IntelliJ Code Style.xml`.
+* Select the IntelliJ configuration file `config/IntelliJ Code Style.xml`
* Go to **File \| Settings \| Checkstyle \| Configuration File**
1. Import the CheckStyle configuration file by clicking the \[+\] button
- 2. For the description provide e.g. "JabRef"
+ 2. For the description provide "JabRef"
3. Click "Browse" and choose `config/checkstyle/checkstyle.xml`
4. Check "Store relative to project location"
5. Click "Next" and "Finish"
6. Activate the CheckStyle configuration file by ticking it in the list
- 7. Save settings by clicking "OK"
+ 7. Ensure that CheckStyle version 8.31 (or higher) is selected
+ 8. Save settings by clicking "OK"
+
+![checkstyle settings](images/intellij-checkstyle-settings.png)
#### Troubleshooting when using both IDEA and Eclipse
@@ -200,7 +203,7 @@ For Eclipse 2020-03 you need to install [jdk14 support](https://marketplace.ecli
1. Run `./gradlew run` to generate all resources and to check if JabRef runs.
* The JabRef GUI should finally appear.
* This step is only required once.
-2. Run `./gradlew eclipse`
+2. Run `./gradlew eclipse`
* **This must always be executed, when there are new upstream changes.**
3. Copy the file `Log4jPlugins.java` from `build/generated/sources/annotationProcessor/java/main/org/jabref/gui/logging/plugins` to `src/main/java/org/jabref/gui/logging/plugins/`
* Usually, the folder `plugins` must be created for that.
@@ -210,29 +213,28 @@ For Eclipse 2020-03 you need to install [jdk14 support](https://marketplace.ecli
5. Create a run/debug configuration for the main class `org.jabref.JabRefLauncher` and/or for `org.jabref.JabRefMain` \(both can be used equivalently\)
* In the tab "Arguments" of the run/debug configuration, enter the following runtime VM arguments:
- * Set "VM Arguments" to:
-
- ```text
- --patch-module test=fastparse_2.12-1.0.0.jar
- --patch-module test2=fastparse-utils_2.12-1.0.0.jar
- --patch-module test3=sourcecode_2.12-0.1.4.jar
- --add-exports javafx.controls/com.sun.javafx.scene.control=org.jabref
- --add-exports org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref
- --add-exports javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls
- --add-exports javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls
- --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls
- --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls
- --add-exports javafx.controls/com.sun.javafx.scene.control=org.controlsfx.controls
- --add-exports javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls
- --add-exports javafx.base/com.sun.javafx.event=org.controlsfx.controls
- --add-exports javafx.base/com.sun.javafx.collections=org.controlsfx.controls
- --add-exports javafx.base/com.sun.javafx.runtime=org.controlsfx.controls
- --add-exports javafx.web/com.sun.webkit=org.controlsfx.controls
- --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls
- --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix
- --add-exports com.oracle.truffle.regex/com.oracle.truffle.regex=org.graalvm.truffle
- --patch-module org.jabref=build\resources\main
- ```
+ ```text
+ --patch-module test=fastparse_2.12-1.0.0.jar
+ --patch-module test2=fastparse-utils_2.12-1.0.0.jar
+ --patch-module test3=sourcecode_2.12-0.1.4.jar
+ --add-exports javafx.controls/com.sun.javafx.scene.control=org.jabref
+ --add-exports org.controlsfx.controls/impl.org.controlsfx.skin=org.jabref
+ --add-exports javafx.graphics/com.sun.javafx.scene=org.controlsfx.controls
+ --add-exports javafx.graphics/com.sun.javafx.scene.traversal=org.controlsfx.controls
+ --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls
+ --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=org.controlsfx.controls
+ --add-exports javafx.controls/com.sun.javafx.scene.control=org.controlsfx.controls
+ --add-exports javafx.controls/com.sun.javafx.scene.control.inputmap=org.controlsfx.controls
+ --add-exports javafx.base/com.sun.javafx.event=org.controlsfx.controls
+ --add-exports javafx.base/com.sun.javafx.collections=org.controlsfx.controls
+ --add-exports javafx.base/com.sun.javafx.runtime=org.controlsfx.controls
+ --add-exports javafx.web/com.sun.webkit=org.controlsfx.controls
+ --add-exports javafx.graphics/com.sun.javafx.css=org.controlsfx.controls
+ --add-exports javafx.controls/com.sun.javafx.scene.control.behavior=com.jfoenix
+ --add-exports com.oracle.truffle.regex/com.oracle.truffle.regex=org.graalvm.truffle
+ --patch-module org.jabref=build\resources\main
+ ```
+
6. Optional: Install the [e\(fx\)clipse plugin](http://www.eclipse.org/efxclipse/index.html) from the Eclipse marketplace: 1. Help -> Eclipse Marketplace... -> Search tab 2. Enter "e\(fx\)clipse" in the search dialogue 3. Click "Go" 4. Click "Install" button next to the plugin 5. Click "Finish"
7. Now you can build and run/debug the application by either using "JabRefLauncher" or "JabRefMain". This is the recommended way, since the application starts quite fast.
diff --git a/docs/images/intellij-checkstyle-settings.png b/docs/images/intellij-checkstyle-settings.png
new file mode 100644
index 00000000000..c605c04cd20
Binary files /dev/null and b/docs/images/intellij-checkstyle-settings.png differ
diff --git a/src/main/java/org/jabref/gui/EntryTypeView.java b/src/main/java/org/jabref/gui/EntryTypeView.java
index e7ffea9fe14..657fe4b20f0 100644
--- a/src/main/java/org/jabref/gui/EntryTypeView.java
+++ b/src/main/java/org/jabref/gui/EntryTypeView.java
@@ -12,7 +12,9 @@
import javafx.scene.control.ComboBox;
import javafx.scene.control.TextField;
import javafx.scene.control.TitledPane;
+import javafx.scene.control.Tooltip;
import javafx.scene.layout.FlowPane;
+import javafx.stage.Screen;
import org.jabref.Globals;
import org.jabref.gui.util.BaseDialog;
@@ -26,6 +28,8 @@
import org.jabref.model.entry.types.BibtexEntryTypeDefinitions;
import org.jabref.model.entry.types.EntryType;
import org.jabref.model.entry.types.IEEETranEntryTypeDefinitions;
+import org.jabref.model.entry.types.StandardEntryType;
+import org.jabref.model.strings.StringUtil;
import org.jabref.preferences.JabRefPreferences;
import com.airhacks.afterburner.views.ViewLoader;
@@ -92,6 +96,17 @@ private void addEntriesToPane(FlowPane pane, Collection extends BibEntryType>
entryButton.setUserData(entryType);
entryButton.setOnAction(event -> setEntryTypeForReturnAndClose(Optional.of(entryType)));
pane.getChildren().add(entryButton);
+
+ EntryType selectedType = entryType.getType();
+ String description = getDescription(selectedType);
+ if (StringUtil.isNotBlank(description)) {
+ Screen currentScreen = Screen.getPrimary();
+ double maxWidth = currentScreen.getBounds().getWidth();
+ Tooltip tooltip = new Tooltip(description);
+ tooltip.setMaxWidth((maxWidth * 2) / 3);
+ tooltip.setWrapText(true);
+ entryButton.setTooltip(tooltip);
+ }
}
}
@@ -168,4 +183,119 @@ private void setEntryTypeForReturnAndClose(Optional entryType) {
viewModel.stopFetching();
this.close();
}
+
+ //The description is coming from biblatex manual chapter 2
+ //Biblatex documentation is favored over the bibtex,
+ //since bibtex is a subset of biblatex and biblatex is better documented.
+ public static String getDescription(EntryType selectedType) {
+ //CHECKSTYLE:OFF
+ if (selectedType instanceof StandardEntryType) {
+ switch ((StandardEntryType) selectedType) {
+ case Article -> {
+ return Localization.lang("An article in a journal, magazine, newspaper, or other periodical which forms a self-contained unit with its own title.");
+ }
+ case Book -> {
+ return Localization.lang("A single-volume book with one or more authors where the authors share credit for the work as a whole.");
+ }
+ case Booklet -> {
+ return Localization.lang("A book-like work without a formal publisher or sponsoring institution.");
+ }
+ case Collection -> {
+ return Localization.lang("A single-volume collection with multiple, self-contained contributions by distinct authors which have their own title. The work as a whole has no overall author but it will usually have an editor.");
+ }
+ case Conference -> {
+ return Localization.lang("A legacy alias for \"InProceedings\".");
+ }
+ case InBook -> {
+ return Localization.lang("A part of a book which forms a self-contained unit with its own title.");
+ }
+ case InCollection -> {
+ return Localization.lang("A contribution to a collection which forms a self-contained unit with a distinct author and title.");
+ }
+ case InProceedings -> {
+ return Localization.lang("An article in a conference proceedings.");
+ }
+ case Manual -> {
+ return Localization.lang("Technical or other documentation, not necessarily in printed form.");
+ }
+ case MastersThesis -> {
+ return Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term Master's thesis.");
+ }
+ case Misc -> {
+ return Localization.lang("A fallback type for entries which do not fit into any other category.");
+ }
+ case PhdThesis -> {
+ return Localization.lang("Similar to \"Thesis\" except that the type field is optional and defaults to the localised term PhD thesis.");
+ }
+ case Proceedings -> {
+ return Localization.lang("A single-volume conference proceedings. This type is very similar to \"Collection\".");
+ }
+ case TechReport -> {
+ return Localization.lang("Similar to \"Report\" except that the type field is optional and defaults to the localised term technical report.");
+ }
+ case Unpublished -> {
+ return Localization.lang("A work with an author and a title which has not been formally published, such as a manuscript or the script of a talk.");
+ }
+ case BookInBook -> {
+ return Localization.lang("This type is similar to \"InBook\" but intended for works originally published as a stand-alone book.");
+ }
+ case InReference -> {
+ return Localization.lang("An article in a work of reference. This is a more specific variant of the generic \"InCollection\" entry type.");
+ }
+ case MvBook -> {
+ return Localization.lang("A multi-volume \"Book\".");
+ }
+ case MvCollection -> {
+ return Localization.lang("A multi-volume \"Collection\".");
+ }
+ case MvProceedings -> {
+ return Localization.lang("A multi-volume \"Proceedings\" entry.");
+ }
+ case MvReference -> {
+ return Localization.lang("A multi-volume \"Reference\" entry. The standard styles will treat this entry type as an alias for \"MvCollection\".");
+ }
+ case Online -> {
+ return Localization.lang("This entry type is intended for sources such as web sites which are intrinsically online resources.");
+ }
+ case Reference -> {
+ return Localization.lang("A single-volume work of reference such as an encyclopedia or a dictionary.");
+ }
+ case Report -> {
+ return Localization.lang("A technical report, research report, or white paper published by a university or some other institution.");
+ }
+ case Set -> {
+ return Localization.lang("An entry set is a group of entries which are cited as a single reference and listed as a single item in the bibliography.");
+ }
+ case SuppBook -> {
+ return Localization.lang("Supplemental material in a \"Book\". This type is provided for elements such as prefaces, introductions, forewords, afterwords, etc. which often have a generic title only.");
+ }
+ case SuppCollection -> {
+ return Localization.lang("Supplemental material in a \"Collection\".");
+ }
+ case SuppPeriodical -> {
+ return Localization.lang("Supplemental material in a \"Periodical\". This type may be useful when referring to items such as regular columns, obituaries, letters to the editor, etc. which only have a generic title.");
+ }
+ case Thesis -> {
+ return Localization.lang("A thesis written for an educational institution to satisfy the requirements for a degree.");
+ }
+ case WWW -> {
+ return Localization.lang("An alias for \"Online\", provided for jurabib compatibility.");
+ }
+ case Software -> {
+ return Localization.lang("Computer software. The standard styles will treat this entry type as an alias for \"Misc\".");
+ }
+ case Dataset -> {
+ return Localization.lang("A data set or a similar collection of (mostly) raw data.");
+ }
+ default -> {
+ return "";
+ }
+ }
+ } else {
+ return "";
+ }
+ //CHECKSTYLE:ON
+
+ }
+
}
diff --git a/src/main/java/org/jabref/gui/JabRefFrame.java b/src/main/java/org/jabref/gui/JabRefFrame.java
index b32e35ceb4e..7cdf41d2891 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/externalfiletype/ExternalFileTypes.java b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java
index 1c24f739ffd..ed26d3d8373 100644
--- a/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java
+++ b/src/main/java/org/jabref/gui/externalfiletype/ExternalFileTypes.java
@@ -56,7 +56,7 @@ public Set getExternalFileTypeSelection() {
* @return The ExternalFileType registered, or null if none.
*/
public Optional getExternalFileTypeByName(String name) {
- Optional externalFileType = externalFileTypes.stream().filter(type -> type.getExtension().equals(name)).findFirst();
+ Optional externalFileType = externalFileTypes.stream().filter(type -> type.getName().equals(name)).findFirst();
if (externalFileType.isPresent()) {
return externalFileType;
}
diff --git a/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java b/src/main/java/org/jabref/gui/fieldeditors/LinkedFileViewModel.java
index e57513db0b7..62994a86a20 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"));
}
@@ -136,12 +155,10 @@ public Optional findIn(List directories) {
return linkedFile.findIn(directories);
}
- /**
- * TODO: Be a bit smarter and try to infer correct icon, for example using {@link
- * org.jabref.gui.externalfiletype.ExternalFileTypes#getExternalFileTypeByName(String)}
- */
public JabRefIcon getTypeIcon() {
- return IconTheme.JabRefIcons.PDF_FILE;
+ return externalFileTypes.fromLinkedFile(linkedFile, false)
+ .map(ExternalFileType::getIcon)
+ .orElse(IconTheme.JabRefIcons.FILE);
}
public void markAsAutomaticallyFound() {
@@ -174,24 +191,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 +265,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 +334,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 +379,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 +469,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/menus/ChangeEntryTypeMenu.java b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java
index bf574cf411a..e5625178f81 100644
--- a/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java
+++ b/src/main/java/org/jabref/gui/menus/ChangeEntryTypeMenu.java
@@ -7,11 +7,15 @@
import javafx.collections.ObservableList;
import javafx.scene.control.ContextMenu;
+import javafx.scene.control.CustomMenuItem;
+import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
+import javafx.scene.control.Tooltip;
import org.jabref.Globals;
+import org.jabref.gui.EntryTypeView;
import org.jabref.gui.undo.CountingUndoManager;
import org.jabref.gui.undo.NamedCompound;
import org.jabref.gui.undo.UndoableChangeType;
@@ -23,6 +27,7 @@
import org.jabref.model.entry.types.BibtexEntryTypeDefinitions;
import org.jabref.model.entry.types.EntryType;
import org.jabref.model.entry.types.IEEETranEntryTypeDefinitions;
+import org.jabref.model.strings.StringUtil;
public class ChangeEntryTypeMenu {
@@ -31,13 +36,18 @@ public ChangeEntryTypeMenu() {
}
public static MenuItem createMenuItem(EntryType type, BibEntry entry, UndoManager undoManager) {
- MenuItem menuItem = new MenuItem(type.getDisplayName());
+ CustomMenuItem menuItem = new CustomMenuItem(new Label(type.getDisplayName()));
menuItem.setOnAction(event -> {
NamedCompound compound = new NamedCompound(Localization.lang("Change entry type"));
entry.setType(type)
.ifPresent(change -> compound.addEdit(new UndoableChangeType(change)));
undoManager.addEdit(compound);
});
+ String description = EntryTypeView.getDescription(type);
+ if (StringUtil.isNotBlank(description)) {
+ Tooltip tooltip = new Tooltip(description);
+ Tooltip.install(menuItem.getContent(), tooltip);
+ }
return menuItem;
}
@@ -92,4 +102,5 @@ private void populate(ObservableList