diff --git a/simpleexample2/core/src/main/java/simpleex/core/MetaData.java b/simpleexample2/core/src/main/java/simpleex/core/MetaData.java index bd8d8d4212c7493ec950b2078d3ea5ab67bdd6de..a1357c06adf4ca5ef6fae044ac9cc14a1cc49880 100644 --- a/simpleexample2/core/src/main/java/simpleex/core/MetaData.java +++ b/simpleexample2/core/src/main/java/simpleex/core/MetaData.java @@ -18,6 +18,12 @@ public class MetaData { */ public final static String DESCRIPTION_PROPERTY = "description"; + /** + * the list containing standard properties associated to location metadata + */ + public final static Collection<String> STD_PROPERTIES = List.of(NAME_PROPERTY, DESCRIPTION_PROPERTY); + + private Collection<String> tags; private List<String> properties; @@ -278,4 +284,49 @@ public class MetaData { } } } + + /** + * check if a certain property is custom or standard + * a property is standard when the name is found in STD_PROPERTIES + * @param propertyName the property name to check + * @return true if custom false if standard prop + */ + public static boolean isCustomProperty(String propertyName) { + return !(STD_PROPERTIES.contains(propertyName)); + } + + /** + * Convenience method to check if the metadata contains custom properties + * that is other properties than the name and description + * @return true if there are any properties apart from standard ones and false otherwise + */ + public boolean hasCustomProperties() { + for (int i = 0; i < this.properties.size(); i = i + 2) { + String propertyName = this.properties.get(i); + if(isCustomProperty(propertyName)) return true; + } + return false; + } + + /** + * Convenience method to check if the metadata contains properties + * other than the ones given in a list + * @return true if there are any properties not in the list, false otherwise + */ + public boolean hasOtherProperties(String ... propertyNames) { + outer: for (int i = 0; i < this.properties.size(); i = i + 2) { + String propertyName = this.properties.get(i); + for (String knownPropertyName : propertyNames) { + if (propertyName.equals(knownPropertyName)) { + continue outer; + } + return true; + } + } + return false; + } + + + } + diff --git a/simpleexample2/fxui/build.gradle b/simpleexample2/fxui/build.gradle index 5123a2bdf3a7c82f0148d5206f3738dbf39529bb..70656e189cba7e52276c3b412a682c07c44fb12d 100644 --- a/simpleexample2/fxui/build.gradle +++ b/simpleexample2/fxui/build.gradle @@ -27,6 +27,20 @@ application { run { args = ["http://localhost:8080/", "[[63.1, 11.2], [63.2, 11.0]]"] + + /*args = ['http://localhost:8080/','[{"latitude":63.1,"longitude":11.2},' + + '{"latitude":63.2,"longitude":11.0},{"latitude":63.5,"longitude":11.5,"metaData":'+ + '{"properties":[{"name":"name","value":"Awsome place"},{"name":"description","value":'+ + '"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla placerat urna non aliquet '+ + 'imperdiet. Nullam tincidunt felis vel sem blandit viverra. Etiam non volutpat erat. '+ + 'In hac habitasse platea dictumst. In lacus quam, rutrum vel malesuada non, molestie eu '+ + 'velit. Donec ut vulputate tortor, id convallis enim. Mauris et ipsum volutpat, dictum '+ + 'risus sed, aliquet sapien. Nam congue fermentum porta. Nullam non odio consequat, laoreet '+ + 'est eget, egestas dui. Aliquam suscipit elit non nisi sagittis, nec ultrices leo condimentum. '+ + 'Maecenas vel ligula nec mi feugiat volutpat. Aenean semper nisi sed tortor maximus tristique. '+ + 'Vestibulum at mauris massa. Nulla laoreet, velit eu lobortis efficitur, tortor sem molestie '+ + 'massa, at pellentesque tortor elit a nibh. In vel orci vitae magna rhoncus pulvinar sit amet '+ + 'id erat."}]}}]'] */ } // javafx specific way of specifying dependencies diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java b/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java index 6cd28996b148f720f2d55bce58c2e45e57d2122a..9b750a0f0d4bb6a8ac2bdbc697f0d5c628135c86 100644 --- a/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/AbstractFxAppController.java @@ -1,184 +1,269 @@ -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.scene.control.ButtonType; -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)); - } - - 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.event.EventHandler; +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.scene.control.ButtonType; +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()); + locationListView.addEventHandler(MetaDataEditorController.METADATA_SAVED, new EventHandler<MetaDataEvent>() { + + @Override + public void handle(MetaDataEvent event) { + System.out.println("saved at list level"); + + } + }); + } + + 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()); + } +} diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/FxApp.java b/simpleexample2/fxui/src/main/java/simpleex/ui/FxApp.java index 203d8dcb955d43744bba1ab26876b9799626c571..aece3fefdaf357d97cc5fdc23b3fe822c1f2c1de 100644 --- a/simpleexample2/fxui/src/main/java/simpleex/ui/FxApp.java +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/FxApp.java @@ -1,69 +1,102 @@ -package simpleex.ui; - -import java.net.URI; -import java.util.ArrayList; -import java.util.List; -import org.glassfish.grizzly.http.server.HttpServer; -import javafx.application.Application; -import javafx.fxml.FXMLLoader; -import javafx.scene.Parent; -import javafx.scene.Scene; -import javafx.stage.Stage; -import simpleex.core.LatLongs; -import simpleex.restapi.LatLongsService; -import simpleex.restserver.LatLongGrizzlyApp; - -public class FxApp extends Application { - - private HttpServer restServer = null; - @Override - public void start(final Stage stage) throws Exception { - URI baseUri = null; - final List<String> args = getParameters().getRaw(); - if (args.size() >= 1) { - final List<String> serverArgs = new ArrayList<String>(); - baseUri = URI.create(args.get(0)); - serverArgs.add(baseUri.toString()); - if (args.size() >= 2) { - // json of initial data - serverArgs.add(args.get(1)); - } - restServer = LatLongGrizzlyApp.startServer(serverArgs.toArray(new String[serverArgs.size()]), 5); - } - final String fxml = (baseUri != null ? "FxAppUsingRest.fxml" : "FxApp.fxml"); - final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxml)); - final Parent root = fxmlLoader.load(); - if (baseUri == null) { - // set initial data manually - final FxAppController controller = fxmlLoader.getController(); - controller.setLatLongs(new LatLongs(63.1, 11.2, 63.2, 11.0)); - } else { - final FxAppUsingRestController controller = fxmlLoader.getController(); - controller.setDataAccess(new RestLatLongsDataAccess(baseUri + LatLongsService.LAT_LONG_SERVICE_PATH, controller.getObjectMapper())); - } - final Scene scene = new Scene(root); - stage.setScene(scene); - stage.show(); - } - - @Override - public void stop() throws Exception { - if (restServer != null) { - restServer.shutdown(); - } - super.stop(); - } - - /** - * Launches the app. - * @param args the command line arguments - */ - public static void main(final String[] args) { - // only needed on ios - System.setProperty("os.target", "ios"); - System.setProperty("os.name", "iOS"); - System.setProperty("glass.platform", "ios"); - System.setProperty("targetos.name", "iOS"); - launch(args); - } -} +package simpleex.ui; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import org.glassfish.grizzly.http.server.HttpServer; +import javafx.application.Application; +import javafx.fxml.FXMLLoader; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.stage.Stage; +import simpleex.core.LatLong; +import simpleex.core.LatLongs; +import simpleex.core.MetaData; +import simpleex.restapi.LatLongsService; +import simpleex.restserver.LatLongGrizzlyApp; + +public class FxApp extends Application { + + private HttpServer restServer = null; + @Override + public void start(final Stage stage) throws Exception { + URI baseUri = null; + final List<String> args = getParameters().getRaw(); + if (args.size() >= 1) { + final List<String> serverArgs = new ArrayList<String>(); + baseUri = URI.create(args.get(0)); + serverArgs.add(baseUri.toString()); + if (args.size() >= 2) { + // json of initial data + serverArgs.add(args.get(1)); + } + restServer = LatLongGrizzlyApp.startServer(serverArgs.toArray(new String[serverArgs.size()]), 5); + } + final String fxml = (baseUri != null ? "FxAppUsingRest.fxml" : "FxApp.fxml"); + final FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource(fxml)); + final Parent root = fxmlLoader.load(); + if (baseUri == null) { + // set initial data manually + final FxAppController controller = fxmlLoader.getController(); + //controller.setLatLongs(new LatLongs(63.1, 11.2, 63.2, 11.0)); + controller.setLatLongs(getInitialData()); + } else { + final FxAppUsingRestController controller = fxmlLoader.getController(); + controller.setDataAccess(new RestLatLongsDataAccess(baseUri + LatLongsService.LAT_LONG_SERVICE_PATH, controller.getObjectMapper())); + } + final Scene scene = new Scene(root); + stage.setScene(scene); + stage.show(); + } + + @Override + public void stop() throws Exception { + if (restServer != null) { + restServer.shutdown(); + } + super.stop(); + } + + /** + * Launches the app. + * @param args the command line arguments + */ + public static void main(final String[] args) { + // only needed on ios + System.setProperty("os.target", "ios"); + System.setProperty("os.name", "iOS"); + System.setProperty("glass.platform", "ios"); + System.setProperty("targetos.name", "iOS"); + launch(args); + } + + /** + * Method to prepare the initial entries in the loaction list + * @return LatLongs instance with several items + */ + protected LatLongs getInitialData() { + LatLongs latLongs = new LatLongs(); + latLongs.addLatLong(new LatLong(63.1, 11.2)); + latLongs.addLatLong(new LatLong(63.2, 11.0)); + LatLong latLongWithMetaData = new LatLong(63.5, 11.5); + latLongWithMetaData.getMetaData().setProperty(MetaData.NAME_PROPERTY, "Awsome place"); + latLongWithMetaData.getMetaData().setProperty(MetaData.DESCRIPTION_PROPERTY, "Lorem ipsum dolor sit amet," + + " consectetur adipiscing elit. Nulla placerat urna non aliquet imperdiet. Nullam tincidunt felis " + + "vel sem blandit viverra. Etiam non volutpat erat. In hac habitasse platea dictumst. In lacus quam, " + + "rutrum vel malesuada non, molestie eu velit. Donec ut vulputate tortor, id convallis enim. Mauris " + + "et ipsum volutpat, dictum risus sed, aliquet sapien. Nam congue fermentum porta. Nullam non " + + "odio consequat, laoreet est eget, egestas dui. Aliquam suscipit elit non nisi sagittis, nec " + + "ultrices leo condimentum. Maecenas vel ligula nec mi feugiat volutpat. Aenean semper nisi sed" + + " tortor maximus tristique. Vestibulum at mauris massa. Nulla laoreet, velit eu lobortis efficitur, " + + "tortor sem molestie massa, at pellentesque tortor elit a nibh. In vel orci vitae magna rhoncus pulvinar " + + "sit amet id erat."); + latLongWithMetaData.getMetaData().addTags("tag 1","tag 2","a much longer tag 3"); + latLongWithMetaData.getMetaData().setProperty("custom property 1", "this is the value for custom property 1"); + latLongWithMetaData.getMetaData().setIntegerProperty("custom property 2 (int)", 13); + latLongWithMetaData.getMetaData().setDoubleProperty("custom property 3 (double)", 35.13); + latLongWithMetaData.getMetaData().setBooleanProperty("custom property 4 (boolean)", false); + latLongs.addLatLong(latLongWithMetaData); + return latLongs; + } + +} diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongCell.java b/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongCell.java new file mode 100644 index 0000000000000000000000000000000000000000..567ed680d22440cb17923c95f18d0edf7cf1512a --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongCell.java @@ -0,0 +1,28 @@ +package simpleex.ui; + +import javafx.scene.control.ListCell; +import javafx.scene.layout.Region; +import simpleex.core.LatLong; + +/** + * Renderer for a listview item containg a LatLong object + * @author Adrian Stoica + * + */ +public class LatLongCell extends ListCell<LatLong> { + + @Override + public void updateItem(LatLong location, boolean empty) { + super.updateItem(location, empty); + if(empty || location == null) { + setText(null); + setGraphic(null); + setPrefHeight(30.0); + } else { + LatLongCellController latLongCellController = new LatLongCellController(this); + latLongCellController.setLatLong(location); + setGraphic(latLongCellController.getCellView(this.isSelected())); + setPrefHeight(Region.USE_COMPUTED_SIZE); + } + } +} diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongCellController.java b/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongCellController.java new file mode 100644 index 0000000000000000000000000000000000000000..4bc5b3da9fac4ae3a500e3d89783a2d1ff4e9e46 --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/LatLongCellController.java @@ -0,0 +1,195 @@ +package simpleex.ui; + + +import java.util.Iterator; + +import javafx.event.EventHandler; +import javafx.fxml.FXMLLoader; +import javafx.geometry.Insets; +import javafx.scene.Parent; +import javafx.scene.Scene; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Region; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; +import javafx.stage.StageStyle; +import simpleex.core.LatLong; +import simpleex.core.MetaData; + +/** + * The controller for the renderer in the ListView cells + * containing the locations + * @author Adrian Stoica + * + */ +public class LatLongCellController { + + /** + * The main container of the cell UI + */ + private Region root; + + /** + * the horizontal box containing the coordinates + * and the add/edit button + */ + private HBox hBox; + + /** + * The button that will allow opening the editor + * for the selected item + */ + private Button editMetadataButton; + + /** + * container for the additional metadata + */ + private VBox vBox; + + /** + * the label for the name property + */ + private Label nameLabel; + + /** + * the label for the coordinates + */ + private Label coordinatesLabel; + + /** + * the label for the description + */ + private Label descriptionLabel; + + /** + * the current LatLong object that needs to be displayed + */ + private LatLong latLong; +private LatLongCell latLongCell; + + /** + * create a new controller for managing a LatLong list cell + * @param latLongCell + */ + public LatLongCellController(LatLongCell latLongCell) { + super(); + this.latLongCell = latLongCell; + } + + /** + * set the location object + * @param latLong the reference to the object to be displayed + */ + public void setLatLong(LatLong latLong) { + this.latLong = latLong; + } + + /** + * prepare the cell UI + * @param selected flag indicating that the item is selected + */ + protected void prepareView(boolean selected) { + this.hBox = new HBox(); + this.hBox.setSpacing(3.0); + this.coordinatesLabel = new Label(this.latLong.toString()); + coordinatesLabel.setPrefWidth(180.0); + this.hBox.getChildren().add(coordinatesLabel); + + if (this.latLong.hasMetaData()) { + if(this.latLong.getMetaData().hasCustomProperties()) { + this.hBox.getChildren().add(new ImageView(new Image(getClass().getResourceAsStream((selected?"i_selected.png":"i.png"))))); + } + } + + if(selected) { + this.editMetadataButton = new Button("..."); + this.hBox.getChildren().add(editMetadataButton); + editMetadataButton.setOnAction(event -> { + try { + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("MetaDataEditor.fxml")); + Parent root1 = fxmlLoader.load(); + MetaDataEditorController metaDataEditorController = fxmlLoader.getController(); + metaDataEditorController.setLatLong(this.latLong); + Stage stage = new Stage(); + stage.initModality(Modality.APPLICATION_MODAL); + stage.initStyle(StageStyle.DECORATED); + stage.setTitle("Location MetaData Editor"); + Scene scene = new Scene(root1); + stage.setScene(scene); + scene.getStylesheets().add(getClass().getResource("/simpleex/ui/tags/tagsbar.css").toExternalForm()); + stage.show(); + stage.addEventHandler(MetaDataEditorController.METADATA_SAVED, new EventHandler<MetaDataEvent>() { + + @Override + public void handle(MetaDataEvent event) { + if ( event.getEventType() == MetaDataEditorController.METADATA_SAVED) { + System.out.println("metadata saved click"); + LatLongCellController.this.latLongCell.updateItem(latLong, false); + } + } + } ); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + } + + if(this.latLong.hasMetaData()) { + this.vBox = new VBox(); + MetaData metaData = this.latLong.getMetaData(); + if(metaData.hasProperty(MetaData.NAME_PROPERTY)) { + nameLabel = new Label(); + nameLabel.setText(metaData.getProperty(MetaData.NAME_PROPERTY)); + vBox.getChildren().add(nameLabel); + } + vBox.getChildren().add(this.hBox); + if(metaData.hasProperty(MetaData.DESCRIPTION_PROPERTY)) { + descriptionLabel = new Label(); + descriptionLabel.setText(metaData.getProperty(MetaData.DESCRIPTION_PROPERTY)); + descriptionLabel.setWrapText(true); + descriptionLabel.setMaxHeight(50.0); + descriptionLabel.setManaged(true); + descriptionLabel.setMaxWidth(200.0); + vBox.getChildren().add(descriptionLabel); + } + + final Iterator<String> tags = metaData.tags(); + if (tags.hasNext()) { + HBox tagsBox = new HBox(); + tagsBox.setSpacing(5.0); + while (tags.hasNext()) { + Label tagLabel = new Label(tags.next()); + tagLabel.setStyle("-fx-background-color: #43464b; \r\n" + + " -fx-text-fill: white;\r\n" + + " -fx-border-radius: 3 3 3 3; \r\n" + + " -fx-background-radius: 3 3 3 03; "); + tagLabel.setPadding(new Insets(0.0,3.0,0.0,3.0)); + tagsBox.getChildren().add(tagLabel); + } + vBox.getChildren().add(tagsBox); + } + this.root = this.vBox; + } else { + this.root = this.hBox; + } + } + + + + + /** + * get the UI for the cell based on selection and + * the available info in the latLong object + * @param selected flag indicating that the item is selected + * @return the root container for the cell UI + */ + public Region getCellView(boolean selected) { + prepareView(selected); + return this.root; + } +} diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/MetaDataEditorController.java b/simpleexample2/fxui/src/main/java/simpleex/ui/MetaDataEditorController.java new file mode 100644 index 0000000000000000000000000000000000000000..88851ac225148f876f87207b3b4bf70c22b4206b --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/MetaDataEditorController.java @@ -0,0 +1,222 @@ +package simpleex.ui; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; + +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.value.ChangeListener; +import javafx.beans.value.ObservableValue; +import javafx.collections.ObservableList; +import javafx.event.ActionEvent; +import javafx.event.Event; +import javafx.event.EventType; +import javafx.fxml.FXML; +import javafx.scene.Node; +import javafx.scene.control.Button; +import javafx.scene.control.Label; +import javafx.scene.control.TableColumn; +import javafx.scene.control.TableView; +import javafx.scene.control.TextArea; +import javafx.scene.control.TextField; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; +import javafx.util.Pair; +import simpleex.core.LatLong; +import simpleex.core.MetaData; +import simpleex.ui.tags.TagsBar; + +/** + * Controller class for the location metadata editor + */ +public class MetaDataEditorController { + /** + * Generic metadata event + */ + public static EventType<MetaDataEvent> OPTIONS_ALL = new EventType<>("OPTIONS_ALL"); + + /** + * Specific metadata event dispatched when the metadata is saved + */ + public static EventType<MetaDataEvent> METADATA_SAVED = new EventType<>(OPTIONS_ALL, "METADATA_SAVED"); + + private LatLong latlong; + + @FXML + private BorderPane rootContainer; + + @FXML + private VBox centerVBox; + + @FXML + private Button saveButton; + + @FXML + private Button cancelButton; + + @FXML + private TextField nameInput; + + @FXML + private TextArea descriptionInput; + + @FXML + private TableView<Pair<String,String>> propertiesTableView; + + @FXML + private TableColumn<Pair<String, String>, String> propertyNamesColumn; + + @FXML + private TableColumn<Pair<String, String>, String> propertyValuesColumn; + + @FXML + private TextField newValueInput; + + @FXML + private TextField newKeyInput; + + @FXML + private Button buttonAddUpdate; + + @FXML + private Button buttonDelete; + + @FXML + private TagsBar tagsBar; + + @FXML + private Label coordinatesLabel; + + @FXML + private void initialize() { + propertyNamesColumn.setCellValueFactory( + cellData -> new SimpleStringProperty( + ((Pair<String, String>) (cellData.getValue())).getKey() + )); + + propertyValuesColumn.setCellValueFactory( + cellData -> new SimpleStringProperty( + ((Pair<String, String>) (cellData.getValue())).getValue() + )); + propertiesTableView.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Pair<String, String>>() { + + @Override + public void changed(ObservableValue<? extends Pair<String, String>> observable, Pair<String, String> oldValue, Pair<String, String> newValue) { + if(newValue == null) { + buttonAddUpdate.setDisable(false); + buttonDelete.setDisable(true); + } else { + buttonAddUpdate.setDisable(false); + buttonDelete.setDisable(false); + newKeyInput.setText(newValue.getKey()); + newValueInput.setText(newValue.getValue()); + } + } + }); + } + + @FXML + void onCancel(ActionEvent event) { + closeDialog(event); + } + + @FXML + void onLocationName(ActionEvent event) { + + } + + void closeDialog(ActionEvent event) { + Stage stage = (Stage) ((Button) event.getSource()).getScene().getWindow(); + stage.close(); + } + + @FXML + void onSave(ActionEvent event) { + latlong.getMetaData().setProperty(MetaData.NAME_PROPERTY, nameInput.getText()); + latlong.getMetaData().setProperty(MetaData.DESCRIPTION_PROPERTY, descriptionInput.getText()); + + ObservableList<Pair<String, String>> props = propertiesTableView.getItems(); + final Iterator<String> propertyNames = latlong.getMetaData().propertyNames(); + while(propertyNames.hasNext()) { + final String propName = propertyNames.next(); + if (MetaData.STD_PROPERTIES.contains(propName)) { + continue; + } else { + propertyNames.remove(); + } + } + for (Pair<String, String> pair : props) { + latlong.getMetaData().setProperty(pair.getKey(), pair.getValue()); + } + + final ObservableList<Node> tags = tagsBar.getTags(); + latlong.getMetaData().setTags(); + for (Node node : tags) { + latlong.getMetaData().addTags(((TagsBar.Tag)node).getTag()); + } + + Event.fireEvent(((Button) event.getSource()).getScene().getWindow(), + new MetaDataEvent(METADATA_SAVED)); + closeDialog(event); + } + + void setLatLong(LatLong latLong) { + this.latlong = latLong; + updateUi(); + } + + private void updateUi() { + nameInput.setText(latlong.getMetaData().getProperty(MetaData.NAME_PROPERTY)); + descriptionInput.setText(latlong.getMetaData().getProperty(MetaData.DESCRIPTION_PROPERTY)); + coordinatesLabel.setText(latlong.toString()); + + updatePropertiesTable(); + updateTags(); + } + + @FXML + public void onAddUpdateProperty(ActionEvent event) { + final String newKey = newKeyInput.getText(); + final String newValue = newValueInput.getText(); + if(!newKey.isBlank() && (MetaData.isCustomProperty(newKey))) { + latlong.getMetaData().setProperty(newKey, newValue); + updatePropertiesTable(); + } + } + + private void updatePropertiesTable() { + MetaData metaData = latlong.getMetaData(); + propertiesTableView.getItems().clear(); + final Iterator<String> propertyNames = metaData.propertyNames(); + Collection<Pair<String, String>> locationProperties = new ArrayList<Pair<String,String>>(); + if( propertyNames.hasNext()) { + while(propertyNames.hasNext()) { + final String propertyName = propertyNames.next(); + if (MetaData.STD_PROPERTIES.contains(propertyName)) { + continue; + } else { + Pair<String,String> p = new Pair<String, String>(propertyName, metaData.getProperty(propertyName)); + locationProperties.add(p); + } + } + propertiesTableView.getItems().addAll(locationProperties); + } + } + + private void updateTags() { + tagsBar.clearAllTags(); + MetaData metaData = latlong.getMetaData(); + final Iterator<String> tags = metaData.tags(); + while(tags.hasNext()) { + final String tag = tags.next(); + tagsBar.addTag(tag); + } + } + + @FXML + public void onDeleteProperty(ActionEvent event) { + int selectedIndex = propertiesTableView.getSelectionModel().getSelectedIndex(); + propertiesTableView.getItems().remove(selectedIndex); + } +} diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/MetaDataEvent.java b/simpleexample2/fxui/src/main/java/simpleex/ui/MetaDataEvent.java new file mode 100644 index 0000000000000000000000000000000000000000..94c09efb9b74d4b95f90b1b74c92f2c39741bbc6 --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/MetaDataEvent.java @@ -0,0 +1,21 @@ +package simpleex.ui; + +import javafx.event.Event; +import javafx.event.EventTarget; +import javafx.event.EventType; + +/** + * Event class intended to handle metadata related actions + */ +public class MetaDataEvent extends Event { + + private static final long serialVersionUID = 1L; + + public MetaDataEvent(Object source, EventTarget target, EventType<? extends Event> eventType) { + super(source, target, eventType); + } + + public MetaDataEvent(EventType<? extends Event> eventType) { + super(eventType); + } +} diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/tags/AutoCompleteTextField.java b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/AutoCompleteTextField.java new file mode 100644 index 0000000000000000000000000000000000000000..c2a6dc4422c2dcb745c3dada0c25eeac130e3d96 --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/AutoCompleteTextField.java @@ -0,0 +1,152 @@ +package simpleex.ui.tags; + +import java.util.SortedSet; +import java.util.TreeSet; + +import javafx.geometry.Side; +import javafx.scene.control.ContextMenu; +import javafx.scene.control.IndexRange; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; + +/** + * @author GOXR3PLUS + * https://github.com/goxr3plus/JavaFX-TagsBar + */ +public class AutoCompleteTextField extends TextField { + + /** The existing auto complete entries. */ + private final SortedSet<String> entries = new TreeSet<>(); + /** The pop up used to select an entry. */ + private ContextMenu contextMenu = new ContextMenu(); + private int maximumEntries = 15; + + private StringBuilder sb = new StringBuilder(); + private int lastLength; + + // Constructor + public AutoCompleteTextField() { + + // TextChanged Listener + textProperty().addListener(l -> { + if (getText().length() == 0) + contextMenu.hide(); + else { + if (entries.size() > 0) { + populatePopup(); + if (!contextMenu.isShowing()) { + contextMenu.show(AutoCompleteTextField.this, Side.BOTTOM, 0, 0); + // Request focus on first item + if (!contextMenu.getItems().isEmpty()) + contextMenu.getSkin().getNode().lookup(".menu-item:nth-child(1)").requestFocus(); + } + + } else + contextMenu.hide(); + + } + + }); + + // FocusListener + focusedProperty().addListener(l -> { + lastLength = 0; + sb.delete(0, sb.length()); + contextMenu.hide(); + }); + + } + + /** + * Get the existing set of autocomplete entries. + * + * @return The existing autocomplete entries. + */ + public SortedSet<String> getEntries() { + return entries; + } + + // TODO ---------------This method not works perfect needs to be redone.... + public void buggedMethod() { + // KeyReleased Listener + setOnKeyReleased(key -> { + KeyCode k = key.getCode(); + // this variable is used to bypass the auto complete process if the + // length is the same. this occurs if user types fast, the length of + // textfield will record after the user has typed after a certain + // delay. + if (lastLength != (getLength() - getSelectedText().length())) + lastLength = getLength() - getSelectedText().length(); + + // Not causing problems by these buttons + if (key.isControlDown() || k == KeyCode.BACK_SPACE || k == KeyCode.RIGHT || k == KeyCode.LEFT + || k == KeyCode.DELETE || k == KeyCode.HOME || k == KeyCode.END || k == KeyCode.TAB) + return; + + IndexRange ir = getSelection(); + sb.delete(0, sb.length()); + sb.append(getText()); + // remove selected string index until end so only unselected text + // will be recorded + try { + sb.delete(ir.getStart(), sb.length()); + } catch (Exception e) { + e.printStackTrace(); + } + + String originalLowered = getText().toLowerCase(); + // Select the first Matching + for (String s : entries) + if (s.toLowerCase().startsWith(originalLowered)) { + try { + setText(s); + } catch (Exception e) { + setText(sb.toString()); + } + positionCaret(sb.toString().length()); + selectEnd(); + break; + } + + }); + + } + + /** + * Populate the entry set with the given search results. + * + * @param sortedSet + * The set of matching strings. + */ + private void populatePopup() { + contextMenu.getItems().clear(); + + String text = getText().toLowerCase(); + // Filter the first maximumEntries matching the text + entries.stream().filter(string -> { + return string.toLowerCase().startsWith(text); + }).limit(maximumEntries).forEach(s -> { + // Add the element + MenuItem item = new MenuItem(s); + item.setOnAction(a -> { + setText(s); + positionCaret(getLength()); + }); + contextMenu.getItems().add(item); + }); + + // Entries to be shown + // for (int i = 0; i < count; i++) { + // final String result = searchResult.get(i); + // Label entryLabel = new Label(result); + // CustomMenuItem item = new CustomMenuItem(entryLabel, true); + // item.setOnAction(a->{ + // setText(result); + // contextMenu.hide(); + // }); + // menuItems.add(item); + // } + + } +} \ No newline at end of file diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/tags/LICENSE b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..8dada3edaf50dbc082c9a125058f25def75e625a --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/tags/README.md b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/README.md new file mode 100644 index 0000000000000000000000000000000000000000..1bc28ed71b0579e205ae92c2950d92f975a8899d --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/README.md @@ -0,0 +1,7 @@ +# JavaFX-TagsBar +Until now javaFX hadn't a TagsBar so here is a simple implementation. + +## IMAGE(using css) + + +### Code source is https://github.com/goxr3plus/JavaFX-TagsBar \ No newline at end of file diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/tags/Snippet.java b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/Snippet.java new file mode 100644 index 0000000000000000000000000000000000000000..953edeee3c8da9daa4a517e0cfe3bb67ec9c4770 --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/Snippet.java @@ -0,0 +1,45 @@ +package simpleex.ui.tags; + +import java.util.Arrays; +import java.util.List; + +import javafx.application.Application; +import javafx.scene.Scene; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +/** + * @author GOXR3PLUS + * https://github.com/goxr3plus/JavaFX-TagsBar + */ +public class Snippet extends Application { + + @Override + public void start(Stage primaryStage) { + + // All the musicGenres + List<String> genres = Arrays.asList("50s", "60s", "70s", "80s", "90s", "Adult Contemporary", "African", + "Alternative", "Ambient", "Americana", "Baladas", "Bass", "Big Band", "Big Beat", "Bluegrass", "Blues ", + "Bollywood", "Breakbeat", "Breakcore", "Breaks", "Calypso", "Caribbean", "Celtic", "Chill", "Zouk"); + + TagsBar tagBar = new TagsBar(); + tagBar.getEntries().addAll(genres); + + // Root + VBox root = new VBox(); + root.getChildren().addAll(tagBar); + root.setMinSize(300, 400); + + // Scene + Scene scene = new Scene(root, 500, 500); + scene.getStylesheets().add(getClass().getResource("style.css").toExternalForm()); + + // PrimaryStage + primaryStage.setScene(scene); + primaryStage.show(); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/simpleexample2/fxui/src/main/java/simpleex/ui/tags/TagsBar.java b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/TagsBar.java new file mode 100644 index 0000000000000000000000000000000000000000..5a46a2e56ed55cee8c6a311b4f27ee1d3d31618a --- /dev/null +++ b/simpleexample2/fxui/src/main/java/simpleex/ui/tags/TagsBar.java @@ -0,0 +1,182 @@ +package simpleex.ui.tags; + +import java.util.SortedSet; + +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.control.ScrollPane.ScrollBarPolicy; +import javafx.scene.image.Image; +import javafx.scene.image.ImageView; +import javafx.scene.image.WritableImage; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.Dragboard; +import javafx.scene.input.TransferMode; +import javafx.scene.layout.HBox; + +/** + * @author GOXR3PLUS + * https://github.com/goxr3plus/JavaFX-TagsBar + */ +public class TagsBar extends HBox { + + private HBox hBox = new HBox(); + private ScrollPane scrollPane = new ScrollPane(hBox); + private AutoCompleteTextField field = new AutoCompleteTextField(); + + // Constructor + public TagsBar() { + + getStyleClass().setAll("tags-bar"); + + // hBox + hBox.setStyle(" -fx-spacing:3px;"); + + // scrollPane + scrollPane.setVbarPolicy(ScrollBarPolicy.NEVER); + scrollPane.setHbarPolicy(ScrollBarPolicy.AS_NEEDED); + + // field + field.setPromptText("tag..."); + field.setMinSize(120, 30); + field.setBackground(null); + field.setOnAction(evt -> { + String text = field.getText(); + // No Duplicates allowed + if (!text.isEmpty() && /*getEntries().contains(text) && */ !hBox.getChildren().stream() + .anyMatch(s -> ((Tag) s).getTag().toLowerCase().equals(text.toLowerCase()))) + hBox.getChildren().add(new Tag(text)); + field.clear(); + }); + + getChildren().addAll(scrollPane, field); + } + + /** + * Returns all the tags of TagsBar + * + * @return + */ + public ObservableList<Node> getTags() { + return hBox.getChildren(); + } + + /** + * Get the existing set of auto complete entries. + * + * @return The existing auto complete entries. + */ + public SortedSet<String> getEntries() { + return field.getEntries(); + } + + /** + * Clears all the tags + * + */ + public void clearAllTags() { + hBox.getChildren().clear(); + } + + /** + * Add this tag if it doesn't exist + * + * @param tag + */ + public void addTag(String tag) { + if (!hBox.getChildren().stream().anyMatch(s -> ((Tag) s).getTag().toLowerCase().equals(tag.toLowerCase()))) + hBox.getChildren().add(new Tag(tag)); + } + + /** + * @author SuperGoliath TagClass + */ + public class Tag extends HBox { + + private Label textLabel = new Label(); + private Label iconLabel = new Label(null, new ImageView(new Image(getClass().getResourceAsStream("x1.png")))); + + // Constructor + public Tag(String tag) { + getStyleClass().add("tag"); + + // drag detected + setOnDragDetected(event -> { + + /* allow copy transfer mode */ + Dragboard db = startDragAndDrop(TransferMode.MOVE); + + /* put a string on dragboard */ + ClipboardContent content = new ClipboardContent(); + content.putString("#c" + getTag()); + + db.setDragView(snapshot(null, new WritableImage((int) getWidth(), (int) getHeight())), getWidth() / 2, + 0); + + db.setContent(content); + + event.consume(); + }); + + // drag over + setOnDragOver((event) -> { + /* + * data is dragged over the target accept it only if it is not + * dragged from the same imageView and if it has a string data + */ + if (event.getGestureSource() != this && event.getDragboard().hasString()) + event.acceptTransferModes(TransferMode.MOVE); + + event.consume(); + }); + + // drag dropped + setOnDragDropped(event -> { + + boolean sucess = false; + if (event.getDragboard().hasString() && event.getDragboard().getString().startsWith("#c")) { + String currentTag = getTag(); + setTag(event.getDragboard().getString().replace("#c", "")); + ((Tag) event.getGestureSource()).setTag(currentTag); + sucess = true; + } + + event.setDropCompleted(sucess); + event.consume(); + }); + + // drag done + setOnDragDone(event -> { + if (event.getTransferMode() == TransferMode.MOVE) { + // System.out.println("Source"+event.getGestureSource() + " + // /Target:" + event.getGestureTarget()); + } + + event.consume(); + }); + + // textLabel + textLabel.getStyleClass().add("label"); + textLabel.setText(tag); + // textLabel.setMinWidth(getTag().length() * 6); + + // iconLabel + iconLabel.setOnMouseReleased(r -> { + hBox.getChildren().remove(this); + }); + + getChildren().addAll(textLabel, iconLabel); + } + + public String getTag() { + return textLabel.getText(); + } + + public void setTag(String text) { + textLabel.setText(text); + } + + } + +} \ No newline at end of file diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/MetaDataEditor.fxml b/simpleexample2/fxui/src/main/resources/simpleex/ui/MetaDataEditor.fxml new file mode 100644 index 0000000000000000000000000000000000000000..3bc0351c73dfbf51b414ca641bb41c09634a544a --- /dev/null +++ b/simpleexample2/fxui/src/main/resources/simpleex/ui/MetaDataEditor.fxml @@ -0,0 +1,64 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<?import javafx.geometry.Insets?> +<?import javafx.scene.control.Button?> +<?import javafx.scene.control.Label?> +<?import javafx.scene.control.TableColumn?> +<?import javafx.scene.control.TableView?> +<?import javafx.scene.control.TextArea?> +<?import javafx.scene.control.TextField?> +<?import javafx.scene.layout.BorderPane?> +<?import javafx.scene.layout.HBox?> +<?import javafx.scene.layout.VBox?> +<?import simpleex.ui.tags.TagsBar?> + +<BorderPane prefHeight="500.0" prefWidth="600.0" xmlns="http://javafx.com/javafx/10.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="simpleex.ui.MetaDataEditorController" fx:id="rootContainer"> + <bottom> + <HBox alignment="CENTER_RIGHT" prefHeight="40.0" prefWidth="600.0" spacing="10.0" BorderPane.alignment="CENTER_RIGHT"> + <children> + <Button id="saveButton" fx:id="saveButton" mnemonicParsing="false" onAction="#onSave" prefWidth="150.0" style="-fx-background-color: darkgreen; -fx-text-fill: white; -fx-font-weight: bold; -fx-border-color: green; -fx-background-radius: 10; -fx-border-radius: 10;" text="Save" /> + <Button fx:id="cancelButton" mnemonicParsing="false" onAction="#onCancel" prefWidth="150.0" style="-fx-background-color: lightgray; -fx-background-radius: 10; -fx-border-color: gray; -fx-border-radius: 10;" text="Cancel" /> + </children> + <padding> + <Insets left="20.0" right="20.0" /> + </padding> + </HBox> + </bottom> + <center> + <VBox fx:id="centerVBox" prefHeight="200.0" prefWidth="100.0" spacing="5.0" BorderPane.alignment="CENTER"> + <children> + <Label text="Name:" /> + <TextField fx:id="nameInput" onAction="#onLocationName" promptText="enter a name for this location" /> + <HBox prefHeight="23.0" prefWidth="580.0" spacing="10.0"> + <children> + <Label text="Coordinates:" /> + <Label layoutX="10.0" layoutY="10.0" text="Label" fx:id="coordinatesLabel"/> + </children> + </HBox> + <Label text="Description:" /> + <TextArea fx:id="descriptionInput" prefHeight="68.0" prefWidth="580.0" + promptText="enter a description for this location" wrapText="true"/> + <Label text="Custom properties:" /> + <TableView fx:id="propertiesTableView" prefHeight="120.0" prefWidth="580.0"> + <columns> + <TableColumn fx:id="propertyNamesColumn" prefWidth="150.0" text="Name" /> + <TableColumn fx:id="propertyValuesColumn" prefWidth="400.0" text="Value" /> + </columns> + </TableView> + <HBox prefWidth="580" spacing="3.0"> + <children> + <TextField fx:id="newKeyInput" promptText="Property name"/> + <TextField fx:id="newValueInput" promptText="Property value"/> + <Button fx:id="buttonAddUpdate" onAction="#onAddUpdateProperty" text="Add / Update"/> + <Button fx:id="buttonDelete" onAction="#onDeleteProperty" text="Delete" disable="true"/> + </children> + </HBox> + <Label text="Tags:" /> + <TagsBar fx:id="tagsBar" /> + </children> + <BorderPane.margin> + <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" /> + </BorderPane.margin> + </VBox> + </center> +</BorderPane> diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/i.png b/simpleexample2/fxui/src/main/resources/simpleex/ui/i.png new file mode 100644 index 0000000000000000000000000000000000000000..1460dbb35089acf6bf1dde84aae34a45d6f48bfc Binary files /dev/null and b/simpleexample2/fxui/src/main/resources/simpleex/ui/i.png differ diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/i_selected.png b/simpleexample2/fxui/src/main/resources/simpleex/ui/i_selected.png new file mode 100644 index 0000000000000000000000000000000000000000..1c9c9515d2c2131918c0f527bf4d034a68bf04c2 Binary files /dev/null and b/simpleexample2/fxui/src/main/resources/simpleex/ui/i_selected.png differ diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/style.css b/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/style.css new file mode 100644 index 0000000000000000000000000000000000000000..5e520d580c1d1d9c9a9233ccabd665e4319d13e2 --- /dev/null +++ b/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/style.css @@ -0,0 +1,138 @@ + + +/******************************************************************************* + +* ToolTips!!! * + +******************************************************************************/ + +.tooltip{ + -fx-background-color: yellow; + -fx-text-fill:black; + -fx-font-size:12.0; + -fx-effect: dropshadow( three-pass-box, black, 10.0, 0.0, 0.0, 0.0); + +} + + +/******************************************************************************* + +* CSS Styles for the Layouts.. * + +******************************************************************************/ + .scroll-pane .viewport { + -fx-background-color: transparent; + } + + .scroll-pane { + -fx-background-color:transparent; + } + + + .scroll-bar{ + -fx-background-color: transparent; + /*-fx-background-radius:2.0em;*/ + } + + .scroll-bar:horizontal .increment-arrow { + -fx-background-color: black; + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-padding: 0.3em; + -fx-rotate: -90.0; + } + + .scroll-bar:horizontal .decrement-arrow { + -fx-background-color: black; + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-padding: 0.3em; + -fx-rotate: 90.0; + } + + .scroll-bar:vertical .increment-arrow { + -fx-background-color: rgb(211.0,211.0,211.0); + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-padding: 0.3em; + -fx-rotate: 0.0; + } + + + + .scroll-bar:vertical .decrement-arrow { + -fx-background-color: rgb(211.0,211.0,211.0); + -fx-shape: "M 0 0 L 4 8 L 8 0 Z"; + -fx-padding: 0.3em; + -fx-rotate: -180.0; + } + + .scroll-bar:vertical .increment-button, + .scroll-bar:vertical .decrement-button { + -fx-background-color:transparent; + } + + .scroll-bar:horizontal .increment-button, + .scroll-bar:horizontal .decrement-button { + -fx-background-color:transparent; + } + + +/******************************************************************************* + +* CSS Styles for the TextField and Search-Box.... * + +******************************************************************************/ + +.text-field { + -fx-background-color: black; + -fx-background-insets:3.0; + -fx-background-radius: 5.0; + -fx-text-fill:white; + -fx-font-size:15.0; +} + +.text-field:focused{ + -fx-background-color: orange, black; + -fx-background-insets: -0.1, 2.0; +} + + + +/******************************************************************************* + +* CSS Styles for the TextArea * + +******************************************************************************/ + + + + /******************************************************************************* + +* CSS Styles for the TagsBar * + +******************************************************************************/ + + .tags-bar { + -fx-background-color:white; + -fx-background-radius:15.0px; + -fx-min-height: 45.0; + -fx-alignment:center-right; + -fx-spacing: 3.0px; + -fx-padding: 3.0px; + } + + .tags-bar .tag { + -fx-background-color:black; + -fx-background-radius:10.0px; + -fx-alignment: center; + -fx-padding:1.0px; + -fx-cursor:hand; + } + + .tags-bar .tag .label{ + -fx-text-fill:white; + -fx-font-size:14.0px; + } + + + .text-field,.text-area { + -fx-prompt-text-fill:white; + } \ No newline at end of file diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/tagsbar.css b/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/tagsbar.css new file mode 100644 index 0000000000000000000000000000000000000000..f99f121541b9285b6119d83915003692a283fea4 --- /dev/null +++ b/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/tagsbar.css @@ -0,0 +1,31 @@ + + /******************************************************************************* + +* CSS Styles for the TagsBar * + +******************************************************************************/ + + .tags-bar { + -fx-background-color:white; + -fx-background-radius:10.0px; + -fx-min-height: 45.0; + /*-fx-alignment:center-right;*/ + -fx-spacing: 3.0px; + -fx-padding: 3.0px; + } + + .tags-bar .tag { + -fx-background-color:black; + -fx-background-radius:3.0px; + -fx-alignment: center; + -fx-padding:1.0px; + -fx-cursor:hand; + } + + .tags-bar .tag .label{ + -fx-text-fill:white; + -fx-font-size:14.0px; + } + + + \ No newline at end of file diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/x.png b/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/x.png new file mode 100644 index 0000000000000000000000000000000000000000..a60eba732fdb5a0451cbd774a274e9c9e6a13800 Binary files /dev/null and b/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/x.png differ diff --git a/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/x1.png b/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/x1.png new file mode 100644 index 0000000000000000000000000000000000000000..c94b0aeee842e6273336f58850934bad7d39cbd6 Binary files /dev/null and b/simpleexample2/fxui/src/main/resources/simpleex/ui/tags/x1.png differ