Skip to content
New issue

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

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

Already on GitHub? # to your account

Change source selector to a dropdown. #631

Merged
merged 6 commits into from
Jul 28, 2016
Merged
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,23 @@
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Predicate;

import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Parent;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Control;
import javafx.scene.control.Dialog;
import javafx.scene.control.MenuButton;
import javafx.scene.control.MenuItem;
import javafx.scene.control.Spinner;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.text.Text;
import javafx.scene.text.TextAlignment;
import javafx.stage.FileChooser;
import javafx.stage.FileChooser.ExtensionFilter;

Expand All @@ -52,29 +50,28 @@
* construct that source. As an example, the image file source results in a file picker that the
* user can use to browse for an image.
*/
public class AddSourceView extends HBox {
public class AddSourceButton extends MenuButton {

@VisibleForTesting
static final String SOURCE_DIALOG_STYLE_CLASS = "source-dialog";
private final EventBus eventBus;

private final Button webcamButton;
private final Button ipcamButton;
private final MenuItem webcamButton;
private final MenuItem ipcamButton;
private final MenuItem httpButton;
private Optional<Dialog> activeDialog = Optional.empty();

@Inject
AddSourceView(EventBus eventBus,
AddSourceButton(EventBus eventBus,
MultiImageFileSource.Factory multiImageSourceFactory,
ImageFileSource.Factory imageSourceFactory,
CameraSource.Factory cameraSourceFactory,
HttpSource.Factory httpSourceFactory) {
super("Add Source");
this.eventBus = eventBus;

this.setFillHeight(true);

addButton("Add\nImage(s)",
getClass().getResource("/edu/wpi/grip/ui/icons/add-image.png"),
mouseEvent -> {

addMenuItem("Image(s)",
getClass().getResource("/edu/wpi/grip/ui/icons/add-image.png"), mouseEvent -> {
// Show a file picker so the user can open one or more images from disk
final FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Open an image");
Expand Down Expand Up @@ -120,14 +117,12 @@ public class AddSourceView extends HBox {
}
});

webcamButton = addButton(
"Add\nWebcam",
getClass().getResource("/edu/wpi/grip/ui/icons/add-webcam.png"),
mouseEvent -> {
webcamButton = addMenuItem("Webcam",
getClass().getResource("/edu/wpi/grip/ui/icons/add-webcam.png"), mouseEvent -> {
final Parent root = this.getScene().getRoot();

// Show a dialog for the user to pick a camera index
final Spinner<Integer> cameraIndex = new Spinner<Integer>(0, Integer.MAX_VALUE, 0);
final Spinner<Integer> cameraIndex = new Spinner<>(0, Integer.MAX_VALUE, 0);
final SourceDialog dialog = new SourceDialog(root, cameraIndex);

dialog.setTitle("Add Webcam");
Expand All @@ -144,15 +139,11 @@ public class AddSourceView extends HBox {
cameraSource.initialize();
return cameraSource;
},
e -> {
dialog.errorText.setText(e.getMessage());
});
e -> dialog.errorText.setText(e.getMessage()));
});

ipcamButton = addButton(
"Add IP\nCamera",
getClass().getResource("/edu/wpi/grip/ui/icons/add-webcam.png"),
mouseEvent -> {
ipcamButton = addMenuItem("IP Camera",
getClass().getResource("/edu/wpi/grip/ui/icons/add-webcam.png"), mouseEvent -> {
final Parent root = this.getScene().getRoot();

// Show a dialog for the user to pick a camera URL
Expand Down Expand Up @@ -197,8 +188,8 @@ public class AddSourceView extends HBox {
e -> dialog.errorText.setText(e.getMessage()));
});

addButton("Add\nHTTP source", getClass().getResource("/edu/wpi/grip/ui/icons/publish.png"),
mouseEvent -> {
httpButton = addMenuItem("HTTP",
getClass().getResource("/edu/wpi/grip/ui/icons/publish.png"), mouseEvent -> {
final Parent root = this.getScene().getRoot();
// Show a dialog to pick the server path images will be uploaded on
final String imageRoot = GripServer.IMAGE_UPLOAD_PATH + "/";
Expand Down Expand Up @@ -252,31 +243,34 @@ private void loadCamera(Dialog<ButtonType> dialog, SupplierWithIO<CameraSource>
/**
* Add a new button for adding a source. This method takes care of setting the event handler.
*/
private Button addButton(String text, URL graphicURL, EventHandler<? super MouseEvent>
onMouseClicked) {
private MenuItem addMenuItem(String text, URL graphicURL, EventHandler<ActionEvent>
onActionEvent) {
final ImageView graphic = new ImageView(graphicURL.toString());
graphic.setFitWidth(DPIUtility.SMALL_ICON_SIZE);
graphic.setFitHeight(DPIUtility.SMALL_ICON_SIZE);

final Button button = new Button(text, graphic);
button.setTextAlignment(TextAlignment.CENTER);
button.setContentDisplay(ContentDisplay.TOP);
button.setOnMouseClicked(onMouseClicked);
final MenuItem menuItem = new MenuItem(" " + text, graphic);
menuItem.setOnAction(onActionEvent);

this.getChildren().add(button);
return button;
getItems().add(menuItem);
return menuItem;
}

@VisibleForTesting
Button getWebcamButton() {
MenuItem getWebcamButton() {
return webcamButton;
}

@VisibleForTesting
Button getIpcamButton() {
MenuItem getIpcamButton() {
return ipcamButton;
}

@VisibleForTesting
MenuItem getHttpButton() {
return httpButton;
}

@VisibleForTesting
void closeDialogs() {
activeDialog.ifPresent(dialog -> {
Expand All @@ -291,7 +285,7 @@ void closeDialogs() {
}

public interface Factory {
AddSourceView create();
AddSourceButton create();
}

private static class SourceDialog extends Dialog<ButtonType> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.stream.Collectors;

import javafx.beans.InvalidationListener;
import javafx.beans.property.ReadOnlyObjectProperty;
import javafx.collections.ObservableList;
Expand All @@ -43,9 +42,7 @@
import javafx.scene.Parent;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.VBox;

import javax.annotation.Nullable;
import javax.inject.Inject;

Expand All @@ -61,7 +58,7 @@ public final class PipelineController {
@FXML
private VBox sourcesBox;
@FXML
private Pane addSourcePane;
private VBox addSourceBox;
@FXML
private HBox stepBox;
@FXML
Expand All @@ -78,7 +75,7 @@ public final class PipelineController {
@Inject
private StepController.Factory stepControllerFactory;
@Inject
private AddSourceView addSourceView;
private AddSourceButton addSourceButton;
@Inject
private OperationDragService operationDragService;
@Inject
Expand Down Expand Up @@ -135,7 +132,7 @@ public void initialize() throws Exception {
});
});

addSourcePane.getChildren().add(addSourceView);
addSourceBox.getChildren().add(addSourceButton);
}

/**
Expand Down
9 changes: 9 additions & 0 deletions ui/src/main/resources/edu/wpi/grip/ui/GRIP.css
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ Label.operation-description {
-fx-padding: 0.5em;
}

.addSource {
-fx-padding: 0.5em;
-fx-spacing: 1em;
}

.addSource * {
-fx-max-width: Infinity;
}

.sources {
-fx-padding: 0.5em;
-fx-spacing: 1em;
Expand Down
3 changes: 1 addition & 2 deletions ui/src/main/resources/edu/wpi/grip/ui/pipeline/Pipeline.fxml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
<?import javafx.scene.control.Separator?>
<?import javafx.scene.Group?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.Pane?>
<?import javafx.scene.layout.StackPane?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.shape.Rectangle?>
Expand All @@ -21,7 +20,7 @@
</maxWidth>
<Label maxWidth="Infinity" styleClass="pane-title" text="Sources" />
<Separator orientation="HORIZONTAL" />
<Pane fx:id="addSourcePane" />
<VBox fx:id="addSourceBox" styleClass="addSource" />
<VBox fx:id="sourcesBox" styleClass="sources" />
</VBox>
<Separator orientation="VERTICAL" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import java.io.IOException;
import java.util.Optional;
import java.util.Properties;

import javafx.application.Platform;
import javafx.scene.Scene;
import javafx.stage.Stage;

Expand All @@ -26,23 +26,23 @@
import static org.testfx.api.FxAssert.verifyThat;

@RunWith(Enclosed.class)
public class AddSourceViewTest {
public class AddSourceButtonTest {

/**
* Tests what happens when a source is created and started successfully
*/
public static class AddSourceViewNoExceptionsTest extends ApplicationTest {

private EventBus eventBus;
private AddSourceView addSourceView;
private AddSourceButton addSourceView;
private MockCameraSourceFactory mockCameraSourceFactory;

@Override
public void start(Stage stage) {
this.eventBus = new EventBus("Test Event Bus");
this.mockCameraSourceFactory = new MockCameraSourceFactory(eventBus);

addSourceView = new AddSourceView(eventBus, null, null, mockCameraSourceFactory, null);
addSourceView = new AddSourceButton(eventBus, null, null, mockCameraSourceFactory, null);

final Scene scene = new Scene(addSourceView, 800, 600);
stage.setScene(scene);
Expand All @@ -51,32 +51,32 @@ public void start(Stage stage) {

@After
public void after() {
// Ensuer that all of the dialogs that were created get closed afterward.
// Ensure that all of the dialogs that were created get closed afterward.
addSourceView.closeDialogs();
}

@Test
@SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert")
public void testClickOnCreateWebCameraOpensDialog() throws Exception {
clickOn(addSourceView.getWebcamButton());
Platform.runLater(() -> addSourceView.getWebcamButton().fire());
WaitForAsyncUtils.waitForFxEvents();
verifyThat("." + AddSourceView.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isVisible());
verifyThat('.' + AddSourceButton.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isVisible());
}

@Test
@SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert")
public void testClickOnCreateIPCameraOpensDialog() throws Exception {
clickOn(addSourceView.getIpcamButton());
Platform.runLater(() -> addSourceView.getIpcamButton().fire());
WaitForAsyncUtils.waitForFxEvents();
verifyThat("." + AddSourceView.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isVisible());
verifyThat("." + AddSourceButton.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isVisible());
}

@Test
public void testCreatesSourceStarted() throws Exception {
// When
clickOn(addSourceView.getWebcamButton());
Platform.runLater(() -> addSourceView.getWebcamButton().fire());
WaitForAsyncUtils.waitForFxEvents();
verifyThat("." + AddSourceView.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isVisible());
verifyThat("." + AddSourceButton.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isVisible());

clickOn("OK");
WaitForAsyncUtils.waitForFxEvents();
Expand All @@ -85,7 +85,7 @@ public void testCreatesSourceStarted() throws Exception {
Optional<CameraSource> cameraSource = mockCameraSourceFactory.lastSourceCreated;
assertTrue("A source was not constructed", cameraSource.isPresent());
assertTrue("A source was not created started", cameraSource.get().isRunning());
verifyThat("." + AddSourceView.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isNull());
verifyThat("." + AddSourceButton.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isNull());
}

class MockCameraSourceFactory implements CameraSource.Factory {
Expand Down Expand Up @@ -123,15 +123,15 @@ private MockCameraSource assignLastCreated(MockCameraSource source) {
*/
public static class AddSourceViewWithExceptionsTest extends ApplicationTest {
private EventBus eventBus;
private AddSourceView addSourceView;
private AddSourceButton addSourceView;
private MockCameraSourceFactory mockCameraSourceFactory;

@Override
public void start(Stage stage) {
this.eventBus = new EventBus("Test Event Bus");
this.mockCameraSourceFactory = new MockCameraSourceFactory(eventBus);

addSourceView = new AddSourceView(eventBus, null, null, mockCameraSourceFactory, null);
addSourceView = new AddSourceButton(eventBus, null, null, mockCameraSourceFactory, null);

final Scene scene = new Scene(addSourceView, 800, 600);
stage.setScene(scene);
Expand All @@ -140,31 +140,31 @@ public void start(Stage stage) {

@After
public void after() {
// Ensuer that all of the dialogs that were created get closed afterward.
// Ensure that all of the dialogs that were created get closed afterward.
addSourceView.closeDialogs();
}

@Test
@SuppressWarnings("PMD.JUnitTestsShouldIncludeAssert")
public void testWhenStartFailsDialogStillCloses() throws Exception {
// When
clickOn(addSourceView.getWebcamButton());
Platform.runLater(() -> addSourceView.getWebcamButton().fire());
WaitForAsyncUtils.waitForFxEvents();

clickOn("OK");

WaitForAsyncUtils.waitForFxEvents();

// The dialog should not have closed because the source wasn't started
verifyThat("." + AddSourceView.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isNull());
verifyThat("." + AddSourceButton.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isNull());
}

@Test
public void testCreatesSourceStartedFails() throws Exception {
// When
clickOn(addSourceView.getWebcamButton());
Platform.runLater(() -> addSourceView.getWebcamButton().fire());
WaitForAsyncUtils.waitForFxEvents();
verifyThat("." + AddSourceView.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isVisible());
verifyThat("." + AddSourceButton.SOURCE_DIALOG_STYLE_CLASS, NodeMatchers.isVisible());

clickOn("OK");
WaitForAsyncUtils.waitForFxEvents();
Expand Down