(gui) - added location/background (rest) - added location

This commit is contained in:
Blake J. Harnden 2018-09-19 16:32:25 -07:00
parent b6cc2ad86e
commit 28f14a9b66
13 changed files with 417 additions and 1 deletions

View file

@ -77,6 +77,8 @@ public class Controller implements Initializable {
private MobilityDialog mobilityDialog = new MobilityDialog(this); private MobilityDialog mobilityDialog = new MobilityDialog(this);
private ChartDialog chartDialog = new ChartDialog(this); private ChartDialog chartDialog = new ChartDialog(this);
private NodeTypesDialog nodeTypesDialog = new NodeTypesDialog(this); private NodeTypesDialog nodeTypesDialog = new NodeTypesDialog(this);
private BackgroundDialog backgroundDialog = new BackgroundDialog(this);
private LocationDialog locationDialog = new LocationDialog(this);
public Controller() { public Controller() {
// load configuration // load configuration
@ -140,6 +142,8 @@ public class Controller implements Initializable {
configDialog.setOwner(window); configDialog.setOwner(window);
mobilityDialog.setOwner(window); mobilityDialog.setOwner(window);
nodeTypesDialog.setOwner(window); nodeTypesDialog.setOwner(window);
backgroundDialog.setOwner(window);
locationDialog.setOwner(window);
} }
@FXML @FXML
@ -147,6 +151,16 @@ public class Controller implements Initializable {
nodeTypesDialog.showDialog(); nodeTypesDialog.showDialog();
} }
@FXML
private void onOptionsMenuBackground(ActionEvent event) {
backgroundDialog.showDialog();
}
@FXML
private void onOptionsMenuLocation(ActionEvent event) {
locationDialog.showDialog();
}
@FXML @FXML
private void onHelpMenuWebsite(ActionEvent event) { private void onHelpMenuWebsite(ActionEvent event) {
application.getHostServices().showDocument("https://github.com/coreemu/core"); application.getHostServices().showDocument("https://github.com/coreemu/core");

View file

@ -79,4 +79,8 @@ public interface ICoreClient {
MobilityConfig getMobilityConfig(CoreNode node) throws IOException; MobilityConfig getMobilityConfig(CoreNode node) throws IOException;
boolean mobilityAction(CoreNode node, String action) throws IOException; boolean mobilityAction(CoreNode node, String action) throws IOException;
LocationConfig getLocationConfig() throws IOException;
boolean setLocationConfig(LocationConfig config) throws IOException;
} }

View file

@ -309,6 +309,18 @@ public class CoreRestClient implements ICoreClient {
return WebUtils.putJson(url, config); return WebUtils.putJson(url, config);
} }
@Override
public LocationConfig getLocationConfig() throws IOException {
String url = getUrl(String.format("sessions/%s/location", sessionId));
return WebUtils.getJson(url, LocationConfig.class);
}
@Override
public boolean setLocationConfig(LocationConfig config) throws IOException {
String url = getUrl(String.format("sessions/%s/location", sessionId));
return WebUtils.putJson(url, config);
}
@Override @Override
public boolean createNode(CoreNode node) throws IOException { public boolean createNode(CoreNode node) throws IOException {
String url = getUrl(String.format("sessions/%s/nodes", sessionId)); String url = getUrl(String.format("sessions/%s/nodes", sessionId));

View file

@ -0,0 +1,10 @@
package com.core.data;
import lombok.Data;
@Data
public class Location {
private Double latitude = 0.0;
private Double longitude = 0.0;
private Double altitude = 0.0;
}

View file

@ -0,0 +1,10 @@
package com.core.data;
import lombok.Data;
@Data
public class LocationConfig {
private Position position = new Position();
private Location location = new Location();
private Double scale;
}

View file

@ -0,0 +1,51 @@
package com.core.graph;
import edu.uci.ics.jung.visualization.Layer;
import edu.uci.ics.jung.visualization.VisualizationViewer;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.io.IOException;
import java.nio.file.Paths;
public class BackgroundPaintable<V, E> implements VisualizationViewer.Paintable {
private final ImageIcon imageIcon;
private final VisualizationViewer<V, E> vv;
private final String imagePath;
public BackgroundPaintable(String imagePath, VisualizationViewer<V, E> vv) throws IOException {
this.imagePath = imagePath;
Image image = ImageIO.read(Paths.get(imagePath).toFile());
imageIcon = new ImageIcon(image);
this.vv = vv;
}
public String getImage() {
return imagePath;
}
@Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
AffineTransform oldXform = g2d.getTransform();
AffineTransform lat =
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.LAYOUT).getTransform();
AffineTransform vat =
vv.getRenderContext().getMultiLayerTransformer().getTransformer(Layer.VIEW).getTransform();
AffineTransform at = new AffineTransform();
at.concatenate(g2d.getTransform());
at.concatenate(vat);
at.concatenate(lat);
g2d.setTransform(at);
g.drawImage(imageIcon.getImage(), 0, 0,
imageIcon.getIconWidth(), imageIcon.getIconHeight(), vv);
g2d.setTransform(oldXform);
}
@Override
public boolean useTransform() {
return false;
}
}

View file

@ -55,6 +55,7 @@ public class NetworkGraph {
private Supplier<CoreLink> linkFactory = () -> new CoreLink(linkId++); private Supplier<CoreLink> linkFactory = () -> new CoreLink(linkId++);
private CorePopupGraphMousePlugin customPopupPlugin; private CorePopupGraphMousePlugin customPopupPlugin;
private CoreAnnotatingGraphMousePlugin<CoreNode, CoreLink> customAnnotatingPlugin; private CoreAnnotatingGraphMousePlugin<CoreNode, CoreLink> customAnnotatingPlugin;
private BackgroundPaintable<CoreNode, CoreLink> backgroundPaintable;
public NetworkGraph(Controller controller) { public NetworkGraph(Controller controller) {
this.controller = controller; this.controller = controller;
@ -164,6 +165,24 @@ public class NetworkGraph {
}); });
} }
public void setBackground(String imagePath) {
try {
backgroundPaintable = new BackgroundPaintable<>(imagePath, graphViewer);
graphViewer.addPreRenderPaintable(backgroundPaintable);
graphViewer.repaint();
} catch (IOException ex) {
logger.error("error setting background", ex);
}
}
public void removeBackground() {
if (backgroundPaintable != null) {
graphViewer.removePreRenderPaintable(backgroundPaintable);
graphViewer.repaint();
backgroundPaintable = null;
}
}
public void setMode(ModalGraphMouse.Mode mode) { public void setMode(ModalGraphMouse.Mode mode) {
graphMouse.setMode(mode); graphMouse.setMode(mode);
} }

View file

@ -0,0 +1,86 @@
package com.core.ui;
import com.core.Controller;
import com.core.data.CoreLink;
import com.core.data.CoreNode;
import com.core.graph.BackgroundPaintable;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import javafx.fxml.FXML;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
import javafx.stage.FileChooser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.nio.file.Paths;
public class BackgroundDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
@FXML
private ImageView imageView;
@FXML
private JFXTextField fileTextField;
@FXML
private JFXButton fileButton;
private JFXButton saveButton;
private JFXButton clearButton;
public BackgroundDialog(Controller controller) {
super(controller, "/fxml/background_dialog.fxml");
setTitle("Background Configuration");
saveButton = createButton("Save");
saveButton.setOnAction(event -> {
controller.getNetworkGraph().setBackground(fileTextField.getText());
close();
});
clearButton = createButton("Clear");
clearButton.setOnAction(event -> {
controller.getNetworkGraph().removeBackground();
close();
});
addCancelButton();
HBox parent = (HBox) imageView.getParent();
imageView.fitHeightProperty().bind(parent.heightProperty());
fileButton.setOnAction(event -> {
FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Select Background");
fileChooser.setInitialDirectory(new File(System.getProperty("user.home")));
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("PNG", "*.png"));
File file = fileChooser.showOpenDialog(getStage());
if (file != null) {
String uri = file.toURI().toString();
imageView.setImage(new Image(uri));
fileTextField.setText(file.getPath());
saveButton.setDisable(false);
}
});
}
public void showDialog() {
BackgroundPaintable<CoreNode, CoreLink> backgroundPaintable = getController().getNetworkGraph()
.getBackgroundPaintable();
saveButton.setDisable(true);
fileTextField.setText(null);
imageView.setImage(null);
if (backgroundPaintable == null) {
clearButton.setDisable(true);
} else {
String imagePath = backgroundPaintable.getImage();
fileTextField.setText(imagePath);
imageView.setImage(new Image(Paths.get(imagePath).toUri().toString()));
clearButton.setDisable(false);
}
show();
}
}

View file

@ -0,0 +1,85 @@
package com.core.ui;
import com.core.Controller;
import com.core.data.LocationConfig;
import com.jfoenix.controls.JFXButton;
import com.jfoenix.controls.JFXTextField;
import com.jfoenix.validation.DoubleValidator;
import javafx.fxml.FXML;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
public class LocationDialog extends StageDialog {
private static final Logger logger = LogManager.getLogger();
@FXML
private JFXTextField scaleTextField;
@FXML
private JFXTextField xTextField;
@FXML
private JFXTextField yTextField;
@FXML
private JFXTextField latTextField;
@FXML
private JFXTextField lonTextField;
@FXML
private JFXTextField altTextField;
private JFXButton saveButton;
public LocationDialog(Controller controller) {
super(controller, "/fxml/location_dialog.fxml");
setTitle("Location Configuration");
saveButton = createButton("Save");
saveButton.setOnAction(event -> {
boolean result = scaleTextField.validate();
if (!result) {
return;
}
LocationConfig config = new LocationConfig();
config.setScale(getDouble(scaleTextField));
config.getPosition().setX(getDouble(xTextField));
config.getPosition().setY(getDouble(yTextField));
config.getLocation().setLatitude(getDouble(latTextField));
config.getLocation().setLongitude(getDouble(lonTextField));
config.getLocation().setAltitude(getDouble(altTextField));
try {
getCoreClient().setLocationConfig(config);
close();
} catch (IOException ex) {
Toast.error("error setting location config", ex);
}
});
addCancelButton();
DoubleValidator validator = new DoubleValidator();
scaleTextField.getValidators().add(validator);
}
public Double getDouble(JFXTextField textField) {
return Double.parseDouble(textField.getText());
}
public void showDialog() {
try {
LocationConfig config = getCoreClient().getLocationConfig();
scaleTextField.setText(config.getScale().toString());
xTextField.setText(config.getPosition().getX().toString());
yTextField.setText(config.getPosition().getY().toString());
latTextField.setText(config.getLocation().getLatitude().toString());
lonTextField.setText(config.getLocation().getLongitude().toString());
altTextField.setText(config.getLocation().getAltitude().toString());
show();
} catch (IOException ex) {
Toast.error("error getting location config", ex);
}
}
}

View file

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXButton?>
<?import com.jfoenix.controls.JFXTextField?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<VBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="10.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="Image File" />
<GridPane hgap="10.0" vgap="10.0">
<columnConstraints>
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" percentWidth="15.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<JFXTextField fx:id="fileTextField" disable="true" />
<JFXButton fx:id="fileButton" maxWidth="1.7976931348623157E308" styleClass="core-button" text="File" GridPane.columnIndex="1" />
</children>
</GridPane>
<HBox alignment="CENTER" VBox.vgrow="ALWAYS">
<children>
<ImageView fx:id="imageView" fitHeight="150.0" fitWidth="654.0" pickOnBounds="true" preserveRatio="true" />
</children>
</HBox>
</children>
</VBox>

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXTextField?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?import javafx.scene.layout.VBox?>
<VBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" spacing="10.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
<children>
<Label text="Scale (100 pixels / Unit)" />
<JFXTextField fx:id="scaleTextField" />
<Label text="Reference Point" />
<GridPane hgap="10.0" vgap="10.0">
<columnConstraints>
<ColumnConstraints minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="X" GridPane.halignment="CENTER" />
<Label text="Y" GridPane.columnIndex="2" GridPane.halignment="CENTER" />
<JFXTextField GridPane.columnIndex="1" fx:id="xTextField" />
<JFXTextField fx:id="yTextField" GridPane.columnIndex="3" />
</children>
</GridPane>
<Label text="Reference Location (Maps to Point)" />
<GridPane hgap="10.0" vgap="10.0">
<columnConstraints>
<ColumnConstraints minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
<ColumnConstraints minWidth="10.0" />
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="SOMETIMES" />
</rowConstraints>
<children>
<Label text="Latitude" GridPane.halignment="CENTER" />
<Label text="Longitude" GridPane.columnIndex="2" GridPane.halignment="CENTER" />
<Label text="Altitude (meters)" GridPane.columnIndex="4" GridPane.halignment="CENTER" />
<JFXTextField fx:id="latTextField" GridPane.columnIndex="1" />
<JFXTextField fx:id="lonTextField" GridPane.columnIndex="3" />
<JFXTextField fx:id="altTextField" GridPane.columnIndex="5" />
</children>
</GridPane>
</children>
</VBox>

View file

@ -32,7 +32,9 @@
</Menu> </Menu>
<Menu mnemonicParsing="false" text="Options"> <Menu mnemonicParsing="false" text="Options">
<items> <items>
<MenuItem mnemonicParsing="false" onAction="#onOptionsMenuNodeTypes" text="Node Configuration" /> <MenuItem mnemonicParsing="false" onAction="#onOptionsMenuNodeTypes" text="Nodes" />
<MenuItem mnemonicParsing="false" onAction="#onOptionsMenuLocation" text="Location" />
<MenuItem mnemonicParsing="false" onAction="#onOptionsMenuBackground" text="Background" />
</items> </items>
</Menu> </Menu>
<Menu mnemonicParsing="false" text="Help"> <Menu mnemonicParsing="false" text="Help">

View file

@ -101,6 +101,38 @@ def set_session_options(session_id):
return jsonify() return jsonify()
@api.route("/sessions/<int:session_id>/location", methods=["PUT"])
def set_session_location(session_id):
session = core_utils.get_session(coreemu, session_id)
data = request.get_json() or {}
position = data["position"]
location = data["location"]
session.location.refxyz = (position["x"], position["y"], position["z"])
session.location.setrefgeo(location["latitude"], location["longitude"], location["altitude"])
session.location.refscale = data["scale"]
return jsonify()
@api.route("/sessions/<int:session_id>/location")
def get_session_location(session_id):
session = core_utils.get_session(coreemu, session_id)
x, y, z = session.location.refxyz
lat, lon, alt = session.location.refgeo
return jsonify({
"position": {
"x": x,
"y": y,
"z": z,
},
"location": {
"latitude": lat,
"longitude": lon,
"altitude": alt
},
"scale": session.location.refscale
})
@api.route("/sessions/<int:session_id>") @api.route("/sessions/<int:session_id>")
def get_session(session_id): def get_session(session_id):
session = core_utils.get_session(coreemu, session_id) session = core_utils.get_session(coreemu, session_id)