PropertiesManager.java 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. package de.tu_darmstadt.informatik.tk.scopviz.ui;
  2. import java.util.ArrayList;
  3. import java.util.Optional;
  4. import org.graphstream.algorithm.Toolkit;
  5. import org.graphstream.graph.Edge;
  6. import org.graphstream.graph.Element;
  7. import org.graphstream.graph.Node;
  8. import de.tu_darmstadt.informatik.tk.scopviz.debug.Debug;
  9. import de.tu_darmstadt.informatik.tk.scopviz.main.GraphManager;
  10. import de.tu_darmstadt.informatik.tk.scopviz.main.Main;
  11. import javafx.application.Platform;
  12. import javafx.beans.binding.Bindings;
  13. import javafx.collections.FXCollections;
  14. import javafx.collections.ObservableList;
  15. import javafx.event.EventHandler;
  16. import javafx.geometry.Insets;
  17. import javafx.scene.control.ButtonBar.ButtonData;
  18. import javafx.scene.control.ButtonType;
  19. import javafx.scene.control.ChoiceBox;
  20. import javafx.scene.control.ContextMenu;
  21. import javafx.scene.control.Dialog;
  22. import javafx.scene.control.Label;
  23. import javafx.scene.control.MenuItem;
  24. import javafx.scene.control.TableColumn.CellEditEvent;
  25. import javafx.scene.control.TableRow;
  26. import javafx.scene.control.TableView;
  27. import javafx.scene.control.TextField;
  28. import javafx.scene.layout.GridPane;
  29. import javafx.util.Callback;
  30. /**
  31. * Manager for the Properties pane and its contents.
  32. *
  33. * @author Julian Ohl, Dominik Renkel
  34. * @version 1.0
  35. *
  36. */
  37. public final class PropertiesManager {
  38. /** Regex for detecting whether a String represent an Integer. */
  39. public static final String IS_INT = "^(-)?\\d+$";
  40. /** Regex for detecting whether a String represents a Boolean. */
  41. public static final String IS_BOOL = "^true$|^false$";
  42. /**
  43. * Regex for detecting whether a String represents a floating point number.
  44. */
  45. public static final String IS_FLOAT = "^[-+]?[0-9]*\\.?[0-9]+([eE][-+]?[0-9]+)?$";
  46. /** The Table of Attributes. */
  47. private static TableView<KeyValuePair> properties;
  48. /** Flag whether the name has been set. */
  49. public static boolean nameSet;
  50. /** Flag whether the value has been set. */
  51. public static boolean valueSet;
  52. /**
  53. * Private Constructor to prevent Instantiation.
  54. */
  55. private PropertiesManager() {
  56. }
  57. /**
  58. * Initializes the Manager by adding the List of properties to display into
  59. * the properties pane.
  60. *
  61. * @param properties
  62. * The list of properties to display
  63. */
  64. public static void initializeItems(TableView<KeyValuePair> propertiesInput) {
  65. properties = propertiesInput;
  66. }
  67. /**
  68. * Update Properties of selected Node/Edge, if a any Property was changed
  69. */
  70. public static final EventHandler<CellEditEvent<KeyValuePair, String>> setOnEditCommitHandler = new EventHandler<CellEditEvent<KeyValuePair, String>>() {
  71. @Override
  72. public void handle(CellEditEvent<KeyValuePair, String> t) {
  73. KeyValuePair editedPair = t.getTableView().getItems().get(t.getTablePosition().getRow());
  74. Object classType = editedPair.getClassType();
  75. String key = editedPair.getKey();
  76. // handling the problem when using his own names for properties
  77. // needed by graphstream
  78. // e.g. "ui.label" as "ID", might need an extra function/structure
  79. // if more of these are added
  80. if (key.equals("ID")) {
  81. key = "ui.label";
  82. }
  83. String oldValue = t.getOldValue();
  84. String newValue = t.getNewValue();
  85. Element selected = getSelected();
  86. // Type-Check the input
  87. if (classType.equals(Integer.class) && newValue.matches(IS_INT)) {
  88. selected.changeAttribute(key, Integer.valueOf(newValue));
  89. editedPair.setValue(newValue);
  90. Debug.out("Edited integer Attribute " + key);
  91. } else if (classType.equals(Boolean.class) && newValue.matches(IS_BOOL)) {
  92. selected.changeAttribute(key, Boolean.valueOf(newValue));
  93. editedPair.setValue(newValue);
  94. Debug.out("Edited boolean Attribute " + key);
  95. } else if (classType.equals(Float.class) && newValue.matches(IS_FLOAT)) {
  96. selected.changeAttribute(key, Float.valueOf(newValue));
  97. editedPair.setValue(newValue);
  98. Debug.out("Edited float Attribute " + key);
  99. } else if (classType.equals(Double.class) && newValue.matches(IS_FLOAT)) {
  100. selected.changeAttribute(key, Float.valueOf(newValue));
  101. editedPair.setValue(newValue);
  102. Debug.out("Edited double Attribute " + key);
  103. } else if (classType.equals(String.class)) {
  104. selected.changeAttribute(key, newValue);
  105. editedPair.setValue(newValue);
  106. Debug.out("Edited String Attribute " + key);
  107. } else {
  108. editedPair.setValue(oldValue);
  109. t.getTableView().getItems().get(t.getTablePosition().getRow()).setKey(oldValue);
  110. setItemsProperties();
  111. Debug.out("invalid input for this attribute type");
  112. }
  113. // Unselect row after updating Property
  114. properties.getSelectionModel().clearSelection();
  115. }
  116. };
  117. /**
  118. * Callback to be executed when a right click occurs on the table.
  119. */
  120. public static Callback<TableView<KeyValuePair>, TableRow<KeyValuePair>> rightClickCallback = new Callback<TableView<KeyValuePair>, TableRow<KeyValuePair>>() {
  121. @Override
  122. public TableRow<KeyValuePair> call(TableView<KeyValuePair> tableView) {
  123. final TableRow<KeyValuePair> row = new TableRow<>();
  124. // ContextMenu on non empty rows (add & delete)
  125. final ContextMenu menuOnNonEmptyRows = new ContextMenu();
  126. final MenuItem addPropMenuItem = new MenuItem("Add..");
  127. final MenuItem deletePropMenuItem = new MenuItem("Delete");
  128. // ContextMenu on empty rows (only add)
  129. final ContextMenu menuOnEmptyRows = new ContextMenu();
  130. final MenuItem onlyAddPropMenuItem = new MenuItem("Add..");
  131. // add functionality
  132. onlyAddPropMenuItem.setOnAction((event) -> addPropFunctionality());
  133. addPropMenuItem.setOnAction((event) -> addPropFunctionality());
  134. // delete functionality
  135. deletePropMenuItem.setOnAction((event) -> {
  136. Debug.out("Remove Element");
  137. removeProperty(row.getItem());
  138. properties.getItems().remove(row.getItem());
  139. });
  140. // add MenuItem to ContextMenu
  141. menuOnEmptyRows.getItems().add(onlyAddPropMenuItem);
  142. menuOnNonEmptyRows.getItems().addAll(addPropMenuItem, deletePropMenuItem);
  143. // when empty row right-clicked open special menu (only add),
  144. // otherwise normal menu (add & delete)
  145. row.contextMenuProperty().bind(Bindings.when(Bindings.isNotNull(row.itemProperty()))
  146. .then(menuOnNonEmptyRows).otherwise(menuOnEmptyRows));
  147. return row;
  148. }
  149. };
  150. /**
  151. * Sets Property-TableView Elements to selected Node or Edge Properties
  152. */
  153. public static void setItemsProperties() {
  154. // TODO: eliminate need for separate handling of nodes and edges
  155. String nid = Main.getInstance().getGraphManager().getSelectedNodeID();
  156. String eid = Main.getInstance().getGraphManager().getSelectedEdgeID();
  157. if (nid != null) {
  158. Node selectedNode = Main.getInstance().getGraphManager().getGraph().getNode(nid);
  159. showNewDataSet(selectedNode);
  160. } else if (eid != null) {
  161. Edge selectedEdge = Main.getInstance().getGraphManager().getGraph().getEdge(eid);
  162. showNewDataSet(selectedEdge);
  163. } else
  164. return;
  165. }
  166. /**
  167. * Add properties of selected Node or Edge to Properties TableView
  168. *
  169. * @param selected
  170. * selected Node or Edge
  171. * @param newData
  172. */
  173. public static void showNewDataSet(Element selected) {
  174. if (selected == null) {
  175. return;
  176. }
  177. ObservableList<KeyValuePair> newData = FXCollections.observableArrayList();
  178. for (String key : selected.getAttributeKeySet()) {
  179. switch (key) {
  180. // filter out or change attributes added by graphstream that are of
  181. // no use to the user
  182. case "ui.label":
  183. if (selected instanceof Node) {
  184. Object actualAttribute = selected.getAttribute(key);
  185. // replace UI Label with ID"
  186. key = "ID";
  187. newData.add(0, new KeyValuePair(key, String.valueOf(actualAttribute), actualAttribute.getClass()));
  188. } else if (selected instanceof Edge) {
  189. }
  190. break;
  191. case "layout.frozen":
  192. break;
  193. case "ui.style":
  194. break;
  195. case "ui.j2dsk":
  196. break;
  197. case "ui.clicked":
  198. break;
  199. case "ui.map.selected":
  200. break;
  201. case "xyz":
  202. double[] pos = Toolkit.nodePosition((Node) selected);
  203. newData.add(new KeyValuePair("x", String.valueOf(pos[0]), double.class));
  204. newData.add(new KeyValuePair("y", String.valueOf(pos[1]), double.class));
  205. newData.add(new KeyValuePair("z", String.valueOf(pos[2]), double.class));
  206. break;
  207. case "mapping":
  208. case "mapping-parent":
  209. case "mapping-parent-id":
  210. case "ui.class":
  211. if (!Debug.DEBUG_ENABLED)
  212. break;
  213. default:
  214. Object actualAttribute = selected.getAttribute(key);
  215. newData.add(new KeyValuePair(key, String.valueOf(actualAttribute), actualAttribute.getClass()));
  216. break;
  217. }
  218. }
  219. properties.setItems(newData);
  220. }
  221. /**
  222. * Get the selected node or edge from the GraphManager.
  223. *
  224. * @return selected node or egde
  225. */
  226. private static Element getSelected() {
  227. GraphManager viz = Main.getInstance().getGraphManager();
  228. String nid = viz.getSelectedNodeID();
  229. String eid = viz.getSelectedEdgeID();
  230. if (nid != null) {
  231. return viz.getGraph().getNode(nid);
  232. } else if (eid != null) {
  233. return viz.getGraph().getEdge(eid);
  234. } else
  235. return null;
  236. }
  237. /**
  238. * Delete a given Pair from the current Node or Edge
  239. *
  240. * @param pair
  241. * selectedProperty
  242. */
  243. private static void removeProperty(KeyValuePair pair) {
  244. Element selected = getSelected();
  245. selected.removeAttribute(pair.getKey());
  246. }
  247. /**
  248. * contextMenu add button functionality
  249. */
  250. private static void addPropFunctionality() {
  251. Debug.out("Add Element");
  252. // Create new Dialog
  253. Dialog<ArrayList<String>> addPropDialog = new Dialog<>();
  254. addPropDialog.setTitle("Add Property");
  255. addPropDialog.setHeaderText("Choose your Property Details");
  256. ButtonType addButtonType = new ButtonType("Confirm", ButtonData.OK_DONE);
  257. addPropDialog.getDialogPane().getButtonTypes().addAll(addButtonType, ButtonType.CANCEL);
  258. // create grid
  259. GridPane grid = new GridPane();
  260. grid.setHgap(10);
  261. grid.setVgap(10);
  262. grid.setPadding(new Insets(20, 150, 10, 10));
  263. // create dialog elements
  264. TextField name = new TextField();
  265. name.setPromptText("Name");
  266. TextField value = new TextField();
  267. value.setPromptText("Value");
  268. ChoiceBox<String> type = new ChoiceBox<String>();
  269. type.setItems(FXCollections.observableArrayList("Integer", "Float", "String", "Boolean"));
  270. type.getSelectionModel().selectFirst();
  271. // position elements on grid
  272. grid.add(new Label("Property Name:"), 0, 0);
  273. grid.add(name, 1, 0);
  274. grid.add(new Label("Property Value:"), 0, 1);
  275. grid.add(value, 1, 1);
  276. grid.add(new Label("Property Type:"), 0, 2);
  277. grid.add(type, 1, 2);
  278. javafx.scene.Node confirmButton = addPropDialog.getDialogPane().lookupButton(addButtonType);
  279. confirmButton.setDisable(true);
  280. nameSet = false;
  281. valueSet = false;
  282. // hide confirm button, when textfields empty
  283. name.textProperty().addListener((observable, oldValue, newValue) -> {
  284. PropertiesManager.nameSet = true;
  285. if (newValue.trim().isEmpty()) {
  286. PropertiesManager.nameSet = false;
  287. confirmButton.setDisable(true);
  288. } else if (PropertiesManager.valueSet) {
  289. confirmButton.setDisable(false);
  290. }
  291. });
  292. value.textProperty().addListener((observable, oldValue, newValue) -> {
  293. PropertiesManager.valueSet = true;
  294. if (newValue.trim().isEmpty()) {
  295. PropertiesManager.valueSet = false;
  296. confirmButton.setDisable(true);
  297. } else if (PropertiesManager.nameSet) {
  298. confirmButton.setDisable(false);
  299. }
  300. });
  301. // set dialog
  302. addPropDialog.getDialogPane().setContent(grid);
  303. Platform.runLater(() -> name.requestFocus());
  304. // get new property values
  305. addPropDialog.setResultConverter(dialogButton -> {
  306. if (dialogButton == addButtonType) {
  307. ArrayList<String> tmp = new ArrayList<String>();
  308. tmp.add(name.getText());
  309. tmp.add(value.getText());
  310. tmp.add(type.getValue());
  311. return tmp;
  312. } else
  313. return null;
  314. });
  315. Optional<ArrayList<String>> result = addPropDialog.showAndWait();
  316. // create new Property
  317. result.ifPresent(t -> {
  318. System.out.println("Name: " + t.get(0) + ", Value: " + t.get(1) + ", Type: " + t.get(2));
  319. Element selected = getSelected();
  320. if (t.get(2).equals("Integer")) {
  321. selected.addAttribute(t.get(0), Integer.valueOf(t.get(1)));
  322. } else if (t.get(2).equals("Float")) {
  323. selected.addAttribute(t.get(0), Float.valueOf(t.get(1)));
  324. } else if (t.get(2).equals("String")) {
  325. selected.addAttribute(t.get(0), String.valueOf(t.get(1)));
  326. } else if (t.get(2).equals("Boolean")) {
  327. selected.addAttribute(t.get(0), Boolean.valueOf(t.get(1)));
  328. }
  329. showNewDataSet(selected);
  330. });
  331. }
  332. }