Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7f72e38
Add context menu for multi-file entries (#12567)
w0nderfu11 Aug 20, 2025
5c3fc14
Apply OpenRewrite recipes
w0nderfu11 Aug 20, 2025
740cc4a
Fixed test and optimized imports
w0nderfu11 Aug 20, 2025
afa12f1
merge for problems with lib
w0nderfu11 Aug 20, 2025
02797f7
fixed library and space in catch block
w0nderfu11 Aug 20, 2025
8924487
Runned rewrite, fixed catch block and optimize logic of test ContextM…
w0nderfu11 Aug 20, 2025
f3f91e7
Fix tests: initialize JavaFX toolkit; replace empty catch with meanin…
w0nderfu11 Aug 20, 2025
e6ef784
Fix tests: added space after {
w0nderfu11 Aug 20, 2025
f31fd86
Trigger CI
w0nderfu11 Aug 20, 2025
650bd9e
Trigger CI
w0nderfu11 Aug 20, 2025
8d86798
Update jabgui/src/test/java/org/jabref/gui/copyfiles/CopyMultipleFile…
w0nderfu11 Sep 1, 2025
181587a
refactor(gui/contextmenu): restore Strategy pattern for linked files …
w0nderfu11 Sep 2, 2025
665b069
feat(gui): multi-file context menu for linked files
w0nderfu11 Sep 5, 2025
d100fba
merge: origin/feat/multi-file-context-menu into feat/multi-file-conte…
w0nderfu11 Sep 5, 2025
56bccb1
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 5, 2025
a926f87
Merge branch 'main' into feat/multi-file-context-menu
Siedlerchr Sep 8, 2025
ccc2863
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 9, 2025
7adefe6
Address review: Optional/map, non-null params, logger, tests, menus
w0nderfu11 Sep 9, 2025
60e9c86
Merge branch 'feat/multi-file-context-menu' of https://github.com/w0n…
w0nderfu11 Sep 9, 2025
a99821d
fixed tests
w0nderfu11 Sep 9, 2025
a9b9e4f
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 10, 2025
ed78f30
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 11, 2025
a429df9
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 12, 2025
d1933c2
Update jabgui/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAc…
w0nderfu11 Sep 12, 2025
8c6387d
Update jabgui/src/main/java/org/jabref/gui/copyfiles/CopySingleFileAc…
w0nderfu11 Sep 12, 2025
259e37c
wip: local changes before style sync
w0nderfu11 Sep 17, 2025
44f084b
Fix styles
calixtus Sep 18, 2025
a9f21ae
Merge remote-tracking branch 'upstream/main' into fork/w0nderfu11/fea…
calixtus Sep 18, 2025
79dec90
Final style and CI checks
w0nderfu11 Sep 18, 2025
7d5b3c6
docs: sync http-server howto; jablib: sync jspecify @Nullable from up…
w0nderfu11 Sep 18, 2025
514232c
Naming convention
calixtus Sep 18, 2025
e3eb1df
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 19, 2025
31af228
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 19, 2025
0832ba0
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 19, 2025
ace2a3c
feat(gui): multi-file context menu for linked files (sync on upstream…
w0nderfu11 Sep 19, 2025
d36dbac
sync of branches
w0nderfu11 Sep 19, 2025
d245346
Fix LOGGER naming (convension)
w0nderfu11 Sep 19, 2025
791caba
fixed submodules
w0nderfu11 Sep 19, 2025
88702c8
Fix submodules
w0nderfu11 Sep 19, 2025
19c71d4
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 19, 2025
668eedb
chore: reset submodules to upstream/main (csl-styles, csl-locales)
w0nderfu11 Sep 19, 2025
374d697
Fix submodules
w0nderfu11 Sep 19, 2025
91bc19a
Fix submodules
w0nderfu11 Sep 19, 2025
5b002c9
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Sep 23, 2025
f5dbd10
Fix test classes
w0nderfu11 Sep 24, 2025
796507f
Merge remote-tracking branch 'origin/feat/multi-file-context-menu' in…
w0nderfu11 Sep 24, 2025
b73504a
Fix test classes V2 (forget to update MultiSelectionMenuBuilderTest a…
w0nderfu11 Sep 24, 2025
c327bd8
Apply JSpecify annotations
calixtus Sep 24, 2025
8fc6724
Fixed bot's cases and fixed submodules
w0nderfu11 Sep 25, 2025
19109fb
Fixed conflict and cases of bot
w0nderfu11 Sep 25, 2025
9cac8be
Merge remote-tracking branch 'upstream/main' into fork/w0nderfu11/fea…
calixtus Sep 26, 2025
dc7ad22
Fix submodules
calixtus Sep 26, 2025
ecbf18a
Migrate to JSpecify annotations
calixtus Sep 26, 2025
c2427d1
Fix wording
calixtus Sep 26, 2025
189f823
Changed assert methods for readability
calixtus Sep 26, 2025
e281d32
interface ---< class, deleted variable
w0nderfu11 Sep 29, 2025
730ddd3
Refactored ContextMenuFactoryTest for readability
calixtus Oct 3, 2025
b8f0625
Merge branch 'main' into feat/multi-file-context-menu
w0nderfu11 Oct 13, 2025
7f8116f
Added fixes, deleted SelectionChecks
w0nderfu11 Oct 13, 2025
99e7745
changelog fixed and localization
w0nderfu11 Oct 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We added "copy preview as markdown" feature. [#12552](https://github.com/JabRef/jabref/issues/12552)
- In case no citation relation information can be fetched, we show the data providers reason. [#13549](https://github.com/JabRef/jabref/pull/13549)
- We added a new button for shortening the DOI near the DOI field in the general tab when viewing an entry. [#13639](https://github.com/JabRef/jabref/issues/13639)
- We added support for managing multiple linked files via the entry context menu. [#12567](https://github.com/JabRef/jabref/issues/12567)

### Changed

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,9 @@ public enum StandardActions implements Action {

OPEN_DATABASE_FOLDER(Localization.lang("Reveal in file explorer")),
OPEN_FOLDER(Localization.lang("Open folder"), Localization.lang("Open folder"), IconTheme.JabRefIcons.FOLDER, KeyBinding.OPEN_FOLDER),
OPEN_FOLDERS(Localization.lang("Open folders"), Localization.lang("Open folders"), IconTheme.JabRefIcons.FOLDER, KeyBinding.OPEN_FOLDER),
OPEN_FILE(Localization.lang("Open file"), Localization.lang("Open file"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE),
OPEN_FILES(Localization.lang("Open files"), Localization.lang("Open files"), IconTheme.JabRefIcons.FILE, KeyBinding.OPEN_FILE),
OPEN_CONSOLE(Localization.lang("Open terminal here"), Localization.lang("Open terminal here"), IconTheme.JabRefIcons.CONSOLE, KeyBinding.OPEN_CONSOLE),
COPY_LINKED_FILES(Localization.lang("Copy linked files to folder...")),
COPY_DOI(Localization.lang("Copy DOI")),
Expand Down Expand Up @@ -160,15 +162,20 @@ public enum StandardActions implements Action {

EDIT_FILE_LINK(Localization.lang("Edit"), IconTheme.JabRefIcons.EDIT, KeyBinding.OPEN_CLOSE_ENTRY_EDITOR),
DOWNLOAD_FILE(Localization.lang("Download file"), IconTheme.JabRefIcons.DOWNLOAD_FILE),
DOWNLOAD_FILES(Localization.lang("Download files"), IconTheme.JabRefIcons.DOWNLOAD_FILE),
REDOWNLOAD_FILE(Localization.lang("Redownload file"), IconTheme.JabRefIcons.DOWNLOAD_FILE),
REDOWNLOAD_FILES(Localization.lang("Redownload files"), IconTheme.JabRefIcons.DOWNLOAD_FILE),
RENAME_FILE_TO_PATTERN(Localization.lang("Rename file to defined pattern"), IconTheme.JabRefIcons.AUTO_RENAME),
RENAME_FILE_TO_NAME(Localization.lang("Rename files to configured filename format pattern"), IconTheme.JabRefIcons.RENAME, KeyBinding.REPLACE_STRING),
MOVE_FILE_TO_FOLDER(Localization.lang("Move file to file directory"), IconTheme.JabRefIcons.MOVE_TO_FOLDER),
MOVE_FILES_TO_FOLDER(Localization.lang("Move files to file directory"), IconTheme.JabRefIcons.MOVE_TO_FOLDER),
MOVE_FILE_TO_FOLDER_AND_RENAME(Localization.lang("Move file to file directory and rename file")),
COPY_FILE_TO_FOLDER(Localization.lang("Copy linked file to folder..."), IconTheme.JabRefIcons.COPY_TO_FOLDER, KeyBinding.COPY),
COPY_FILES_TO_FOLDER(Localization.lang("Copy linked files to folder..."), IconTheme.JabRefIcons.COPY_TO_FOLDER, KeyBinding.COPY),
REMOVE_LINK(Localization.lang("Remove link"), IconTheme.JabRefIcons.REMOVE_LINK),
REMOVE_LINKS(Localization.lang("Remove links"), IconTheme.JabRefIcons.REMOVE_LINK),
DELETE_FILE(Localization.lang("Permanently delete local file"), IconTheme.JabRefIcons.DELETE_FILE, KeyBinding.DELETE_ENTRY),
DELETE_FILES(Localization.lang("Permanently delete local files"), IconTheme.JabRefIcons.DELETE_FILE, KeyBinding.DELETE_ENTRY),

HELP(Localization.lang("Online help"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP),
HELP_GROUPS(Localization.lang("Open Help page"), IconTheme.JabRefIcons.HELP, KeyBinding.HELP),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package org.jabref.gui.copyfiles;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.util.Objects;
import java.util.Optional;

import javafx.beans.binding.Bindings;
import javafx.collections.ObservableList;

import org.jabref.gui.DialogService;
import org.jabref.gui.actions.SimpleCommand;
import org.jabref.gui.fieldeditors.LinkedFileViewModel;
import org.jabref.gui.util.DirectoryDialogConfiguration;
import org.jabref.logic.FilePreferences;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;

public class CopyMultipleFilesAction extends SimpleCommand {

private final ObservableList<LinkedFileViewModel> selectedFiles;
private final DialogService dialogService;
private final BibDatabaseContext databaseContext;
private final FilePreferences filePreferences;

public CopyMultipleFilesAction(ObservableList<LinkedFileViewModel> selectedFiles,
DialogService dialogService,
BibDatabaseContext databaseContext,
FilePreferences filePreferences) {
this.selectedFiles = Objects.requireNonNull(selectedFiles);
this.dialogService = Objects.requireNonNull(dialogService);
this.databaseContext = Objects.requireNonNull(databaseContext);
this.filePreferences = Objects.requireNonNull(filePreferences);
this.executable.bind(Bindings.createBooleanBinding(
() -> !selectedFiles.isEmpty() && selectedFiles.stream().allMatch(vm ->
!vm.getFile().isOnlineLink()
&& vm.getFile().findIn(databaseContext, filePreferences).isPresent()),
selectedFiles
));
}

@Override
public void execute() {
DirectoryDialogConfiguration conf = new DirectoryDialogConfiguration.Builder().build();
dialogService.showDirectorySelectionDialog(conf).ifPresent(this::copyAllTo);
}

private void copyAllTo(Path targetDir) {
try {
Files.createDirectories(targetDir);
} catch (IOException e) {
dialogService.showErrorDialogAndWait(Localization.lang("Cannot create directory '%0'", targetDir));
return;
}

int copied = 0;

for (LinkedFileViewModel vm : selectedFiles) {
Optional<Path> srcOpt = vm.getFile().findIn(databaseContext, filePreferences);
if (srcOpt.isEmpty()) {
continue;
}

Path src = srcOpt.get();
Path dst = targetDir.resolve(src.getFileName());

if (Files.exists(dst) && Files.isDirectory(dst)) {
IOException ex = new IOException("Destination is a directory: " + dst);
dialogService.showErrorDialogAndWait(
Localization.lang("Cannot copy '%0' to '%1'", src, dst), ex);
continue;
}

try {
Files.copy(src, dst, StandardCopyOption.REPLACE_EXISTING);
copied++;
} catch (IOException ex) {
dialogService.showErrorDialogAndWait(
Localization.lang("Cannot copy '%0' to '%1'", src, dst), ex);
}
}

if (copied > 0) {
dialogService.notify(Localization.lang("Copied %0 file(s) to %1", copied, targetDir));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -203,8 +203,11 @@ public void acceptAsLinked() {

public Observable[] getObservables() {
List<Observable> observables = new ArrayList<>(Arrays.asList(linkedFile.getObservables()));
/*
Fix: JavaFX crashes with these 2 options lower while multi-downloading 2 or more files
observables.add(downloadOngoing);
observables.add(downloadProgress);
*/
observables.add(isAutomaticallyFound);
return observables.toArray(new Observable[0]);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,15 +59,23 @@ public ContextAction(StandardActions command,
public void execute() {
switch (command) {
case EDIT_FILE_LINK -> linkedFile.edit();
case OPEN_FILE -> linkedFile.open();
case OPEN_FOLDER -> linkedFile.openFolder();
case DOWNLOAD_FILE -> linkedFile.download(true);
case REDOWNLOAD_FILE -> linkedFile.redownload();
case OPEN_FILE, OPEN_FILES -> linkedFile.open();
case OPEN_FOLDER, OPEN_FOLDERS -> linkedFile.openFolder();
case DOWNLOAD_FILE, DOWNLOAD_FILES -> {
if (linkedFile.getFile().isOnlineLink()) {
linkedFile.download(true);
}
}
case REDOWNLOAD_FILE, REDOWNLOAD_FILES -> {
if (!linkedFile.getFile().getSourceUrl().isEmpty()) {
linkedFile.redownload();
}
}
case RENAME_FILE_TO_PATTERN -> linkedFile.renameToSuggestion();
case RENAME_FILE_TO_NAME -> linkedFile.askForNameAndRename();
case MOVE_FILE_TO_FOLDER -> linkedFile.moveToDefaultDirectory();
case MOVE_FILE_TO_FOLDER, MOVE_FILES_TO_FOLDER -> linkedFile.moveToDefaultDirectory();
case MOVE_FILE_TO_FOLDER_AND_RENAME -> linkedFile.moveToDefaultDirectoryAndRename();
case DELETE_FILE -> viewModel.deleteFile(linkedFile);
case DELETE_FILE, DELETE_FILES -> viewModel.deleteFile(linkedFile);
case REMOVE_LINK, REMOVE_LINKS -> viewModel.removeFileLink(linkedFile);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.jabref.gui.DialogService;
import org.jabref.gui.actions.ActionFactory;
import org.jabref.gui.actions.StandardActions;
import org.jabref.gui.copyfiles.CopyMultipleFilesAction;
import org.jabref.gui.copyfiles.CopySingleFileAction;
import org.jabref.gui.fieldeditors.LinkedFileViewModel;
import org.jabref.gui.fieldeditors.LinkedFilesEditorViewModel;
Expand All @@ -21,8 +22,6 @@ public class ContextMenuFactory {
private final DialogService dialogService;
private final GuiPreferences preferences;
private final BibDatabaseContext databaseContext;
private final ObservableOptionalValue<BibEntry> bibEntry;
private final LinkedFilesEditorViewModel viewModel;
private final SingleContextCommandFactory singleCommandFactory;
private final MultiContextCommandFactory multiCommandFactory;

Expand All @@ -36,8 +35,6 @@ public ContextMenuFactory(DialogService dialogService,
this.dialogService = dialogService;
this.preferences = preferences;
this.databaseContext = databaseContext;
this.bibEntry = bibEntry;
this.viewModel = viewModel;
this.singleCommandFactory = singleCommandFactory;
this.multiCommandFactory = multiCommandFactory;
}
Expand All @@ -56,10 +53,33 @@ private ContextMenu createContextMenuForMultiFile(ObservableList<LinkedFileViewM
ActionFactory factory = new ActionFactory();

menu.getItems().addAll(
factory.createMenuItem(
StandardActions.OPEN_FILES,
multiCommandFactory.build(StandardActions.OPEN_FILES, selectedFiles)),
factory.createMenuItem(
StandardActions.OPEN_FOLDERS,
multiCommandFactory.build(StandardActions.OPEN_FOLDERS, selectedFiles)),
new SeparatorMenuItem(),
factory.createMenuItem(
StandardActions.DOWNLOAD_FILES,
multiCommandFactory.build(StandardActions.DOWNLOAD_FILES, selectedFiles)),
factory.createMenuItem(
StandardActions.REDOWNLOAD_FILES,
multiCommandFactory.build(StandardActions.REDOWNLOAD_FILES, selectedFiles)),
new SeparatorMenuItem(),
factory.createMenuItem(
StandardActions.MOVE_FILES_TO_FOLDER,
multiCommandFactory.build(StandardActions.MOVE_FILES_TO_FOLDER, selectedFiles)),
factory.createMenuItem(
StandardActions.COPY_FILES_TO_FOLDER,
new CopyMultipleFilesAction(selectedFiles, dialogService, databaseContext, preferences.getFilePreferences())),
new SeparatorMenuItem(),
factory.createMenuItem(
StandardActions.REMOVE_LINKS,
multiCommandFactory.build(StandardActions.REMOVE_LINKS, selectedFiles)
)
multiCommandFactory.build(StandardActions.REMOVE_LINKS, selectedFiles)),
factory.createMenuItem(
StandardActions.DELETE_FILES,
multiCommandFactory.build(StandardActions.DELETE_FILES, selectedFiles))
);

return menu;
Expand Down
Loading