Skip to content

Commit a7d3256

Browse files
committed
Merge pull request #561 from JLLeitschuh/feat/dragAndDropOperations
Feat/drag and drop operations
2 parents 1ede713 + f02d15a commit a7d3256

File tree

17 files changed

+490
-58
lines changed

17 files changed

+490
-58
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ install:
2626
- '[ "${TRAVIS_PULL_REQUEST}" = "false" ] && ./gradlew :ui:assemble --stacktrace || ./gradlew --stacktrace '
2727

2828
script:
29-
- ./gradlew check --stacktrace -PprintTestResults
29+
- ./gradlew check --stacktrace -Pheadless=true
3030

3131
after_success:
3232
- codecov

appveyor.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ install:
22
- choco install -y InnoSetup
33

44
build_script:
5-
- gradlew.bat :ui:assemble --stacktrace -PprintTestResults
5+
- gradlew.bat :ui:assemble --stacktrace
66

77
# to run your custom scripts instead of automatic tests
88
test_script:
9-
- gradlew.bat check --stacktrace -PprintTestResults
9+
- gradlew.bat check --stacktrace -Pheadless=true
1010

1111
platform:
1212
- x64

build.gradle

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,7 @@ project(":ui") {
290290
testCompile files(project(':core').sourceSets.test.output.resourcesDir)
291291
testCompile group: 'org.testfx', name: 'testfx-core', version: '4.0.+'
292292
testCompile group: 'org.testfx', name: 'testfx-junit', version: '4.0.+'
293+
testRuntime group: 'org.testfx', name: 'openjfx-monocle', version: '1.8.0_20'
293294
}
294295

295296
evaluationDependsOn(':core')
@@ -299,6 +300,17 @@ project(":ui") {
299300
scopes.PROVIDED.plus += [configurations.ideProvider]
300301
}
301302

303+
/*
304+
* Allows you to run the UI tests in headless mode by calling gradle with the -Pheadless=true argument
305+
*/
306+
if (project.hasProperty('headless') && project.headless) {
307+
println "Running UI Tests Headless"
308+
test {
309+
jvmArgs = ['-Djava.awt.headless=true', '-Dtestfx.robot=glass', '-Dtestfx.headless=true', '-Dprism.order=sw', '-Dprism.text=t2k']
310+
}
311+
}
312+
313+
302314
javafx {
303315
profiles {
304316
linux {

core/src/main/java/edu/wpi/grip/core/Pipeline.java

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import edu.wpi.grip.core.sockets.OutputSocket;
1515
import edu.wpi.grip.core.sockets.SocketHint;
1616

17+
import javax.annotation.Nullable;
1718
import java.util.*;
1819
import java.util.concurrent.locks.Lock;
1920
import java.util.concurrent.locks.ReadWriteLock;
@@ -230,6 +231,37 @@ public void onSourceRemoved(SourceRemovedEvent event) {
230231
}
231232
}
232233

234+
/**
235+
* Adds the step between two other steps.
236+
* @param stepToAdd The step to add to the pipeline
237+
* @param lower The step to be added above
238+
* @param higher The step to be added below
239+
*/
240+
public void addStepBetween(Step stepToAdd, @Nullable Step lower, @Nullable Step higher) {
241+
checkNotNull(stepToAdd, "The step to add cannot be null");
242+
int index = readStepsSafely(steps -> {
243+
// If not in the list these can return -1
244+
int lowerIndex = steps.indexOf(lower);
245+
int upperIndex = steps.indexOf(higher);
246+
if (lowerIndex != -1 && upperIndex != -1) {
247+
assert lowerIndex <= upperIndex : "Upper step was before lower index";
248+
int difference = Math.abs(upperIndex - lowerIndex); // Just in case
249+
// Place the step halfway between these two steps
250+
return lowerIndex + 1 + difference / 2;
251+
} else if (lowerIndex != -1) {
252+
// Place it above the lower one
253+
return lowerIndex + 1;
254+
} else if (upperIndex != -1) {
255+
// Place it below the upper one
256+
return upperIndex;
257+
} else {
258+
// Otherwise if they are both null place it at the end of the list
259+
return steps.size();
260+
}
261+
});
262+
addStep(index, stepToAdd);
263+
}
264+
233265
public void addStep(int index, Step step) {
234266
checkNotNull(step, "The step can not be null");
235267
checkArgument(!step.removed(), "The step must not have been disabled already");

core/src/test/java/edu/wpi/grip/core/PipelineTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -275,4 +275,64 @@ public void testCannotConnectIncompatibleTypes() {
275275

276276
assertFalse("Should not be able to connect incompatible types", pipeline.canConnect((OutputSocket) b, (InputSocket) a));
277277
}
278+
279+
@Test
280+
public void testAddBetweenSteps() {
281+
final Step
282+
stepToAdd = new MockStep(),
283+
lowerStep = new MockStep(),
284+
upperStep = new MockStep();
285+
pipeline.addStep(lowerStep);
286+
pipeline.addStep(upperStep);
287+
288+
pipeline.addStepBetween(stepToAdd, lowerStep, upperStep);
289+
assertEquals("The step was not added to the middle of the pipeline",
290+
Arrays.asList(lowerStep, stepToAdd, upperStep), pipeline.getSteps());
291+
}
292+
293+
@Test
294+
public void testAddBetweenNullAndStep() {
295+
final Step
296+
stepToAdd = new MockStep(),
297+
lowerStep = new MockStep(),
298+
upperStep = new MockStep();
299+
pipeline.addStep(lowerStep);
300+
pipeline.addStep(upperStep);
301+
pipeline.addStepBetween(stepToAdd, null, lowerStep);
302+
assertEquals("The step was not added to the begining of the pipeline",
303+
Arrays.asList(stepToAdd, lowerStep, upperStep), pipeline.getSteps());
304+
}
305+
306+
@Test
307+
public void testAddBetweenStepAndNull() {
308+
final Step
309+
stepToAdd = new MockStep(),
310+
lowerStep = new MockStep(),
311+
upperStep = new MockStep();
312+
pipeline.addStep(lowerStep);
313+
pipeline.addStep(upperStep);
314+
pipeline.addStepBetween(stepToAdd, upperStep, null);
315+
assertEquals("The step was not added to the end of the pipeline",
316+
Arrays.asList(lowerStep, upperStep, stepToAdd), pipeline.getSteps());
317+
}
318+
319+
@Test
320+
public void testAddBetweenTwoNulls() {
321+
final Step stepToAdd = new MockStep();
322+
pipeline.addStepBetween(stepToAdd, null, null);
323+
assertEquals("The step should have been added to the pipeline",
324+
Collections.singletonList(stepToAdd), pipeline.getSteps());
325+
}
326+
327+
@Test(expected = AssertionError.class)
328+
public void testAddBetweenStepsOutOfOrder() {
329+
final Step
330+
stepToAdd = new MockStep(),
331+
lowerStep = new MockStep(),
332+
upperStep = new MockStep();
333+
pipeline.addStep(lowerStep);
334+
pipeline.addStep(upperStep);
335+
336+
pipeline.addStepBetween(stepToAdd, upperStep, lowerStep);
337+
}
278338
}

ui/src/main/java/edu/wpi/grip/ui/OperationController.java

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,20 @@
66
import edu.wpi.grip.core.Pipeline;
77
import edu.wpi.grip.core.Step;
88
import edu.wpi.grip.ui.annotations.ParametrizedController;
9+
import edu.wpi.grip.ui.dragging.OperationDragService;
910
import edu.wpi.grip.ui.util.StyleClassNameUtility;
1011
import javafx.fxml.FXML;
1112
import javafx.scene.control.Label;
1213
import javafx.scene.control.Tooltip;
1314
import javafx.scene.image.Image;
1415
import javafx.scene.image.ImageView;
16+
import javafx.scene.input.DataFormat;
17+
import javafx.scene.input.Dragboard;
18+
import javafx.scene.input.TransferMode;
1519
import javafx.scene.layout.GridPane;
1620

21+
import java.util.Collections;
22+
1723
/**
1824
* A JavaFX control that renders information about an {@link Operation}. This is used in the palette view to present
1925
* the user with information on the various operations to choose from.
@@ -35,16 +41,18 @@ public class OperationController implements Controller {
3541

3642
private final Pipeline pipeline;
3743
private final Step.Factory stepFactory;
44+
private final OperationDragService operationDragService;
3845
private final Operation operation;
3946

4047
public interface Factory {
4148
OperationController create(Operation operation);
4249
}
4350

4451
@Inject
45-
OperationController(Pipeline pipeline, Step.Factory stepFactory, @Assisted Operation operation) {
52+
OperationController(Pipeline pipeline, Step.Factory stepFactory, OperationDragService operationDragService, @Assisted Operation operation) {
4653
this.pipeline = pipeline;
4754
this.stepFactory = stepFactory;
55+
this.operationDragService = operationDragService;
4856
this.operation = operation;
4957
}
5058

@@ -65,6 +73,22 @@ public void initialize() {
6573

6674
// Ensures that when this element is hidden that it also removes its size calculations
6775
root.managedProperty().bind(root.visibleProperty());
76+
77+
78+
root.setOnDragDetected(mouseEvent -> {
79+
// Create a snapshot to use as the cursor
80+
final ImageView preview = new ImageView(root.snapshot(null, null));
81+
82+
final Dragboard db = root.startDragAndDrop(TransferMode.ANY);
83+
db.setContent(Collections.singletonMap(DataFormat.PLAIN_TEXT, operation.getName()));
84+
db.setDragView(preview.getImage());
85+
// Begin the dragging
86+
root.startFullDrag();
87+
// Tell the drag service that this is the operation that will be received
88+
operationDragService.beginDrag(operation);
89+
90+
mouseEvent.consume();
91+
});
6892
}
6993

7094
@FXML
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package edu.wpi.grip.ui.dragging;
2+
3+
4+
import javafx.beans.property.ObjectProperty;
5+
import javafx.beans.property.ReadOnlyObjectProperty;
6+
import javafx.beans.property.SimpleObjectProperty;
7+
8+
import java.util.Optional;
9+
10+
/**
11+
* A service to provide data transfer capabilities between two controllers.
12+
* Concrete versions of the service are usually {@link com.google.inject.Singleton Singletons}
13+
* so that they can be injected into the two controllers.
14+
*
15+
* @param <T> The value that the object property holds.
16+
*/
17+
public abstract class DragService<T> {
18+
private final ObjectProperty<T> dragProperty;
19+
20+
/**
21+
* @param name The name for the {@link SimpleObjectProperty}
22+
*/
23+
public DragService(String name) {
24+
this.dragProperty = new SimpleObjectProperty<>(this, name);
25+
}
26+
27+
/**
28+
* @return The read only version of this object property.
29+
*/
30+
public ReadOnlyObjectProperty<T> getDragProperty() {
31+
return dragProperty;
32+
}
33+
34+
/**
35+
* @return The value stored in the object property.
36+
*/
37+
public Optional<T> getValue() {
38+
return Optional.ofNullable(dragProperty.get());
39+
}
40+
41+
/**
42+
* Begins the drag action
43+
*
44+
* @param value The value to be transferred during the drag.
45+
*/
46+
public void beginDrag(T value) {
47+
this.dragProperty.set(value);
48+
}
49+
50+
/**
51+
* This should be called when the drag action is complete.
52+
*/
53+
public void completeDrag() {
54+
dragProperty.setValue(null);
55+
}
56+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package edu.wpi.grip.ui.dragging;
2+
3+
4+
import com.google.inject.Singleton;
5+
import edu.wpi.grip.core.Operation;
6+
7+
/**
8+
* Service for dragging an {@link Operation} from the {@link edu.wpi.grip.ui.pipeline.PipelineController}
9+
* to the {@link edu.wpi.grip.ui.pipeline.PipelineController}.
10+
*/
11+
@Singleton
12+
public class OperationDragService extends DragService<Operation> {
13+
14+
public OperationDragService() {
15+
super("operation");
16+
}
17+
}

0 commit comments

Comments
 (0)