Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Copy to option #12374

Merged
merged 17 commits into from
Jan 27, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
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 @@ -29,6 +29,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv

### Added

- We added a 'Copy to' context menu option with features for cross-reference inclusion/exclusion, as well as the ability to save user preferences. [#12374](https://github.com/JabRef/jabref/pull/12374)
- We added a Markdown export layout. [#12220](https://github.com/JabRef/jabref/pull/12220)
- We added a "view as BibTeX" option before importing an entry from the citation relation tab. [#11826](https://github.com/JabRef/jabref/issues/11826)
- We added support finding LaTeX-encoded special characters based on plain Unicode and vice versa. [#11542](https://github.com/JabRef/jabref/pull/11542)
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/org/jabref/gui/actions/ActionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,15 @@ public MenuItem createMenuItem(Action action, Command command) {
return menuItem;
}

public MenuItem createCustomMenuItem(Action action, Command command, String text) {
MenuItem menuItem = new MenuItem();
configureMenuItem(action, command, menuItem);
menuItem.textProperty().unbind();
menuItem.setText(text);

return menuItem;
}

public CheckMenuItem createCheckMenuItem(Action action, Command command, boolean selected) {
CheckMenuItem checkMenuItem = ActionUtils.createCheckMenuItem(new JabRefAction(action, command, keyBindingRepository));
checkMenuItem.setSelected(selected);
Expand Down
1 change: 1 addition & 0 deletions src/main/java/org/jabref/gui/actions/StandardActions.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

public enum StandardActions implements Action {

COPY_TO(Localization.lang("Copy to")),
COPY_MORE(Localization.lang("Copy") + "..."),
COPY_TITLE(Localization.lang("Copy title"), KeyBinding.COPY_TITLE),
COPY_KEY(Localization.lang("Copy citation key"), KeyBinding.COPY_CITATION_KEY),
Expand Down
109 changes: 109 additions & 0 deletions src/main/java/org/jabref/gui/edit/CopyTo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package org.jabref.gui.edit;

import java.util.ArrayList;
import java.util.List;

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.externalfiles.ImportHandler;
import org.jabref.logic.l10n.Localization;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntry;
import org.jabref.model.entry.field.StandardField;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class CopyTo extends SimpleCommand {

private static final Logger LOGGER = LoggerFactory.getLogger(CopyTo.class);

private final DialogService dialogService;
private final StateManager stateManager;
private final CopyToPreferences copyToPreferences;
private final ImportHandler importHandler;
private final BibDatabaseContext sourceDatabaseContext;
private final BibDatabaseContext targetDatabaseContext;

public CopyTo(DialogService dialogService,
StateManager stateManager,
CopyToPreferences copyToPreferences,
ImportHandler importHandler,
BibDatabaseContext sourceDatabaseContext,
BibDatabaseContext targetDatabaseContext) {
this.dialogService = dialogService;
this.stateManager = stateManager;
this.copyToPreferences = copyToPreferences;
this.importHandler = importHandler;
this.sourceDatabaseContext = sourceDatabaseContext;
this.targetDatabaseContext = targetDatabaseContext;

this.executable.bind(ActionHelper.needsEntriesSelected(stateManager));
}

@Override
public void execute() {
List<BibEntry> selectedEntries = stateManager.getSelectedEntries();

boolean includeCrossReferences = false;
boolean showDialogBox = copyToPreferences.getShouldAskForIncludingCrossReferences();

for (BibEntry bibEntry: selectedEntries) {
if (bibEntry.hasField(StandardField.CROSSREF) && showDialogBox) {
includeCrossReferences = askForCrossReferencedEntries();
copyToPreferences.setShouldIncludeCrossReferences(includeCrossReferences);
}
}

if (includeCrossReferences) {
copyEntriesWithCrossRef(selectedEntries, targetDatabaseContext);
} else {
copyEntriesWithoutCrossRef(selectedEntries, targetDatabaseContext);
}
}

public void copyEntriesWithCrossRef(List<BibEntry> selectedEntries, BibDatabaseContext targetDatabaseContext) {
List<BibEntry> entriesToAdd = new ArrayList<>(selectedEntries);

for (BibEntry bibEntry: selectedEntries) {
if (bibEntry.hasField(StandardField.CROSSREF)) {
BibEntry crossRefEntry = getCrossRefEntry(bibEntry, sourceDatabaseContext);
if (crossRefEntry != null) {
entriesToAdd.add(crossRefEntry);
}
}
}

importHandler.importEntriesWithDuplicateCheck(targetDatabaseContext, entriesToAdd);
}

public void copyEntriesWithoutCrossRef(List<BibEntry> selectedEntries, BibDatabaseContext targetDatabaseContext) {
importHandler.importEntriesWithDuplicateCheck(targetDatabaseContext, selectedEntries);
}

public BibEntry getCrossRefEntry(BibEntry bibEntry, BibDatabaseContext sourceDatabaseContext) {
for (BibEntry entry: sourceDatabaseContext.getEntries()) {
if (bibEntry.getField(StandardField.CROSSREF).equals(entry.getCitationKey())) {
return entry;
}
}
return null;
}

private boolean askForCrossReferencedEntries() {
if (copyToPreferences.getShouldAskForIncludingCrossReferences()) {
return dialogService.showConfirmationDialogWithOptOutAndWait(
Localization.lang("Include or exclude cross-referenced entries"),
Localization.lang("Would you like to include cross-reference entries in the current operation?"),
Localization.lang("Include"),
Localization.lang("Exclude"),
Localization.lang("Do not ask again"),
optOut -> copyToPreferences.setShouldAskForIncludingCrossReferences(!optOut)
);
} else {
return copyToPreferences.getShouldIncludeCrossReferences();
}
}
}
30 changes: 30 additions & 0 deletions src/main/java/org/jabref/gui/edit/CopyToPreferences.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package org.jabref.gui.edit;

import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;

public class CopyToPreferences {
private final BooleanProperty shouldIncludeCrossReferences = new SimpleBooleanProperty();
private final BooleanProperty shouldAskForIncludingCrossReferences = new SimpleBooleanProperty();

public CopyToPreferences(boolean shouldAskForIncludingCrossReferences, boolean shouldIncludeCrossReferences) {
this.shouldIncludeCrossReferences.set(shouldIncludeCrossReferences);
this.shouldAskForIncludingCrossReferences.set(shouldAskForIncludingCrossReferences);
}

public boolean getShouldIncludeCrossReferences() {
priyanshu16095 marked this conversation as resolved.
Show resolved Hide resolved
return shouldIncludeCrossReferences.get();
}

public void setShouldIncludeCrossReferences(boolean decision) {
this.shouldIncludeCrossReferences.set(decision);
}

public boolean getShouldAskForIncludingCrossReferences() {
return shouldAskForIncludingCrossReferences.get();
}

public void setShouldAskForIncludingCrossReferences(boolean decision) {
this.shouldAskForIncludingCrossReferences.set(decision);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ public void importCleanedEntries(List<BibEntry> entries) {
addToGroups(entries, stateManager.getSelectedGroups(bibDatabaseContext));
}

public void importCleanedEntries(BibDatabaseContext bibDatabaseContext, List<BibEntry> entries) {
bibDatabaseContext.getDatabase().insertEntries(entries);
generateKeys(entries);
setAutomaticFields(entries);
addToGroups(entries, stateManager.getSelectedGroups(bibDatabaseContext));
}

public void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext, BibEntry entry) {
importEntryWithDuplicateCheck(bibDatabaseContext, entry, BREAK);
}
Expand All @@ -239,7 +246,7 @@ private void importEntryWithDuplicateCheck(BibDatabaseContext bibDatabaseContext
}
finalEntry = duplicateHandledEntry.get();
}
importCleanedEntries(List.of(finalEntry));
importCleanedEntries(bibDatabaseContext, List.of(finalEntry));
downloadLinkedFiles(finalEntry);
BibEntry entryToFocus = finalEntry;
stateManager.activeTabProperty().get().ifPresent(tab -> tab.clearAndSelect(entryToFocus));
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/org/jabref/gui/maintable/MainTable.java
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,8 @@ public MainTable(MainTableDataModel model,
clipBoardManager,
taskExecutor,
Injector.instantiateModelOrService(JournalAbbreviationRepository.class),
entryTypesManager))
entryTypesManager,
importHandler))
.withPseudoClass(MATCHING_SEARCH_AND_GROUPS, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_AND_GROUPS))
.withPseudoClass(MATCHING_SEARCH_NOT_GROUPS, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_SEARCH_NOT_GROUPS))
.withPseudoClass(MATCHING_GROUPS_NOT_SEARCH, entry -> entry.matchCategory().isEqualTo(MatchCategory.MATCHING_GROUPS_NOT_SEARCH))
Expand Down
59 changes: 58 additions & 1 deletion src/main/java/org/jabref/gui/maintable/RightClickMenu.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package org.jabref.gui.maintable;

import java.nio.file.Path;
import java.util.Optional;

import javax.swing.undo.UndoManager;

import javafx.collections.ObservableList;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
Expand All @@ -14,8 +18,10 @@
import org.jabref.gui.actions.ActionFactory;
import org.jabref.gui.actions.StandardActions;
import org.jabref.gui.edit.CopyMoreAction;
import org.jabref.gui.edit.CopyTo;
import org.jabref.gui.edit.EditAction;
import org.jabref.gui.exporter.ExportToClipboardAction;
import org.jabref.gui.externalfiles.ImportHandler;
import org.jabref.gui.frame.SendAsKindleEmailAction;
import org.jabref.gui.frame.SendAsStandardEmailAction;
import org.jabref.gui.keyboard.KeyBindingRepository;
Expand All @@ -31,7 +37,11 @@
import org.jabref.logic.citationstyle.CitationStyleOutputFormat;
import org.jabref.logic.citationstyle.CitationStylePreviewLayout;
import org.jabref.logic.journals.JournalAbbreviationRepository;
import org.jabref.logic.l10n.Localization;
import org.jabref.logic.shared.DatabaseLocation;
import org.jabref.logic.util.TaskExecutor;
import org.jabref.logic.util.io.FileUtil;
import org.jabref.model.database.BibDatabaseContext;
import org.jabref.model.entry.BibEntryTypesManager;
import org.jabref.model.entry.field.SpecialField;

Expand All @@ -49,7 +59,8 @@ public static ContextMenu create(BibEntryTableViewModel entry,
ClipBoardManager clipBoardManager,
TaskExecutor taskExecutor,
JournalAbbreviationRepository abbreviationRepository,
BibEntryTypesManager entryTypesManager) {
BibEntryTypesManager entryTypesManager,
ImportHandler importHandler) {
ActionFactory factory = new ActionFactory();
ContextMenu contextMenu = new ContextMenu();

Expand All @@ -61,6 +72,7 @@ public static ContextMenu create(BibEntryTableViewModel entry,
contextMenu.getItems().addAll(
factory.createMenuItem(StandardActions.COPY, new EditAction(StandardActions.COPY, () -> libraryTab, stateManager, undoManager)),
createCopySubMenu(factory, dialogService, stateManager, preferences, clipBoardManager, abbreviationRepository, taskExecutor),
createCopyToMenu(factory, dialogService, stateManager, preferences, libraryTab, importHandler),
factory.createMenuItem(StandardActions.PASTE, new EditAction(StandardActions.PASTE, () -> libraryTab, stateManager, undoManager)),
factory.createMenuItem(StandardActions.CUT, new EditAction(StandardActions.CUT, () -> libraryTab, stateManager, undoManager)),
factory.createMenuItem(StandardActions.MERGE_ENTRIES, new MergeEntriesAction(dialogService, stateManager, undoManager, preferences)),
Expand Down Expand Up @@ -103,6 +115,51 @@ public static ContextMenu create(BibEntryTableViewModel entry,
return contextMenu;
}

private static Menu createCopyToMenu(ActionFactory factory,
DialogService dialogService,
StateManager stateManager,
GuiPreferences preferences,
LibraryTab libraryTab,
ImportHandler importHandler
) {
Menu copyToMenu = factory.createMenu(StandardActions.COPY_TO);

ObservableList<BibDatabaseContext> openDatabases = stateManager.getOpenDatabases();

BibDatabaseContext sourceDatabaseContext = libraryTab.getBibDatabaseContext();

Optional<Path> sourcePath = libraryTab.getBibDatabaseContext().getDatabasePath();
String sourceDatabaseName = FileUtil.getUniquePathFragment(stateManager.collectAllDatabasePaths(), sourcePath.get()).get();

if (!openDatabases.isEmpty()) {
openDatabases.forEach(bibDatabaseContext -> {
Optional<Path> destinationPath = Optional.empty();
String destinationDatabaseName = "";

if (bibDatabaseContext.getDatabasePath().isPresent()) {
destinationPath = bibDatabaseContext.getDatabasePath();
String uniquePathName = FileUtil.getUniquePathFragment(stateManager.collectAllDatabasePaths(), destinationPath.get()).get();
if (uniquePathName.equals(sourceDatabaseName)) {
return;
subhramit marked this conversation as resolved.
Show resolved Hide resolved
}
destinationDatabaseName = uniquePathName;
} else if (bibDatabaseContext.getLocation() == DatabaseLocation.SHARED) {
destinationDatabaseName = bibDatabaseContext.getDBMSSynchronizer().getDBName() + " [" + Localization.lang("shared") + "]";
}

copyToMenu.getItems().addAll(
factory.createCustomMenuItem(
StandardActions.COPY_TO,
new CopyTo(dialogService, stateManager, preferences.getCopyToPreferences(), importHandler, sourceDatabaseContext, bibDatabaseContext),
destinationDatabaseName
)
);
});
}

return copyToMenu;
}

private static Menu createCopySubMenu(ActionFactory factory,
DialogService dialogService,
StateManager stateManager,
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/jabref/gui/preferences/GuiPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import org.jabref.gui.CoreGuiPreferences;
import org.jabref.gui.WorkspacePreferences;
import org.jabref.gui.autocompleter.AutoCompletePreferences;
import org.jabref.gui.edit.CopyToPreferences;
import org.jabref.gui.entryeditor.EntryEditorPreferences;
import org.jabref.gui.externalfiles.UnlinkedFilesDialogPreferences;
import org.jabref.gui.frame.ExternalApplicationsPreferences;
Expand All @@ -19,6 +20,8 @@
import org.jabref.logic.preferences.CliPreferences;

public interface GuiPreferences extends CliPreferences {
CopyToPreferences getCopyToPreferences();

EntryEditorPreferences getEntryEditorPreferences();

MergeDialogPreferences getMergeDialogPreferences();
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/org/jabref/gui/preferences/JabRefGuiPreferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.jabref.gui.autocompleter.AutoCompletePreferences;
import org.jabref.gui.desktop.os.NativeDesktop;
import org.jabref.gui.duplicationFinder.DuplicateResolverDialog;
import org.jabref.gui.edit.CopyToPreferences;
import org.jabref.gui.entryeditor.EntryEditorPreferences;
import org.jabref.gui.externalfiles.UnlinkedFilesDialogPreferences;
import org.jabref.gui.externalfiletype.ExternalFileType;
Expand Down Expand Up @@ -213,6 +214,9 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre
private static final String UNLINKED_FILES_SELECTED_DATE_RANGE = "unlinkedFilesSelectedDateRange";
private static final String UNLINKED_FILES_SELECTED_SORT = "unlinkedFilesSelectedSort";

private static final String INCLUDE_CROSS_REFERENCES = "includeCrossReferences";
private static final String ASK_FOR_INCLUDING_CROSS_REFERENCES = "askForIncludingCrossReferences";

private static JabRefGuiPreferences singleton;

private EntryEditorPreferences entryEditorPreferences;
Expand All @@ -232,6 +236,7 @@ public class JabRefGuiPreferences extends JabRefCliPreferences implements GuiPre
private ColumnPreferences mainTableColumnPreferences;
private ColumnPreferences searchDialogColumnPreferences;
private KeyBindingRepository keyBindingRepository;
private CopyToPreferences copyToPreferences;

private JabRefGuiPreferences() {
super();
Expand Down Expand Up @@ -394,6 +399,9 @@ private JabRefGuiPreferences() {
// By default disable "Fit table horizontally on the screen"
defaults.put(AUTO_RESIZE_MODE, Boolean.FALSE);
// endregion

defaults.put(ASK_FOR_INCLUDING_CROSS_REFERENCES, Boolean.TRUE);
defaults.put(INCLUDE_CROSS_REFERENCES, Boolean.FALSE);
}

/**
Expand All @@ -409,6 +417,17 @@ public static JabRefGuiPreferences getInstance() {
return JabRefGuiPreferences.singleton;
}

public CopyToPreferences getCopyToPreferences() {
if (copyToPreferences != null) {
return copyToPreferences;
}
copyToPreferences = new CopyToPreferences(
getBoolean(ASK_FOR_INCLUDING_CROSS_REFERENCES),
getBoolean(INCLUDE_CROSS_REFERENCES)
);
return copyToPreferences;
}

// region EntryEditorPreferences
public EntryEditorPreferences getEntryEditorPreferences() {
if (entryEditorPreferences != null) {
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/l10n/JabRef_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -2820,3 +2820,9 @@ Citation\ Entry=Citation Entry

File\ Move\ Errors=File Move Errors
Could\ not\ move\ file\ %0.\ Please\ close\ this\ file\ and\ retry.=Could not move file %0. Please close this file and retry.

Copy\ to=Copy to
Include=Include
Exclude=Exclude
Include\ or\ exclude\ cross-referenced\ entries=Include or exclude cross-referenced entries
Would\ you\ like\ to\ include\ cross-reference\ entries\ in\ the\ current\ operation?=Would you like to include cross-reference entries in the current operation?
Loading
Loading