Source: controller/MatchingController.js

  1. /**
  2. * @file Class MatchingController
  3. * @version March 26, 2017
  4. *
  5. * @author Olivier Pirson --- http://www.opimedia.be/
  6. * @license GPLv3 --- Copyright (C) 2017 Olivier Pirson
  7. */
  8. /**
  9. * Controller for a matching and its view.
  10. */
  11. class MatchingController {
  12. /**
  13. * Construct a controller for this matching and this view.
  14. *
  15. * @param {Matching} matching
  16. * @param {MatchingView} view
  17. */
  18. constructor(matching, view) {
  19. assert(matching instanceof Matching, matching);
  20. assert(view instanceof MatchingView, view);
  21. // Constant to distinct action mode (point or segment)
  22. this.ACTION_POINT = 1;
  23. this.ACTION_SEGMENT = 2;
  24. // Attributes
  25. this._matching = matching;
  26. this._view = view;
  27. this._segmentFirstPoint = null; // Used as first point when build segment
  28. this._action = null; // add/remove point or segment
  29. this._verticalHorizontal = null; // draw any segment or only vertical or horizontal
  30. this._globalView = null; // global view
  31. this._setEventListeners();
  32. // Set values from HTML elements
  33. this.changeDrawSegments();
  34. }
  35. /**
  36. * Returns the matching.
  37. */
  38. get matching() { return this._matching; }
  39. /**
  40. * Returns the view.
  41. */
  42. get view() { return this._view; }
  43. /**
  44. * Set action property from HTML element
  45. * and reset the first point used to build segment.
  46. */
  47. changeAction() {
  48. this._action = (document.getElementById("radio-action-point").checked
  49. ? this.ACTION_POINT
  50. : this.ACTION_SEGMENT);
  51. this.clearSegmentFirstPoint();
  52. }
  53. /**
  54. * Set drawSegment property from HTML element
  55. * and update the view.
  56. */
  57. changeDrawSegments() {
  58. const drawSegments = (document.getElementById("radio-draw-segments-only").checked
  59. ? this.view.DRAW_SEGMENTS_ONLY
  60. : (document.getElementById("radio-draw-segments-consecutive").checked
  61. ? this.view.DRAW_SEGMENTS_CONSECUTIVE
  62. : this.view.DRAW_SEGMENTS_ALL));
  63. if (this._globalView !== null) {
  64. for (let view of this._globalView._leftView._linkedMatchingViews) {
  65. view.setDrawSegments(drawSegments);
  66. }
  67. }
  68. }
  69. /**
  70. * Set vertical-horizontal property from HTML element.
  71. */
  72. changeVerticalHorizontal() {
  73. this._verticalHorizontal = document.getElementById("checkbox-vertical-horizontal").checked;
  74. }
  75. /**
  76. * Reset the controller,
  77. * clear and update the view,
  78. * and update the global view.
  79. */
  80. clear() {
  81. this.clearSegmentFirstPoint();
  82. this.matching.clear();
  83. this.view.update();
  84. this._globalView.update(null, false);
  85. }
  86. /**
  87. * Reset the first point used to build segment.
  88. */
  89. clearSegmentFirstPoint() {
  90. this._segmentFirstPoint = null;
  91. }
  92. /**
  93. * If point is already in the matching
  94. * then remove it,
  95. * else add it.
  96. *
  97. * @param {Point} point
  98. */
  99. addOrRemovePoint(point) {
  100. assert(point instanceof Point, point);
  101. this._matching.clearIntermediaryLinkedMatchings();
  102. if (this.matching.pointIsInMatching(point)) { // remove existing point
  103. this.matching.pointRemove(point);
  104. }
  105. else { // add new point
  106. this.matching.pointAdd(point);
  107. }
  108. }
  109. /**
  110. * If segment is already in the matching
  111. * then remove it,
  112. * else if the segment don't intersects other segment in this matching then add it.
  113. *
  114. * @param {Segment} segment
  115. */
  116. addOrRemoveSegment(segment) {
  117. assert(segment instanceof Segment, segment);
  118. if (this.matching.segmentIsInMatching(segment)) {
  119. // Remove the existing segment
  120. this._matching.clearIntermediaryLinkedMatchings();
  121. this.matching.segmentRemove(segment);
  122. }
  123. else if (!this.matching.segmentIsIntersect(segment)) {
  124. // If no intersection then add the segment and non existing points
  125. this._matching.clearIntermediaryLinkedMatchings();
  126. if (!this.matching.pointIsInMatching(segment.a)) {
  127. this.matching.pointAdd(segment.a);
  128. }
  129. if (!this.matching.pointIsInMatching(segment.b)) {
  130. this.matching.pointAdd(segment.b);
  131. }
  132. this.matching.segmentAdd(segment);
  133. }
  134. }
  135. /**
  136. * Set the global view.
  137. *
  138. * @param {GlobalView} globalView
  139. */
  140. setGlobalView(globalView) {
  141. assert(globalView instanceof GlobalView, globalView);
  142. this._globalView = globalView;
  143. this.changeDrawSegments();
  144. }
  145. /**
  146. * Add/remove a point or a segment,
  147. * update the view,
  148. * and update the global view.
  149. *
  150. * @param {MouseEvent} event
  151. */
  152. _click(event) {
  153. assert(event instanceof MouseEvent, event);
  154. var currentPoint;
  155. var nearestPoint;
  156. [currentPoint, nearestPoint] = this._nearestPointIfExist(this._eventMouseToPoint(event));
  157. if (this._action === this.ACTION_POINT) { // point
  158. this.addOrRemovePoint(currentPoint);
  159. }
  160. else { // segment
  161. assert(this._action === this.ACTION_SEGMENT);
  162. if (this._segmentFirstPoint === null) { // set a first point
  163. this._segmentFirstPoint = currentPoint;
  164. }
  165. else { // second point of the segment
  166. if (this._verticalHorizontal) {
  167. currentPoint = this._verticalHorizontalSecondPoint(currentPoint);
  168. }
  169. this.addOrRemoveSegment(new Segment(this._segmentFirstPoint, currentPoint));
  170. this.clearSegmentFirstPoint();
  171. }
  172. }
  173. // Update views
  174. this.view.update({"currentPoint": currentPoint});
  175. this._globalView.update(currentPoint, false);
  176. }
  177. /**
  178. * Returns a point corresponding to the position in the mouse event.
  179. *
  180. * The vertical coordinate is reversed to have (*, 0) in the bottom.
  181. *
  182. * @param {MouseEvent} event
  183. *
  184. * @returns {Point}
  185. */
  186. _eventMouseToPoint(event) {
  187. assert(event instanceof MouseEvent, event);
  188. return new Point(Math.max(0, event.layerX),
  189. Math.max(0, event.target.height - 1 - event.layerY));
  190. }
  191. /**
  192. * Move the current point
  193. * and if a first point was fixed the building segment,
  194. * and update the view of the matching.
  195. *
  196. * @param {MouseEvent} event
  197. */
  198. _move(event) {
  199. assert(event instanceof MouseEvent, event);
  200. var currentPoint;
  201. var nearestPoint;
  202. [currentPoint, nearestPoint] = this._nearestPointIfExist(this._eventMouseToPoint(event));
  203. // Building segment or not
  204. var temporarySegment = null;
  205. if (this._segmentFirstPoint !== null) {
  206. // Building a segment with first point already fixed
  207. if (this._verticalHorizontal) {
  208. // Set vertical or horizontal segment
  209. currentPoint = this._verticalHorizontalSecondPoint(currentPoint);
  210. }
  211. if (!this._segmentFirstPoint.isEquals(currentPoint)) {
  212. // It's a real segment (with 2 distinct points)
  213. temporarySegment = new Segment(this._segmentFirstPoint, currentPoint);
  214. }
  215. }
  216. // Update the coordinates of the current point
  217. this._globalView.updateInfosCurrentPoint(currentPoint);
  218. // Update the temporary part of the view
  219. this.view.update({"updatePermanent": false,
  220. "currentPoint": currentPoint,
  221. "nearestPoint": nearestPoint,
  222. "temporarySegment": temporarySegment});
  223. }
  224. /**
  225. * If there exist an point closed enough to point
  226. * then return [this closed point, this close point],
  227. * else return [point, null].
  228. *
  229. * @param {Point} point
  230. *
  231. * @returns {Array} [Point, null or Point]
  232. */
  233. _nearestPointIfExist(point) {
  234. const nearestPoint = this.matching.nearestPoint(point);
  235. if (nearestPoint !== null) {
  236. // there exist a point closed enough so it become the current point
  237. point = nearestPoint;
  238. }
  239. return [point, nearestPoint];
  240. }
  241. /**
  242. * Reset the first point used to build segment
  243. * and update the view.
  244. */
  245. _out() {
  246. this.clearSegmentFirstPoint();
  247. this._globalView.updateInfosCurrentPoint();
  248. this.view.update({"updatePermanent": false});
  249. }
  250. /**
  251. * Set listeners on the view.
  252. */
  253. _setEventListeners() {
  254. const controller = this;
  255. // Add/remove point or segment
  256. this.view.canvasContainer.addEventListener("click",
  257. function (event) { controller._click(event); },
  258. false);
  259. // Move pointer or temporary segment
  260. this.view.canvasContainer.addEventListener("mousemove",
  261. function (event) { controller._move(event); },
  262. false);
  263. // Out canvas
  264. this.view.canvasContainer.addEventListener("mouseout",
  265. function () { controller._out(); },
  266. false);
  267. }
  268. /**
  269. * If point is in a vertical or horizontal position compared to _segmentFirstPoint
  270. * then return point,
  271. * else return a new point in the nearest good vertical or horizontal position.
  272. *
  273. * @param {Point} point
  274. *
  275. * @returns {Point}
  276. */
  277. _verticalHorizontalSecondPoint(point) {
  278. assert(point instanceof Point, point);
  279. assert(this._segmentFirstPoint instanceof Point, this._segmentFirstPoint);
  280. const horizontalPoint = new Point(point.x, this._segmentFirstPoint.y);
  281. const verticalPoint = new Point(this._segmentFirstPoint.x, point.y);
  282. const newPoint = (point.distanceSqr(horizontalPoint) <= point.distanceSqr(verticalPoint)
  283. ? horizontalPoint
  284. : verticalPoint);
  285. return (point.isEquals(newPoint)
  286. ? point
  287. : newPoint);
  288. }
  289. }