Skip to content

Commit 589a30e

Browse files
authored
Merge pull request #2632 from JabRef/groupDragDrop
Add drag and drop for groups -> groups
2 parents 8a43694 + 1c636a7 commit 589a30e

21 files changed

+425
-503
lines changed
+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package org.jabref.gui;
2+
3+
import javafx.scene.input.DataFormat;
4+
5+
/**
6+
* Contains all the different {@link DataFormat}s that may occur in JabRef.
7+
*/
8+
public class DragAndDropDataFormats {
9+
10+
public static final DataFormat GROUP = new DataFormat("dnd/org.jabref.model.groups.GroupTreeNode");
11+
public static final DataFormat ENTRIES = new DataFormat("application/x-java-jvm-local-objectref");
12+
}

Diff for: src/main/java/org/jabref/gui/groups/AddToGroupAction.java

+8-8
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ public AddToGroupAction(GroupTreeNodeViewModel node, boolean move, BasePanel pan
3535
}
3636

3737
public AddToGroupAction(boolean move) {
38-
super(move ? Localization.lang("Assign entry selection exclusively to this group") :
39-
Localization.lang("Add entry selection to this group"));
38+
super(move ? Localization.lang("Assign entry selection exclusively to this group") : Localization
39+
.lang("Add entry selection to this group"));
4040
this.move = move;
4141
}
4242

@@ -73,19 +73,19 @@ public void actionPerformed(ActionEvent evt) {
7373
}
7474

7575
private void moveToGroup(List<BibEntry> entries, NamedCompound undoAll) {
76-
List<AbstractGroup> affectedGroups =
77-
node.getNode().getRoot().getContainingGroups(entries, false).stream()
78-
.map(GroupTreeNode::getGroup)
79-
.filter(group -> group instanceof GroupEntryChanger)
80-
.collect(Collectors.toList());
76+
List<AbstractGroup> affectedGroups = node.getNode().getRoot()
77+
.getContainingGroups(entries, false).stream()
78+
.map(GroupTreeNode::getGroup)
79+
.filter(group -> group instanceof GroupEntryChanger)
80+
.collect(Collectors.toList());
8181
affectedGroups.add(node.getNode().getGroup());
8282
if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(affectedGroups, panel.frame())) {
8383
return; // user aborted operation
8484
}
8585

8686
// first remove
8787
for (AbstractGroup group : affectedGroups) {
88-
GroupEntryChanger entryChanger = (GroupEntryChanger)group;
88+
GroupEntryChanger entryChanger = (GroupEntryChanger) group;
8989
List<FieldChange> changes = entryChanger.remove(entries);
9090
if (!changes.isEmpty()) {
9191
undoAll.addEdit(UndoableChangeEntriesOfGroup.getUndoableEdit(node, changes));

Diff for: src/main/java/org/jabref/gui/groups/EntryTableTransferHandler.java

+1-11
Original file line numberDiff line numberDiff line change
@@ -43,22 +43,14 @@
4343
public class EntryTableTransferHandler extends TransferHandler {
4444

4545
private final MainTable entryTable;
46-
4746
private final JabRefFrame frame;
48-
4947
private final BasePanel panel;
50-
5148
private DataFlavor urlFlavor;
52-
5349
private final DataFlavor stringFlavor;
54-
5550
private static final boolean DROP_ALLOWED = true;
56-
5751
private static final Log LOGGER = LogFactory.getLog(EntryTableTransferHandler.class);
58-
5952
private boolean draggingFile;
6053

61-
6254
/**
6355
* Construct the transfer handler.
6456
*
@@ -174,14 +166,12 @@ public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
174166
return false;
175167
}
176168

177-
178-
179169
@Override
180170
public void exportAsDrag(JComponent comp, InputEvent e, int action) {
181171
if (e instanceof MouseEvent) {
182172
int columnIndex = entryTable.columnAtPoint(((MouseEvent) e).getPoint());
183173
int modelIndex = entryTable.getColumnModel().getColumn(columnIndex).getModelIndex();
184-
if(entryTable.isFileColumn(modelIndex)) {
174+
if (entryTable.isFileColumn(modelIndex)) {
185175
LOGGER.info("Dragging file");
186176
draggingFile = true;
187177
}

Diff for: src/main/java/org/jabref/gui/groups/GroupAddRemoveDialog.java

-2
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ public class GroupAddRemoveDialog implements BaseAction {
4747
private List<BibEntry> selection;
4848
private JTree tree;
4949

50-
5150
public GroupAddRemoveDialog(BasePanel panel, boolean add, boolean move) {
5251
this.panel = panel;
5352
this.add = add;
@@ -199,7 +198,6 @@ private boolean checkGroupEnable(GroupTreeNodeViewModel node) {
199198
return (add ? node.canAddEntries(selection) : node.canRemoveEntries(selection));
200199
}
201200

202-
203201
class AddRemoveGroupTreeCellRenderer extends GroupTreeCellRenderer {
204202

205203
@Override

Diff for: src/main/java/org/jabref/gui/groups/GroupDescriptions.java

+19-19
Original file line numberDiff line numberDiff line change
@@ -9,25 +9,25 @@
99
public class GroupDescriptions {
1010

1111
public static String getDescriptionForPreview(String field, String expr, boolean caseSensitive, boolean regExp) {
12-
String header = regExp ? Localization.lang("This group contains entries whose <b>%0</b> field contains the regular expression <b>%1</b>",
13-
field, StringUtil.quoteForHTML(expr))
14-
: Localization.lang("This group contains entries whose <b>%0</b> field contains the keyword <b>%1</b>",
15-
field, StringUtil.quoteForHTML(expr));
16-
String caseSensitiveText = caseSensitive ? Localization.lang("case sensitive") :
17-
Localization.lang("case insensitive");
18-
String footer = regExp ?
19-
Localization.lang("Entries cannot be manually assigned to or removed from this group.")
20-
: Localization.lang(
21-
"Additionally, entries whose <b>%0</b> field does not contain "
22-
+ "<b>%1</b> can be assigned manually to this group by selecting them "
23-
+ "then using either drag and drop or the context menu. "
24-
+ "This process adds the term <b>%1</b> to "
25-
+ "each entry's <b>%0</b> field. "
26-
+ "Entries can be removed manually from this group by selecting them "
27-
+ "then using the context menu. "
28-
+ "This process removes the term <b>%1</b> from "
29-
+ "each entry's <b>%0</b> field.",
30-
field, StringUtil.quoteForHTML(expr));
12+
String header = regExp ? Localization.lang(
13+
"This group contains entries whose <b>%0</b> field contains the regular expression <b>%1</b>",
14+
field, StringUtil.quoteForHTML(expr)) : Localization.lang(
15+
"This group contains entries whose <b>%0</b> field contains the keyword <b>%1</b>",
16+
field, StringUtil.quoteForHTML(expr));
17+
String caseSensitiveText = caseSensitive ? Localization.lang("case sensitive") : Localization
18+
.lang("case insensitive");
19+
String footer = regExp ? Localization
20+
.lang("Entries cannot be manually assigned to or removed from this group.") : Localization.lang(
21+
"Additionally, entries whose <b>%0</b> field does not contain "
22+
+ "<b>%1</b> can be assigned manually to this group by selecting them "
23+
+ "then using either drag and drop or the context menu. "
24+
+ "This process adds the term <b>%1</b> to "
25+
+ "each entry's <b>%0</b> field. "
26+
+ "Entries can be removed manually from this group by selecting them "
27+
+ "then using the context menu. "
28+
+ "This process removes the term <b>%1</b> from "
29+
+ "each entry's <b>%0</b> field.",
30+
field, StringUtil.quoteForHTML(expr));
3131
return String.format("%s (%s). %s", header, caseSensitiveText, footer);
3232
}
3333

Diff for: src/main/java/org/jabref/gui/groups/GroupNodeViewModel.java

+67-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package org.jabref.gui.groups;
22

3+
import java.util.List;
34
import java.util.Map;
45
import java.util.Objects;
6+
import java.util.Optional;
57
import java.util.concurrent.ConcurrentHashMap;
68
import java.util.function.Function;
79
import java.util.function.Predicate;
@@ -15,18 +17,22 @@
1517
import javafx.beans.property.SimpleIntegerProperty;
1618
import javafx.collections.FXCollections;
1719
import javafx.collections.ObservableList;
20+
import javafx.scene.input.Dragboard;
1821
import javafx.scene.paint.Color;
1922

23+
import org.jabref.gui.DragAndDropDataFormats;
2024
import org.jabref.gui.IconTheme;
2125
import org.jabref.gui.StateManager;
2226
import org.jabref.gui.util.BindingsHelper;
2327
import org.jabref.logic.groups.DefaultGroupsFactory;
2428
import org.jabref.logic.layout.format.LatexToUnicodeFormatter;
29+
import org.jabref.model.FieldChange;
2530
import org.jabref.model.database.BibDatabaseContext;
2631
import org.jabref.model.entry.BibEntry;
2732
import org.jabref.model.entry.event.EntryEvent;
2833
import org.jabref.model.groups.AbstractGroup;
2934
import org.jabref.model.groups.AutomaticGroup;
35+
import org.jabref.model.groups.GroupEntryChanger;
3036
import org.jabref.model.groups.GroupTreeNode;
3137
import org.jabref.model.strings.StringUtil;
3238

@@ -39,6 +45,7 @@ public class GroupNodeViewModel {
3945
private final boolean isRoot;
4046
private final ObservableList<GroupNodeViewModel> children;
4147
private final BibDatabaseContext databaseContext;
48+
private final StateManager stateManager;
4249
private final GroupTreeNode groupNode;
4350
private final SimpleIntegerProperty hits;
4451
private final SimpleBooleanProperty hasChildren;
@@ -47,6 +54,7 @@ public class GroupNodeViewModel {
4754
private final BooleanBinding allSelectedEntriesMatched;
4855
public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager stateManager, GroupTreeNode groupNode) {
4956
this.databaseContext = Objects.requireNonNull(databaseContext);
57+
this.stateManager = Objects.requireNonNull(stateManager);
5058
this.groupNode = Objects.requireNonNull(groupNode);
5159

5260
LatexToUnicodeFormatter formatter = new LatexToUnicodeFormatter();
@@ -58,7 +66,7 @@ public GroupNodeViewModel(BibDatabaseContext databaseContext, StateManager state
5866
// TODO: Update on changes to entry list (however: there is no flatMap and filter as observable TransformationLists)
5967
children = databaseContext.getDatabase()
6068
.getEntries().stream()
61-
.flatMap(stream -> createSubgroups(databaseContext, stateManager, automaticGroup, stream))
69+
.flatMap(stream -> createSubgroups(automaticGroup, stream))
6270
.filter(distinctByKey(group -> group.getGroupNode().getName()))
6371
.sorted((group1, group2) -> group1.getDisplayName().compareToIgnoreCase(group2.getDisplayName()))
6472
.collect(Collectors.toCollection(FXCollections::observableArrayList));
@@ -94,11 +102,25 @@ static GroupNodeViewModel getAllEntriesGroup(BibDatabaseContext newDatabase, Sta
94102
return new GroupNodeViewModel(newDatabase, stateManager, DefaultGroupsFactory.getAllEntriesGroup());
95103
}
96104

97-
private Stream<GroupNodeViewModel> createSubgroups(BibDatabaseContext databaseContext, StateManager stateManager, AutomaticGroup automaticGroup, BibEntry entry) {
105+
private Stream<GroupNodeViewModel> createSubgroups(AutomaticGroup automaticGroup, BibEntry entry) {
98106
return automaticGroup.createSubgroups(entry).stream()
99107
.map(child -> new GroupNodeViewModel(databaseContext, stateManager, child));
100108
}
101109

110+
public List<FieldChange> addEntriesToGroup(List<BibEntry> entries) {
111+
// TODO: warn if assignment has undesired side effects (modifies a field != keywords)
112+
//if (!WarnAssignmentSideEffects.warnAssignmentSideEffects(group, groupSelector.frame))
113+
//{
114+
// return; // user aborted operation
115+
//}
116+
117+
return groupNode.addEntriesToGroup(entries);
118+
119+
// TODO: Store undo
120+
// if (!undo.isEmpty()) {
121+
// groupSelector.concludeAssignment(UndoableChangeEntriesOfGroup.getUndoableEdit(target, undo), target.getNode(), assignedEntries);
122+
}
123+
102124
public SimpleBooleanProperty expandedProperty() {
103125
return expandedProperty;
104126
}
@@ -133,12 +155,18 @@ public SimpleIntegerProperty getHits() {
133155

134156
@Override
135157
public boolean equals(Object o) {
136-
if (this == o) return true;
137-
if (o == null || getClass() != o.getClass()) return false;
158+
if (this == o) {
159+
return true;
160+
}
161+
if ((o == null) || (getClass() != o.getClass())) {
162+
return false;
163+
}
138164

139165
GroupNodeViewModel that = (GroupNodeViewModel) o;
140166

141-
if (!groupNode.equals(that.groupNode)) return false;
167+
if (!groupNode.equals(that.groupNode)) {
168+
return false;
169+
}
142170
return true;
143171
}
144172

@@ -205,4 +233,38 @@ boolean isMatchedBy(String searchString) {
205233
public Color getColor() {
206234
return groupNode.getGroup().getColor().orElse(IconTheme.getDefaultColor());
207235
}
236+
237+
public String getPath() {
238+
return groupNode.getPath();
239+
}
240+
241+
public Optional<GroupNodeViewModel> getChildByPath(String pathToSource) {
242+
return groupNode.getChildByPath(pathToSource).map(child -> new GroupNodeViewModel(databaseContext, stateManager, child));
243+
}
244+
245+
/**
246+
* Decides if the content stored in the given {@link Dragboard} can be droped on the given target row.
247+
* Currently, the following sources are allowed:
248+
* - another group (will be added as subgroup on drop)
249+
* - entries if the group implements {@link GroupEntryChanger} (will be assigned to group on drop)
250+
*/
251+
public boolean acceptableDrop(Dragboard dragboard) {
252+
// TODO: we should also check isNodeDescendant
253+
boolean canDropOtherGroup = dragboard.hasContent(DragAndDropDataFormats.GROUP);
254+
boolean canDropEntries = dragboard.hasContent(DragAndDropDataFormats.ENTRIES)
255+
&& groupNode.getGroup() instanceof GroupEntryChanger;
256+
return canDropOtherGroup || canDropEntries;
257+
}
258+
259+
public void moveTo(GroupNodeViewModel target) {
260+
// TODO: Add undo and display message
261+
//MoveGroupChange undo = new MoveGroupChange(((GroupTreeNodeViewModel)source.getParent()).getNode(),
262+
// source.getNode().getPositionInParent(), target.getNode(), target.getChildCount());
263+
264+
getGroupNode().moveTo(target.getGroupNode());
265+
//panel.getUndoManager().addEdit(new UndoableMoveGroup(this.groupsRoot, moveChange));
266+
//panel.markBaseChanged();
267+
//frame.output(Localization.lang("Moved group \"%0\".", node.getNode().getGroup().getName()));
268+
269+
}
208270
}

0 commit comments

Comments
 (0)