Skip to content

Commit

Permalink
Add bounding shape image saving feature, fix shortcut key handling (#100
Browse files Browse the repository at this point in the history
)

* Add bounding shape image saving feature.

* Fix image saving initial filename extraction.

* Trigger shortcuts on key-release.

* Prevent shortcut firing when using textfields, improve image-saving unit test filechooser mocking.

* Extend unit tests.
  • Loading branch information
mfl28 committed Nov 4, 2023
1 parent ce3a8f9 commit 10520bc
Show file tree
Hide file tree
Showing 13 changed files with 451 additions and 144 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ dependencies {
// Commons Lang https://mvnrepository.com/artifact/org.apache.commons/commons-lang3
implementation 'org.apache.commons:commons-lang3:3.13.0'

// https://mvnrepository.com/artifact/commons-io/commons-io
implementation 'commons-io:commons-io:2.15.0'

// ControlsFX https://mvnrepository.com/artifact/org.controlsfx/controlsfx
implementation('org.controlsfx:controlsfx:11.1.2') {
exclude group: 'org.openjfx'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
package com.github.mfl28.boundingboxeditor.controller;

import com.github.mfl28.boundingboxeditor.controller.utils.KeyCombinationEventHandler;
import com.github.mfl28.boundingboxeditor.controller.utils.SingleFireKeyCombinationEventHandler;
import com.github.mfl28.boundingboxeditor.model.Model;
import com.github.mfl28.boundingboxeditor.model.data.ImageAnnotation;
import com.github.mfl28.boundingboxeditor.model.data.ImageMetaData;
Expand Down Expand Up @@ -53,9 +52,7 @@
import javafx.concurrent.WorkerStateEvent;
import javafx.event.ActionEvent;
import javafx.scene.Cursor;
import javafx.scene.control.ButtonBar;
import javafx.scene.control.ButtonType;
import javafx.scene.control.TableColumn;
import javafx.scene.control.*;
import javafx.scene.image.Image;
import javafx.scene.input.*;
import javafx.scene.paint.Color;
Expand Down Expand Up @@ -498,16 +495,18 @@ public void onRegisterExitAction() {
* @param event the short-cut key-event
*/
public void onRegisterSceneKeyPressed(KeyEvent event) {
// While the user is drawing a shape, all key-events will be ignored.
if(view.getEditorImagePane().isDrawingInProgress()) {
event.consume();
return;
}

if(event.isShortcutDown()) {
view.getEditorImagePane().setZoomableAndPannable(true);
}

if(event.getTarget() instanceof TextInputControl) {
return;
}

keyCombinationHandlers.stream()
.filter(keyCombinationHandler -> keyCombinationHandler.handlesPressed(event))
.findFirst()
Expand All @@ -520,10 +519,18 @@ public void onRegisterSceneKeyPressed(KeyEvent event) {
* @param event the short-cut key-event
*/
public void onRegisterSceneKeyReleased(KeyEvent event) {
if(view.getEditorImagePane().isDrawingInProgress()) {
return;
}

if(!event.isShortcutDown()) {
view.getEditorImagePane().setZoomableAndPannable(false);
}

if(event.getTarget() instanceof TextInputControl) {
return;
}

keyCombinationHandlers.stream()
.filter(keyCombinationHandler -> keyCombinationHandler.handlesReleased(event))
.findFirst()
Expand Down Expand Up @@ -744,46 +751,50 @@ private List<KeyCombinationEventHandler> createKeyCombinationHandlers() {
navigatePreviousKeyPressed.set(false);
navigateNextKeyPressed.set(false);
},
event -> KeyCombinations.navigationReleaseKeyCodes.contains(event.getCode()) || !event.isShortcutDown()),
event -> (navigateNextKeyPressed.get() || navigatePreviousKeyPressed.get()) &&
(KeyCombinations.navigationReleaseKeyCodes.contains(event.getCode()) || !event.isShortcutDown())
),
new KeyCombinationEventHandler(KeyCombinations.navigateNext,
event -> handleNavigateNextKeyPressed(), null),
new KeyCombinationEventHandler(KeyCombinations.navigatePrevious,
event -> handleNavigatePreviousKeyPressed(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.deleteSelectedBoundingShape,
event -> view.removeSelectedTreeItemAndChildren(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.removeEditingVerticesWhenBoundingPolygonSelected,
event -> view.removeEditingVerticesWhenPolygonViewSelected(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.focusCategorySearchField,
event -> view.getCategorySearchField().requestFocus(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.focusFileSearchField,
event -> view.getImageFileSearchField().requestFocus(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.focusCategoryNameTextField,
event -> view.getObjectCategoryInputField().requestFocus(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.focusTagTextField,
event -> view.getTagInputField().requestFocus(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.hideSelectedBoundingShape,
event -> view.getObjectTree().setToggleIconStateForSelectedObjectTreeItem(false), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.hideAllBoundingShapes,
event -> view.getObjectTree().setToggleIconStateForAllTreeItems(false), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.hideNonSelectedBoundingShapes,
event -> view.getObjectTree().setToggleIconStateForNonSelectedObjectTreeItems(false), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.showSelectedBoundingShape,
event -> view.getObjectTree().setToggleIconStateForSelectedObjectTreeItem(true), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.showAllBoundingShapes,
event -> view.getObjectTree().setToggleIconStateForAllTreeItems(true), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.resetSizeAndCenterImage,
event -> view.getEditorImagePane().resetImageViewSize(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.selectRectangleDrawingMode,
event -> view.getEditor().getEditorToolBar().getRectangleModeButton().setSelected(true), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.selectPolygonDrawingMode,
event -> view.getEditor().getEditorToolBar().getPolygonModeButton().setSelected(true), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.selectFreehandDrawingMode,
event -> view.getEditor().getEditorToolBar().getFreehandModeButton().setSelected(true), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.changeSelectedBoundingShapeCategory,
event -> view.initiateCurrentSelectedBoundingBoxCategoryChange(), null),
new SingleFireKeyCombinationEventHandler(KeyCombinations.simplifyPolygon,
event -> view.simplifyCurrentSelectedBoundingPolygon(), null)
);
new KeyCombinationEventHandler(KeyCombinations.deleteSelectedBoundingShape,
null, event -> view.removeSelectedTreeItemAndChildren()),
new KeyCombinationEventHandler(KeyCombinations.removeEditingVerticesWhenBoundingPolygonSelected,
null, event -> view.removeEditingVerticesWhenPolygonViewSelected()),
new KeyCombinationEventHandler(KeyCombinations.focusCategorySearchField,
null, event -> view.getCategorySearchField().requestFocus()),
new KeyCombinationEventHandler(KeyCombinations.focusFileSearchField,
null, event -> view.getImageFileSearchField().requestFocus()),
new KeyCombinationEventHandler(KeyCombinations.focusCategoryNameTextField,
null, event -> view.getObjectCategoryInputField().requestFocus()),
new KeyCombinationEventHandler(KeyCombinations.focusTagTextField,
null, event -> view.getTagInputField().requestFocus()),
new KeyCombinationEventHandler(KeyCombinations.hideSelectedBoundingShape,
null, event -> view.getObjectTree().setToggleIconStateForSelectedObjectTreeItem(false)),
new KeyCombinationEventHandler(KeyCombinations.hideAllBoundingShapes,
null, event -> view.getObjectTree().setToggleIconStateForAllTreeItems(false)),
new KeyCombinationEventHandler(KeyCombinations.hideNonSelectedBoundingShapes,
null, event -> view.getObjectTree().setToggleIconStateForNonSelectedObjectTreeItems(false)),
new KeyCombinationEventHandler(KeyCombinations.showSelectedBoundingShape,
null, event -> view.getObjectTree().setToggleIconStateForSelectedObjectTreeItem(true)),
new KeyCombinationEventHandler(KeyCombinations.showAllBoundingShapes,
null, event -> view.getObjectTree().setToggleIconStateForAllTreeItems(true)),
new KeyCombinationEventHandler(KeyCombinations.resetSizeAndCenterImage,
null, event -> view.getEditorImagePane().resetImageViewSize()),
new KeyCombinationEventHandler(KeyCombinations.selectRectangleDrawingMode,
null, event -> view.getEditor().getEditorToolBar().getRectangleModeButton().setSelected(true)),
new KeyCombinationEventHandler(KeyCombinations.selectPolygonDrawingMode,
null, event -> view.getEditor().getEditorToolBar().getPolygonModeButton().setSelected(true)),
new KeyCombinationEventHandler(KeyCombinations.selectFreehandDrawingMode,
null, event -> view.getEditor().getEditorToolBar().getFreehandModeButton().setSelected(true)),
new KeyCombinationEventHandler(KeyCombinations.changeSelectedBoundingShapeCategory,
null, event -> view.initiateCurrentSelectedBoundingBoxCategoryChange()),
new KeyCombinationEventHandler(KeyCombinations.simplifyPolygon,
null, event -> view.simplifyCurrentSelectedBoundingPolygon()),
new KeyCombinationEventHandler(KeyCombinations.saveBoundingShapeAsImage,
null, event -> view.saveCurrentSelectedBoundingShapeAsImage())
);
}

private void onBoundingBoxPredictionSucceeded(WorkerStateEvent event) {
Expand Down Expand Up @@ -1635,6 +1646,8 @@ public static class KeyCombinations {

public static final KeyCombination simplifyPolygon =
new KeyCodeCombination(KeyCode.S, KeyCombination.SHIFT_DOWN);
public static final KeyCombination saveBoundingShapeAsImage =
new KeyCodeCombination(KeyCode.I, KeyCombination.SHIFT_DOWN);

private KeyCombinations() {
throw new IllegalStateException("Key Combination Class");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.github.mfl28.boundingboxeditor.utils.UiUtils;
import javafx.geometry.Orientation;
import javafx.scene.control.*;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
Expand Down Expand Up @@ -287,6 +288,26 @@ private void setUpInternalListeners() {

categorySearchField.setOnAction(event -> requestFocus());

categorySearchField.setOnKeyReleased(event -> {
if(event.getCode() == KeyCode.ESCAPE) {
requestFocus();
event.consume();
}
});

categoryNameTextField.focusedProperty().addListener((observable, oldValue, newValue) -> {
if(!Boolean.TRUE.equals(newValue)) {
categoryNameTextField.setText(null);
}
});

categoryNameTextField.setOnKeyReleased(event -> {
if(event.getCode() == KeyCode.ESCAPE) {
requestFocus();
event.consume();
}
});

expandTreeItemsButton.setOnAction(event -> objectTree.expandAllTreeItems());

collapseTreeItemsButton.setOnAction(event ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.Region;
Expand Down Expand Up @@ -145,5 +146,12 @@ private void setUpInternalListeners() {
});

imageFileSearchField.setOnAction(event -> requestFocus());

imageFileSearchField.setOnKeyReleased(event -> {
if(event.getCode() == KeyCode.ESCAPE) {
requestFocus();
event.consume();
}
});
}
}
17 changes: 13 additions & 4 deletions src/main/java/com/github/mfl28/boundingboxeditor/ui/MainView.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,13 +159,13 @@ public static File displayDirectoryChooserAndGetChoice(String title, Stage stage
* Displays a file chooser window and returns the chosen directory.
*
* @param title The title of the file chooser window
* @param stage The stage on top of which the window will be shown
* @param window The window on top of which the window will be shown
* @param initialDirectory The initial directory
* @param initialFileName The initial default filename
* @param extensionFilter The extension filter to apply
* @return The chosen file, or null if the user closed the window without choosing.
*/
public static File displayFileChooserAndGetChoice(String title, Stage stage, File initialDirectory,
public static File displayFileChooserAndGetChoice(String title, Window window, File initialDirectory,
String initialFileName,
FileChooser.ExtensionFilter extensionFilter,
FileChooserType type) {
Expand All @@ -188,9 +188,9 @@ public static File displayFileChooserAndGetChoice(String title, Stage stage, Fil
File result;

if(type.equals(FileChooserType.SAVE)) {
result = fileChooser.showSaveDialog(stage);
result = fileChooser.showSaveDialog(window);
} else {
result = fileChooser.showOpenDialog(stage);
result = fileChooser.showOpenDialog(window);
}

return result;
Expand Down Expand Up @@ -432,6 +432,15 @@ public void simplifyCurrentSelectedBoundingPolygon() {
}
}

public void saveCurrentSelectedBoundingShapeAsImage() {
final TreeItem<Object> selectedTreeItem = getObjectTree().getSelectionModel().getSelectedItem();

if(selectedTreeItem instanceof BoundingShapeTreeItem boundingShapeTreeItem &&
boundingShapeTreeItem.getValue() instanceof BoundingShapeViewable boundingShapeViewable) {
workspaceSplitPane.initiateSaveAsImage(boundingShapeViewable, boundingShapeTreeItem.getId());
}
}

public ImageFileExplorerView getImageFileExplorer() {
return workspaceSplitPane.getImageFileExplorer();
}
Expand Down
Loading

0 comments on commit 10520bc

Please sign in to comment.