From 2a9ca3246ad2dbc960f44442389facba3d83cddc Mon Sep 17 00:00:00 2001
From: Adrian Stoica <george.a.stoica@ntnu.no>
Date: Fri, 11 Oct 2019 21:14:24 +0200
Subject: [PATCH] added tooltip to marker; this should fix issue #10

---
 .../simpleex/ui/AbstractFxAppController.java  | 453 ++++++++++--------
 1 file changed, 264 insertions(+), 189 deletions(-)

diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java b/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java
index 68b115f..b8f6e97 100644
--- a/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java
+++ b/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java
@@ -1,189 +1,264 @@
-package simpleex.ui;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import fxmapcontrol.Location;
-import fxmapcontrol.MapBase;
-import fxmapcontrol.MapItemsControl;
-import fxmapcontrol.MapNode;
-import fxmapcontrol.MapProjection;
-import javafx.collections.FXCollections;
-import javafx.fxml.FXML;
-import javafx.geometry.Point2D;
-import javafx.scene.Node;
-import javafx.scene.control.Alert;
-import javafx.scene.control.Alert.AlertType;
-import javafx.util.Callback;
-import javafx.scene.control.ButtonType;
-import javafx.scene.control.ListCell;
-import javafx.scene.control.ListView;
-import javafx.scene.control.Slider;
-import simpleex.core.LatLong;
-import simpleex.json.LatLongsModule;
-
-/*
-@startuml
-class AbstractFxAppController
-class LatLongs
-class BorderPane
-class "ListView<LatLong>" as ListView
-class "fxmapcontrol.MapBase" as MapBase
-
-BorderPane *--> ListView: "left"
-BorderPane *--> MapBase: "center"
-
-AbstractFxAppController --> MapBase: "mapView"
-AbstractFxAppController --> ListView: "locationListView"
-@enduml
- */
-
-/**
- * The controller for the app.
- * @author hal
- *
- */
-public abstract class AbstractFxAppController {
-
-  private LatLongsDataAccess dataAccess;
-
-  protected LatLongsDataAccess getDataAccess() {
-    return dataAccess;
-  }
-
-  protected void setDataAccess(final LatLongsDataAccess dataAccess) {
-    this.dataAccess = dataAccess;
-    if (locationListView != null) {
-      updateLocationViewList(0);
-    }
-  }
-
-  @FXML
-  private ListView<LatLong> locationListView;
-
-  @FXML
-  private MapBase mapView;
-
-  private MapItemsControl<MapNode> markersParent;
-  private MapMarker marker = null;
-  private DraggableNodeController draggableMapController = null;
-  private DraggableNodeController draggableMarkerController = null;
-
-  @FXML
-  private Slider zoomSlider;
-
-  @FXML
-  private void initialize() {
-    // map stuff
-    // mapView.getChildren().add(MapTileLayer.getOpenStreetMapLayer());
-    zoomSlider.valueProperty()
-      .addListener((prop, oldValue, newValue) -> mapView.setZoomLevel(zoomSlider.getValue()));
-    zoomSlider.setValue(8);
-    markersParent = new MapItemsControl<MapNode>();
-    mapView.getChildren().add(markersParent);
-    draggableMapController = new DraggableNodeController(this::handleMapDragged);
-    draggableMapController.setImmediate(true);
-    draggableMapController.attach(mapView);
-    draggableMarkerController = new DraggableNodeController(this::handleMarkerDragged);
-    // the location list
-    locationListView.getSelectionModel().selectedIndexProperty()
-      .addListener((prop, oldValue, newValue) -> updateMapMarker(true));
-    //connect the cell renderer to the list
-    locationListView.setCellFactory(listView -> new LatLongCell());
-  }
-
-  private void handleMapDragged(final Node node, final double dx, final double dy) {
-    final MapProjection projection = mapView.getProjection();
-    final Point2D point = projection.locationToViewportPoint(mapView.getCenter());
-    final Location newCenter = projection.viewportPointToLocation(point.add(-dx, -dy));
-    mapView.setCenter(newCenter);
-  }
-
-  private void handleMarkerDragged(final Node node, final double dx, final double dy) {
-    final MapProjection projection = mapView.getProjection();
-    final Point2D point = projection.locationToViewportPoint(marker.getLocation());
-    final Location newLocation = projection.viewportPointToLocation(point.add(dx, dy));
-    dataAccess.setLatLong(locationListView.getSelectionModel().getSelectedIndex(),
-        location2LatLong(newLocation));
-    updateLocationViewListSelection(false);
-  }
-
-  private LatLong location2LatLong(final Location newLocation) {
-    return new LatLong(newLocation.getLatitude(), newLocation.getLongitude());
-  }
-
-  private void updateMapMarker(final boolean centerOnMarker) {
-    final int num = locationListView.getSelectionModel().getSelectedIndex();
-    if (num < 0) {
-      markersParent.getItems().clear();
-      if (draggableMarkerController != null) {
-        draggableMarkerController.detach(marker);
-      }
-      marker = null;
-    } else {
-      final LatLong latLong = dataAccess.getLatLong(num);
-      if (marker == null) {
-        marker = new MapMarker(latLong);
-        markersParent.getItems().add(marker);
-        if (draggableMarkerController != null) {
-          draggableMarkerController.attach(marker);
-        }
-      } else {
-        marker.setLocation(latLong);
-      }
-      if (centerOnMarker) {
-        mapView.setCenter(marker.getLocation());
-      }
-    }
-  }
-
-  @FXML
-  void handleAddLocation() {
-    final Location center = mapView.getCenter();
-    final LatLong latLong = location2LatLong(center);
-    final int pos = dataAccess.addLatLong(latLong);
-    updateLocationViewList(pos);
-  }
-
-  private void updateLocationViewListSelection(final Boolean updateMapMarker) {
-    final int selectedIndex = locationListView.getSelectionModel().getSelectedIndex();
-    locationListView.getItems().set(selectedIndex, dataAccess.getLatLong(selectedIndex));
-    if (updateMapMarker != null) {
-      updateMapMarker(updateMapMarker);
-    }
-  }
-
-  protected void updateLocationViewList(int selectedIndex) {
-    final LatLong[] latLongs = dataAccess.getAllLatLongs().toArray(new LatLong[0]);
-    final int oldSelectionIndex = locationListView.getSelectionModel().getSelectedIndex();
-    locationListView.setItems(FXCollections.observableArrayList(latLongs));
-    if (selectedIndex < 0 || selectedIndex >= latLongs.length) {
-      selectedIndex = oldSelectionIndex;
-    }
-    if (selectedIndex >= 0 && selectedIndex < latLongs.length) {
-      locationListView.getSelectionModel().select(selectedIndex);
-    }
-    
-  }
-
-  private ObjectMapper objectMapper;
-
-  /**
-   * Gets the ObjectMapper used by this controller.
-   * @return the ObjectMapper used by this controller
-   */
-  public ObjectMapper getObjectMapper() {
-    if (objectMapper == null) {
-      objectMapper = new ObjectMapper();
-      objectMapper.registerModule(new LatLongsModule());
-    }
-    return objectMapper;
-  }
-
-  protected void showExceptionDialog(final String message) {
-    final Alert alert = new Alert(AlertType.ERROR, message, ButtonType.CLOSE);
-    alert.showAndWait();
-  }
-
-  protected void showExceptionDialog(final String message, final Exception e) {
-    showExceptionDialog(message + ": " + e.getLocalizedMessage());
-  }
-}
+package simpleex.ui;
+
+import java.util.Iterator;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import fxmapcontrol.Location;
+import fxmapcontrol.MapBase;
+import fxmapcontrol.MapItemsControl;
+import fxmapcontrol.MapNode;
+import fxmapcontrol.MapProjection;
+import javafx.collections.FXCollections;
+import javafx.fxml.FXML;
+import javafx.geometry.Insets;
+import javafx.geometry.Point2D;
+import javafx.scene.Node;
+import javafx.scene.control.Alert;
+import javafx.scene.control.Alert.AlertType;
+import javafx.scene.layout.HBox;
+import javafx.util.Callback;
+import javafx.scene.control.ButtonType;
+import javafx.scene.control.Label;
+import javafx.scene.control.ListCell;
+import javafx.scene.control.ListView;
+import javafx.scene.control.OverrunStyle;
+import javafx.scene.control.Slider;
+import javafx.scene.control.Tooltip;
+import simpleex.core.LatLong;
+import simpleex.core.MetaData;
+import simpleex.json.LatLongsModule;
+
+/*
+@startuml
+class AbstractFxAppController
+class LatLongs
+class BorderPane
+class "ListView<LatLong>" as ListView
+class "fxmapcontrol.MapBase" as MapBase
+
+BorderPane *--> ListView: "left"
+BorderPane *--> MapBase: "center"
+
+AbstractFxAppController --> MapBase: "mapView"
+AbstractFxAppController --> ListView: "locationListView"
+@enduml
+ */
+
+/**
+ * The controller for the app.
+ * @author hal
+ *
+ */
+public abstract class AbstractFxAppController {
+
+  private LatLongsDataAccess dataAccess;
+
+  protected LatLongsDataAccess getDataAccess() {
+    return dataAccess;
+  }
+
+  protected void setDataAccess(final LatLongsDataAccess dataAccess) {
+    this.dataAccess = dataAccess;
+    if (locationListView != null) {
+      updateLocationViewList(0);
+    }
+  }
+
+  @FXML
+  private ListView<LatLong> locationListView;
+
+  @FXML
+  private MapBase mapView;
+
+  private MapItemsControl<MapNode> markersParent;
+  private MapMarker marker = null;
+  private DraggableNodeController draggableMapController = null;
+  private DraggableNodeController draggableMarkerController = null;
+  private Tooltip markerTooltip = null;
+  
+  
+  @FXML
+  private Slider zoomSlider;
+
+  @FXML
+  private void initialize() {
+    // map stuff
+    // mapView.getChildren().add(MapTileLayer.getOpenStreetMapLayer());
+    zoomSlider.valueProperty()
+      .addListener((prop, oldValue, newValue) -> mapView.setZoomLevel(zoomSlider.getValue()));
+    zoomSlider.setValue(8);
+    markersParent = new MapItemsControl<MapNode>();
+    mapView.getChildren().add(markersParent);
+    draggableMapController = new DraggableNodeController(this::handleMapDragged);
+    draggableMapController.setImmediate(true);
+    draggableMapController.attach(mapView);
+    draggableMarkerController = new DraggableNodeController(this::handleMarkerDragged);
+    // the location list
+    locationListView.getSelectionModel().selectedIndexProperty()
+      .addListener((prop, oldValue, newValue) -> updateMapMarker(true));
+    //connect the cell renderer to the list
+    locationListView.setCellFactory(listView -> new LatLongCell());
+  }
+
+  private void handleMapDragged(final Node node, final double dx, final double dy) {
+    final MapProjection projection = mapView.getProjection();
+    final Point2D point = projection.locationToViewportPoint(mapView.getCenter());
+    final Location newCenter = projection.viewportPointToLocation(point.add(-dx, -dy));
+    mapView.setCenter(newCenter);
+  }
+
+  private void handleMarkerDragged(final Node node, final double dx, final double dy) {
+    final MapProjection projection = mapView.getProjection();
+    final Point2D point = projection.locationToViewportPoint(marker.getLocation());
+    final Location newLocation = projection.viewportPointToLocation(point.add(dx, dy));
+    dataAccess.setLatLong(locationListView.getSelectionModel().getSelectedIndex(),
+        location2LatLong(newLocation));
+    updateLocationViewListSelection(false);
+  }
+
+  private LatLong location2LatLong(final Location newLocation) {
+    return new LatLong(newLocation.getLatitude(), newLocation.getLongitude());
+  }
+
+  private void updateMapMarker(final boolean centerOnMarker) {
+    final int num = locationListView.getSelectionModel().getSelectedIndex();
+    if (num < 0) {
+      markersParent.getItems().clear();
+      if (draggableMarkerController != null) {
+        draggableMarkerController.detach(marker);
+      }
+      marker = null;
+    } else {
+      final LatLong latLong = dataAccess.getLatLong(num);
+      if (marker == null) {
+        marker = new MapMarker(latLong);
+        markersParent.getItems().add(marker);
+        if (draggableMarkerController != null) {
+          draggableMarkerController.attach(marker);
+        }
+      } else {
+        marker.setLocation(latLong);
+      }
+      if (centerOnMarker) {
+        mapView.setCenter(marker.getLocation());
+      }
+    }
+    prepareMarkerTooltip();
+  }
+  
+  void prepareMarkerTooltip() {
+	  if ((marker != null) && (this.markerTooltip != null)) {
+		  Tooltip.uninstall(marker, markerTooltip);
+	  }
+	  
+	  Tooltip tooltip = new Tooltip();
+	  tooltip.setPrefWidth(300.0);
+	  tooltip.setWrapText(true);
+	  tooltip.setTextOverrun(OverrunStyle.ELLIPSIS);
+	  tooltip.setStyle(" -fx-background: rgba(255, 237, 131);\r\n" + 
+	  		"    -fx-text-fill: black;\r\n" + 
+	  		"    -fx-background-color: rgba(255, 237, 131, 0.8);\r\n" + 
+	  		"    -fx-background-radius: 6px;\r\n" + 
+	  		"    -fx-background-insets: 0;\r\n" + 
+	  		"    -fx-padding: 0.667em 0.75em 0.667em 0.75em; /* 10px */\r\n" + 
+	  		"    -fx-effect: dropshadow( three-pass-box , rgba(0,0,0,0.5) , 10, 0.0 , 0 , 3 );\r\n" + 
+	  		"    -fx-font-size: 0.85em;");
+	  
+	  final int num = locationListView.getSelectionModel().getSelectedIndex();
+		if (num >= 0) {
+			final LatLong latLong = dataAccess.getLatLong(num);
+			String tooltipText = "";
+			if(latLong.hasMetaData()) {
+				if(latLong.getMetaData().hasProperty(MetaData.NAME_PROPERTY)) {
+					tooltipText += latLong.getMetaData().getProperty(MetaData.NAME_PROPERTY) +"\n"; 
+				}
+				tooltipText += latLong.toString()+"\n"; 
+				if(latLong.getMetaData().hasProperty(MetaData.DESCRIPTION_PROPERTY)) {
+					tooltipText += latLong.getMetaData().getProperty(MetaData.DESCRIPTION_PROPERTY) +"\n"; 
+				}
+				MetaData metaData = latLong.getMetaData();
+				
+				final Iterator<String> props = metaData.propertyNames();
+			    if (props.hasNext()) {
+			    	String propString = "\n";
+			        while (props.hasNext()) {
+			        	String propName = props.next();
+			        	if((propName != MetaData.NAME_PROPERTY)
+			        			&&(propName != MetaData.DESCRIPTION_PROPERTY)) {
+			        		propString +=  propName + ":" + metaData.getProperty(propName) + "\n";
+			        	}
+			        }
+			        if(!propString.isBlank()) tooltipText +=  propString + "\n";
+			    }
+				
+				
+				final Iterator<String> tags = metaData.tags();
+			      if (tags.hasNext()) {
+			        String tagString = "\n| ";
+			        while (tags.hasNext()) {
+			        	tagString += tags.next() + " | ";
+			        }
+			        tooltipText +=  tagString + "\n";
+			      }
+			      
+			} else {
+				tooltipText += latLong.toString();
+			}
+			tooltip.setText(tooltipText);
+		}
+		this.markerTooltip = tooltip;
+		Tooltip.install(marker, tooltip);
+  }
+
+  @FXML
+  void handleAddLocation() {
+    final Location center = mapView.getCenter();
+    final LatLong latLong = location2LatLong(center);
+    final int pos = dataAccess.addLatLong(latLong);
+    updateLocationViewList(pos);
+  }
+
+  private void updateLocationViewListSelection(final Boolean updateMapMarker) {
+    final int selectedIndex = locationListView.getSelectionModel().getSelectedIndex();
+    locationListView.getItems().set(selectedIndex, dataAccess.getLatLong(selectedIndex));
+    if (updateMapMarker != null) {
+      updateMapMarker(updateMapMarker);
+    }
+  }
+
+  protected void updateLocationViewList(int selectedIndex) {
+    final LatLong[] latLongs = dataAccess.getAllLatLongs().toArray(new LatLong[0]);
+    final int oldSelectionIndex = locationListView.getSelectionModel().getSelectedIndex();
+    locationListView.setItems(FXCollections.observableArrayList(latLongs));
+    if (selectedIndex < 0 || selectedIndex >= latLongs.length) {
+      selectedIndex = oldSelectionIndex;
+    }
+    if (selectedIndex >= 0 && selectedIndex < latLongs.length) {
+      locationListView.getSelectionModel().select(selectedIndex);
+    }
+    
+  }
+
+  private ObjectMapper objectMapper;
+
+  /**
+   * Gets the ObjectMapper used by this controller.
+   * @return the ObjectMapper used by this controller
+   */
+  public ObjectMapper getObjectMapper() {
+    if (objectMapper == null) {
+      objectMapper = new ObjectMapper();
+      objectMapper.registerModule(new LatLongsModule());
+    }
+    return objectMapper;
+  }
+
+  protected void showExceptionDialog(final String message) {
+    final Alert alert = new Alert(AlertType.ERROR, message, ButtonType.CLOSE);
+    alert.showAndWait();
+  }
+
+  protected void showExceptionDialog(final String message, final Exception e) {
+    showExceptionDialog(message + ": " + e.getLocalizedMessage());
+  }
+}
-- 
GitLab