Skip to content

Commit

Permalink
#128, #146 - Trying out a swing table
Browse files Browse the repository at this point in the history
  • Loading branch information
hohonuuli committed Feb 15, 2023
1 parent 95bf1b9 commit 7552c31
Show file tree
Hide file tree
Showing 10 changed files with 398 additions and 4 deletions.
Expand Up @@ -48,13 +48,14 @@ public class AppController {
private final MediaPlayers mediaPlayers;
private final Logger log = LoggerFactory.getLogger(getClass());
private final FileChooser fileChooser = new FileChooser();
private final LocalizationLifecycleController localizationLifecycleController;
// Disabled ZeroMQ for https://github.com/mbari-media-management/vars-annotation/issues/148
// private final LocalizationLifecycleController localizationLifecycleController;

public AppController(UIToolBox toolBox) {
this.toolBox = toolBox;
alerts = new Alerts(toolBox);
mediaPlayers = new MediaPlayers(toolBox);
localizationLifecycleController = new LocalizationLifecycleController(toolBox);
// localizationLifecycleController = new LocalizationLifecycleController(toolBox);
initialize();
}

Expand Down
Expand Up @@ -13,6 +13,7 @@
import javafx.scene.text.Text;
import org.mbari.vars.core.util.Preconditions;
import org.mbari.vars.ui.UIToolBox;
import org.mbari.vars.ui.javafx.timeline.TimelineController;
import org.mbari.vars.ui.messages.*;
import org.mbari.vars.ui.javafx.Icons;
import org.mbari.vars.services.model.User;
Expand Down Expand Up @@ -49,12 +50,14 @@ public class ConceptButtonPanesController {
private Logger log = LoggerFactory.getLogger(getClass());
private BooleanProperty lockProperty = new SimpleBooleanProperty(false);
private final ConceptButtonPanesWithHighlightController overviewController;
private final TimelineController timelineController;

public ConceptButtonPanesController(UIToolBox toolBox) {
Preconditions.checkNotNull(toolBox, "The UIToolbox arg can not be null");
this.toolBox = toolBox;
this.i18n = toolBox.getI18nBundle();
overviewController = new ConceptButtonPanesWithHighlightController(toolBox);
timelineController = new TimelineController(toolBox);
// add listener to Data.user. When changed remove all panes and reload
toolBox.getData()
.userProperty()
Expand Down Expand Up @@ -136,8 +139,25 @@ private VBox getControlPane() {
}
});

Button timelineButton = new JFXButton();
Text timelineIcon = Icons.TIMELINE.standardSize();
String timelineLabel = i18n.getString("cppanel.tabpane.timeline.label");
Tab timelineTab = new Tab(timelineLabel, timelineController.getRoot());
timelineButton.setGraphic(timelineIcon);
timelineButton.setTooltip(new Tooltip(i18n.getString("cppanel.tabpane.timeline.tooltip")));
timelineButton.setOnAction(e -> {
ObservableList<Tab> tabs = getTabPane().getTabs();
if (tabs.contains(timelineTab)) {
tabs.remove(timelineTab);
}
else {
tabs.add(timelineTab);
getTabPane().getSelectionModel().select(timelineTab);
}
});

// COntrol Pane
controlPane = new VBox(addButton, removeButton, lockButton, overviewButton);
controlPane = new VBox(addButton, removeButton, lockButton, overviewButton, timelineButton);
}
return controlPane;
}
Expand Down
Expand Up @@ -66,6 +66,7 @@ private void init() {
log.atDebug().log("Created " + locView.size() + " Localization UI objects");
machineLearningStage.setLocalizations(locView);
machineLearningStage.show();

});
} else {
machineLearningStage.setLocalizations(Collections.emptyList());
Expand Down Expand Up @@ -130,7 +131,7 @@ public void analyze2() throws IOException {
var mlService = new OkHttpMegalodonService(mlRemoteUrlOpt.get());
try {
var mlImageInference = MLAnalysisService.analyzeCurrentElapsedTime(toolBox, mlService);
inference.set(mlImageInference);
Platform.runLater(() -> inference.set(mlImageInference));
}
catch (Exception e) {
var i18n = toolBox.getI18nBundle();
Expand Down
Expand Up @@ -175,6 +175,14 @@ void initialize() {
}
}
});
conceptComboBox.focusedProperty().addListener((obs, oldv, newv) -> {
if (newv) {
conceptComboBox.setStyle("-fx-background-color: #5D3D3D");
}
else {
conceptComboBox.setStyle(null);
}
});
loadComboBoxData();

// If the cache is cleared reload combobox data
Expand Down
@@ -0,0 +1,114 @@
package org.mbari.vars.ui.javafx.timeline;

import javafx.application.Platform;
import javafx.beans.binding.DoubleBinding;
import javafx.scene.control.Label;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;
import org.mbari.vars.core.EventBus;
import org.mbari.vars.services.model.Annotation;
import org.mbari.vars.ui.events.AnnotationsSelectedEvent;
import org.mbari.vars.ui.util.ColorUtil;

import java.util.List;

record DisplayedAnnotation(Annotation annotation, Label label, Line line) {


public void addTo(Pane parent,
Line horizontalAxis,
DoubleBinding distanceBetweenMinutes,
EventBus eventBus) {
if (annotation.getElapsedTime() == null) {
return;
}

Platform.runLater(() -> {
var minutes = annotation.getElapsedTime().toMillis() / 1000.0 / 60.0;
var xProp = distanceBetweenMinutes.multiply(minutes).add(TimelineController.OFFSET);

// --- LABEL
var firstLetter = annotation.getConcept().toUpperCase().charAt(0);
var charCode = (int) firstLetter;
var shortName = firstLetter + "";

label.getStylesheets().clear();
label.setText(shortName);
label.layoutXProperty().bind(xProp.subtract(label.widthProperty().divide(2)));
var incremProp = parent.heightProperty()
.subtract(TimelineController.OFFSET * 2)
.subtract(horizontalAxis.startYProperty())
.divide(26)
.multiply(charCode - 65)
.add(horizontalAxis.startYProperty());
label.layoutYProperty().bind(incremProp);

var fillHex = ColorUtil.stringToHexColor(annotation.getConcept(), 0.7);
var fill = Color.web(fillHex, 0.7);
// label.setTextFill(fill);


label.setStyle("-fx-font-weight: bold; -fx-font-size: 16px; -fx-text-fill: " + fillHex + ";");


label.setOnMouseClicked(evt -> {
var e = new AnnotationsSelectedEvent(DisplayedAnnotation.class, List.of(annotation));
eventBus.send(e);
});

if (annotation.getRecordedTimestamp() != null) {
label.setTooltip(new Tooltip(annotation.getConcept() + " at " + annotation.getRecordedTimestamp()));
}
else {
label.setTooltip(new Tooltip(annotation.getConcept() + " at " + minutes + " minutes"));
}

// -- LINE
line.startXProperty().bind(xProp);
line.endXProperty().bind(xProp);
line.startYProperty().bind(horizontalAxis.startYProperty());
line.endYProperty().bind(label.layoutYProperty());

var lightStroke = new Color(fill.getRed(), fill.getGreen(), fill.getBlue(), 0.15);
var heavyStroke = new Color(fill.getRed(), fill.getGreen(), fill.getBlue(), 1);
var heavyStrokeHex = ColorUtil.toHex(heavyStroke);
line.setStroke(lightStroke);

label.setOnMouseEntered(evt -> Platform.runLater(() -> {
line.setStroke(heavyStroke);
line.setStrokeWidth(3);
label.setStyle("-fx-font-weight: bold; -fx-font-size: 18px; -fx-text-fill: " + heavyStrokeHex + ";");
label.setText(annotation.getConcept());
}));

label.setOnMouseExited(evt -> Platform.runLater(() -> {
line.setStroke(lightStroke);
line.setStrokeWidth(3);
label.setStyle("-fx-font-weight: bold; -fx-font-size: 16px; -fx-text-fill: " + fillHex + ";");
label.setText(shortName);
}));

parent.getChildren().addAll(line, label);
});

}

public void removeFrom(Pane parent) {
Platform.runLater(() -> {
label.layoutXProperty().unbind();
label.layoutYProperty().unbind();
line.startXProperty().unbind();
line.startYProperty().unbind();
line.endXProperty().unbind();
line.endYProperty().unbind();
label.setOnMouseEntered(e -> {});
label.setOnMouseExited(e -> {});
label.setOnMouseClicked(e -> {});
parent.getChildren().removeAll(label, line);
parent.requestLayout();
});

}
}
@@ -0,0 +1,43 @@
package org.mbari.vars.ui.javafx.timeline;

import javafx.application.Platform;
import javafx.beans.binding.DoubleBinding;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Line;

record TickMark(int minute, Line tick, Label label) {

public void addTo(Pane parent, Line horizontalAxis, DoubleBinding distanceBetweenMinutes) {
Platform.runLater(() -> {
var offset = TimelineController.OFFSET;
tick.startXProperty().bind(distanceBetweenMinutes.multiply(minute).add(offset));
tick.endXProperty().bind(tick.startXProperty());

tick.startYProperty().bind(horizontalAxis.startYProperty().subtract(offset));
tick.endYProperty().bind(horizontalAxis.startYProperty().add(offset));
tick.setStyle("-fx-stroke: #B3A9A3; -fx-stroke-width: 2px;");

var label = new Label("" + minute);
label.setTextFill(Color.valueOf("#B3A9A3"));
label.layoutXProperty().bind(tick.startXProperty().subtract(label.widthProperty().divide(2)));
label.layoutYProperty().bind(tick.startYProperty().subtract(offset * 2));
label.setStyle("-fx-font-weight: bold; -fx-font-size: 18px;");
parent.getChildren().addAll(tick, label);
});
}

public void removeFrom(Pane parent) {
Platform.runLater(() -> {
parent.getChildren().removeAll(tick, label);
tick.startXProperty().unbind();
tick.endXProperty().unbind();
tick.startYProperty().unbind();
tick.endYProperty().unbind();
label.layoutXProperty().unbind();
label.layoutYProperty().unbind();
});

}
}
@@ -0,0 +1,18 @@
package org.mbari.vars.ui.javafx.timeline;

import javafx.beans.binding.DoubleBinding;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import org.mbari.vars.services.model.Media;

import java.util.List;

record Timeline(Media media, List<TickMark> tickMarks) {
public void addTo(Pane parent, Line horizontalAxis, DoubleBinding distanceBetweenMinutes) {
tickMarks.forEach(t -> t.addTo(parent, horizontalAxis, distanceBetweenMinutes));
}

public void removeFrom(Pane parent) {
tickMarks.forEach(t -> t.removeFrom(parent));
}
}
@@ -0,0 +1,122 @@
package org.mbari.vars.ui.javafx.timeline;

import javafx.application.Platform;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.Label;
import javafx.scene.layout.Pane;
import javafx.scene.shape.Line;
import org.mbari.vars.services.model.Annotation;
import org.mbari.vars.services.model.Media;
import org.mbari.vars.ui.UIToolBox;
import org.mbari.vars.ui.events.AnnotationsAddedEvent;
import org.mbari.vars.ui.events.AnnotationsChangedEvent;
import org.mbari.vars.ui.events.AnnotationsRemovedEvent;
import org.mbari.vars.ui.events.MediaChangedEvent;

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

public class TimelineController {

public static final int OFFSET = 10;


private final UIToolBox toolBox;
private Pane root;
DoubleProperty endXProperty = new SimpleDoubleProperty();
DoubleProperty numberOfMinutesProperty = new SimpleDoubleProperty();
DoubleBinding distanceBetweenMinutesProperty;
Line horizontalAxis;
List<DisplayedAnnotation> displayedAnno = new ArrayList<>();
private Timeline timeline;

public TimelineController(UIToolBox toolBox) {
this.toolBox = toolBox;
init();
var eventBus = toolBox.getEventBus().toObserverable();

eventBus.ofType(MediaChangedEvent.class)
.subscribe(e -> setMedia(e.get()));

eventBus.ofType(AnnotationsAddedEvent.class)
.subscribe(e -> e.get().forEach(this::addAnnotation));

eventBus.ofType(AnnotationsRemovedEvent.class)
.subscribe(e -> e.get().forEach(this::removeAnnotation));

eventBus.ofType(AnnotationsChangedEvent.class)
.subscribe(e -> e.get().forEach(a -> {
removeAnnotation(a);
addAnnotation(a);
}));

}

private void init() {
root = new Pane();
endXProperty.bind(root.widthProperty().subtract(OFFSET));
horizontalAxis = new Line(OFFSET, OFFSET * 3, endXProperty.get(), OFFSET * 3);
horizontalAxis.endXProperty().bind(endXProperty);
horizontalAxis.setStyle("-fx-stroke: #B3A9A3; -fx-stroke-width: 2px;");
root.getChildren().add(horizontalAxis);
distanceBetweenMinutesProperty = horizontalAxis.endXProperty()
.subtract(horizontalAxis.startXProperty())
.divide(numberOfMinutesProperty);

}

public Pane getRoot() {
return root;
}

private void addAnnotation(Annotation a) {
Platform.runLater(() -> {
var da = new DisplayedAnnotation(a, new Label(), new Line());
displayedAnno.add(da);
da.addTo(root, horizontalAxis, distanceBetweenMinutesProperty, toolBox.getEventBus());
});
}

private void removeAnnotation(Annotation a) {
Platform.runLater(() -> {
for (int i = 0; i < displayedAnno.size(); i++) {
var d = displayedAnno.get(i);
if (d.annotation().getObservationUuid().equals(a.getObservationUuid())) {
displayedAnno.remove(i);
d.removeFrom(root);
break;
}
}
});
}



private void setMedia(Media media) {
displayedAnno.forEach(d -> d.removeFrom(root));
displayedAnno.clear();
if (media == null || media.getDuration() == null) {
if (timeline != null) {
timeline.removeFrom(root);
}
}
else {
var numberOfMinutes = Math.ceil(media.getDuration().toMillis() / 1000D / 60D);
numberOfMinutesProperty.set(numberOfMinutes);

var tickMarks = new ArrayList<TickMark>();
for (int i = 0; i <= numberOfMinutes; i++) {
tickMarks.add(new TickMark(i, new Line(), new Label()));
}
timeline = new Timeline(media, tickMarks);
timeline.addTo(root, horizontalAxis, distanceBetweenMinutesProperty);
}
}


}

0 comments on commit 7552c31

Please sign in to comment.