No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

NodesInputPanel.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424
  1. package org.insa.graphs.gui;
  2. import java.awt.Color;
  3. import java.awt.Font;
  4. import java.awt.GridBagConstraints;
  5. import java.awt.GridBagLayout;
  6. import java.awt.Insets;
  7. import java.awt.event.ActionEvent;
  8. import java.awt.event.ActionListener;
  9. import java.util.ArrayList;
  10. import java.util.Collections;
  11. import java.util.IdentityHashMap;
  12. import java.util.List;
  13. import java.util.Map;
  14. import javax.swing.JButton;
  15. import javax.swing.JComponent;
  16. import javax.swing.JLabel;
  17. import javax.swing.JPanel;
  18. import javax.swing.JTextField;
  19. import javax.swing.event.DocumentEvent;
  20. import javax.swing.event.DocumentListener;
  21. import org.insa.graphs.gui.drawing.Drawing;
  22. import org.insa.graphs.gui.drawing.Drawing.AlphaMode;
  23. import org.insa.graphs.gui.drawing.DrawingClickListener;
  24. import org.insa.graphs.gui.drawing.overlays.MarkerOverlay;
  25. import org.insa.graphs.model.Graph;
  26. import org.insa.graphs.model.Node;
  27. import org.insa.graphs.model.Point;
  28. public class NodesInputPanel extends JPanel
  29. implements DrawingClickListener, DrawingChangeListener, GraphChangeListener {
  30. /**
  31. *
  32. */
  33. private static final long serialVersionUID = 1L;
  34. private static final Color DEFAULT_MARKER_COLOR = Color.BLUE;
  35. /**
  36. * Utility class that can be used to find a node from coordinates in a "fast"
  37. * way.
  38. *
  39. */
  40. private static class NodeFinder {
  41. // Graph associated with this node finder.
  42. private Graph graph;
  43. /**
  44. * @param graph
  45. */
  46. public NodeFinder(Graph graph) {
  47. this.graph = graph;
  48. }
  49. /**
  50. * @param point
  51. *
  52. * @return the closest node to the given point, or null if no node is "close
  53. * enough".
  54. */
  55. public Node findClosestNode(Point point) {
  56. Node minNode = null;
  57. double minDis = Double.POSITIVE_INFINITY;
  58. for (Node node: graph.getNodes()) {
  59. double dlon = point.getLongitude() - node.getPoint().getLongitude();
  60. double dlat = point.getLatitude() - node.getPoint().getLatitude();
  61. double dis = dlon * dlon + dlat * dlat; // No need to square
  62. if (dis < minDis) {
  63. minNode = node;
  64. minDis = dis;
  65. }
  66. }
  67. return minNode;
  68. }
  69. }
  70. /**
  71. * Event data send when a node input has changed.
  72. *
  73. */
  74. public class InputChangedEvent extends ActionEvent {
  75. /**
  76. *
  77. */
  78. private static final long serialVersionUID = 3440024811352247731L;
  79. protected static final String ALL_INPUT_FILLED_EVENT_COMMAND = "allInputFilled";
  80. protected static final int ALL_INPUT_FILLED_EVENT_ID = 0x1;
  81. // List of nodes
  82. List<Node> nodes;
  83. public InputChangedEvent(List<Node> nodes2) {
  84. super(NodesInputPanel.this, ALL_INPUT_FILLED_EVENT_ID, ALL_INPUT_FILLED_EVENT_COMMAND);
  85. this.nodes = nodes2;
  86. }
  87. List<Node> getNodes() {
  88. return Collections.unmodifiableList(nodes);
  89. }
  90. };
  91. // Node inputs and markers.
  92. private final ArrayList<JTextField> nodeInputs = new ArrayList<>();
  93. private final Map<JTextField, MarkerOverlay> markerTrackers = new IdentityHashMap<JTextField, MarkerOverlay>();
  94. // Component that can be enabled/disabled.
  95. private ArrayList<JComponent> components = new ArrayList<>();
  96. private int inputToFillIndex;
  97. // ActionListener called when all inputs are filled.
  98. private ArrayList<ActionListener> inputChangeListeners = new ArrayList<>();
  99. // Drawing and graph
  100. private Drawing drawing;
  101. private Graph graph;
  102. private NodeFinder nodeFinder;
  103. /**
  104. * Create a new NodesInputPanel.
  105. *
  106. */
  107. public NodesInputPanel() {
  108. super(new GridBagLayout());
  109. initInputToFill();
  110. }
  111. /**
  112. * Add an InputChanged listener to this panel. This listener will be notified by
  113. * a {@link InputChangedEvent} each time an input in this panel change (click,
  114. * clear, manual input).
  115. *
  116. * @param listener Listener to add.
  117. *
  118. * @see InputChangedEvent
  119. */
  120. public void addInputChangedListener(ActionListener listener) {
  121. inputChangeListeners.add(listener);
  122. }
  123. @Override
  124. public void setVisible(boolean visible) {
  125. super.setVisible(visible);
  126. for (JTextField input: nodeInputs) {
  127. MarkerOverlay marker = markerTrackers.getOrDefault(input, null);
  128. if (marker != null) {
  129. marker.setVisible(visible && !input.getText().trim().isEmpty());
  130. }
  131. }
  132. }
  133. @Override
  134. public void setEnabled(boolean enabled) {
  135. for (JComponent component: components) {
  136. component.setEnabled(enabled);
  137. }
  138. super.setEnabled(enabled);
  139. if (enabled) {
  140. // Enable: Check if there is an input to fill, otherwize find the next one.
  141. if (getInputToFill() == null) {
  142. nextInputToFill();
  143. }
  144. }
  145. else {
  146. // Disable, next input to fill = -1.
  147. this.inputToFillIndex = -1;
  148. }
  149. }
  150. public void clear() {
  151. for (JTextField field: nodeInputs) {
  152. field.setText("");
  153. markerTrackers.put(field, null);
  154. }
  155. initInputToFill();
  156. }
  157. public void addTextField(String label) {
  158. addTextField(label, DEFAULT_MARKER_COLOR);
  159. }
  160. public void addTextField(String label, Color markerColor) {
  161. GridBagConstraints c = new GridBagConstraints();
  162. c.insets = new Insets(3, 3, 3, 3);
  163. JLabel jLabel = new JLabel(label);
  164. jLabel.setFont(jLabel.getFont().deriveFont(~Font.BOLD));
  165. JTextField textField = new JTextField();
  166. jLabel.setLabelFor(textField);
  167. c.gridx = 0;
  168. c.gridy = nodeInputs.size();
  169. c.weightx = 0;
  170. c.gridwidth = 1;
  171. c.fill = GridBagConstraints.HORIZONTAL;
  172. add(jLabel, c);
  173. c.gridx = 1;
  174. c.gridy = nodeInputs.size();
  175. c.weightx = 1;
  176. c.gridwidth = 1;
  177. c.fill = GridBagConstraints.HORIZONTAL;
  178. add(textField, c);
  179. JButton clearButton = new JButton("Clear");
  180. c.gridx = 2;
  181. c.gridy = nodeInputs.size();
  182. c.weightx = 0;
  183. c.gridwidth = 1;
  184. c.fill = GridBagConstraints.HORIZONTAL;
  185. add(clearButton, c);
  186. JButton clickButton = new JButton("Click");
  187. c.gridx = 3;
  188. c.gridy = nodeInputs.size();
  189. c.weightx = 0;
  190. c.gridwidth = 1;
  191. c.fill = GridBagConstraints.HORIZONTAL;
  192. add(clickButton, c);
  193. // Did not find something easier that this... ?
  194. textField.getDocument().addDocumentListener(new DocumentListener() {
  195. @Override
  196. public void changedUpdate(DocumentEvent e) {
  197. insertUpdate(e);
  198. }
  199. @Override
  200. public void removeUpdate(DocumentEvent e) {
  201. insertUpdate(e);
  202. }
  203. @Override
  204. public void insertUpdate(DocumentEvent e) {
  205. // Draw marker if possible
  206. Node curnode = getNodeForInput(textField);
  207. MarkerOverlay tracker = markerTrackers.getOrDefault(textField, null);
  208. if (curnode != null) {
  209. if (tracker == null) {
  210. tracker = drawing.drawMarker(curnode.getPoint(), markerColor, Color.BLACK,
  211. AlphaMode.TRANSPARENT);
  212. markerTrackers.put(textField, tracker);
  213. }
  214. else {
  215. tracker.moveTo(curnode.getPoint());
  216. }
  217. tracker.setVisible(true);
  218. }
  219. else if (tracker != null) {
  220. tracker.setVisible(false);
  221. if (getInputToFill() == null) {
  222. nextInputToFill();
  223. }
  224. }
  225. // Create array of nodes
  226. List<Node> nodes = getNodeForInputs();
  227. // Trigger change event.
  228. for (ActionListener lis: inputChangeListeners) {
  229. lis.actionPerformed(new InputChangedEvent(nodes));
  230. }
  231. }
  232. });
  233. clearButton.addActionListener(new ActionListener() {
  234. @Override
  235. public void actionPerformed(ActionEvent e) {
  236. textField.setText("");
  237. setInputToFill(textField);
  238. }
  239. });
  240. clickButton.addActionListener(new ActionListener() {
  241. @Override
  242. public void actionPerformed(ActionEvent e) {
  243. setInputToFill(textField);
  244. }
  245. });
  246. nodeInputs.add(textField);
  247. components.add(textField);
  248. components.add(clearButton);
  249. components.add(clickButton);
  250. }
  251. /**
  252. * @return Current graph associated with this input panel.
  253. */
  254. protected Graph getGraph() {
  255. return this.graph;
  256. }
  257. /**
  258. * @return The node for the given text field, or null if the content of the text
  259. * field is invalid.
  260. */
  261. protected Node getNodeForInput(JTextField textfield) {
  262. try {
  263. Node node = graph.get(Integer.valueOf(textfield.getText().trim()));
  264. return node;
  265. }
  266. catch (IllegalArgumentException | IndexOutOfBoundsException ex) {
  267. return null;
  268. }
  269. }
  270. /**
  271. * @return List of nodes associated with the input. Some nodes may be null if
  272. * their associated input is invalid.
  273. */
  274. public List<Node> getNodeForInputs() {
  275. List<Node> nodes = new ArrayList<>(nodeInputs.size());
  276. for (JTextField input: nodeInputs) {
  277. nodes.add(getNodeForInput(input));
  278. }
  279. return nodes;
  280. }
  281. /**
  282. * Get the next input that should be filled by a click, or null if none should
  283. * be filled.
  284. *
  285. * @return
  286. */
  287. protected JTextField getInputToFill() {
  288. if (inputToFillIndex < 0 || inputToFillIndex >= nodeInputs.size()) {
  289. return null;
  290. }
  291. return nodeInputs.get(inputToFillIndex);
  292. }
  293. /**
  294. * Initialize the next input to fill.
  295. */
  296. protected void initInputToFill() {
  297. inputToFillIndex = 0;
  298. }
  299. /**
  300. * Set the next input to fill to the given text field.
  301. *
  302. * @param input
  303. */
  304. protected void setInputToFill(JTextField input) {
  305. inputToFillIndex = nodeInputs.indexOf(input);
  306. }
  307. /**
  308. * Find the next input to fill, if any.
  309. */
  310. protected void nextInputToFill() {
  311. boolean found = false;
  312. if (inputToFillIndex == -1) {
  313. inputToFillIndex = 0;
  314. }
  315. for (int i = 0; i < nodeInputs.size() && !found; ++i) {
  316. int nextIndex = (i + inputToFillIndex) % nodeInputs.size();
  317. JTextField input = nodeInputs.get(nextIndex);
  318. if (input.getText().trim().isEmpty()) {
  319. inputToFillIndex = nextIndex;
  320. found = true;
  321. }
  322. }
  323. if (!found) {
  324. inputToFillIndex = -1;
  325. }
  326. }
  327. @Override
  328. public void mouseClicked(Point point) {
  329. JTextField input = getInputToFill();
  330. if (input != null) {
  331. Node node = nodeFinder.findClosestNode(point);
  332. input.setText(String.valueOf(node.getId()));
  333. nextInputToFill();
  334. }
  335. }
  336. @Override
  337. public void newGraphLoaded(Graph graph) {
  338. if (graph != this.graph) {
  339. this.clear();
  340. this.graph = graph;
  341. nodeFinder = new NodeFinder(graph);
  342. // Disable if previously disabled...
  343. setEnabled(this.isEnabled());
  344. }
  345. }
  346. @Override
  347. public void onDrawingLoaded(Drawing oldDrawing, Drawing newDrawing) {
  348. if (newDrawing != drawing) {
  349. this.drawing = newDrawing;
  350. }
  351. }
  352. @Override
  353. public void onRedrawRequest() {
  354. for (JTextField input: nodeInputs) {
  355. MarkerOverlay tracker = markerTrackers.getOrDefault(input, null);
  356. if (tracker != null) {
  357. MarkerOverlay newMarker = this.drawing.drawMarker(tracker.getPoint(),
  358. tracker.getColor(), Color.BLACK, AlphaMode.TRANSPARENT);
  359. markerTrackers.put(input, newMarker);
  360. newMarker.setVisible(tracker.isVisible());
  361. tracker.delete();
  362. }
  363. }
  364. }
  365. }