Commit 528c56fa authored by Hallvard Trætteberg's avatar Hallvard Trætteberg

Merge branch...

Merge branch 'issue-11-allow-user-to-enter-and-update-metadata-about-the-latlong-points' into 'master'

Issue 11 allow user to enter and update metadata about the latlong points

Closes #10 and #11

See merge request !6
parents 4adf95b1 7e45ac70
Pipeline #53471 passed with stage
in 2 minutes and 11 seconds
......@@ -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;
}
}
......@@ -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
......
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;
}
}
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);
}
}
}
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;
}
}
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();