Source: controller/GlobalController.js

  1. /**
  2. * @file Class GlobalController
  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 global controls of all matchings and views.
  10. */
  11. class GlobalController {
  12. /**
  13. * Construct a controller for the global view
  14. * and link to two controllers of matching.
  15. *
  16. * @param {GlobalView} globalView
  17. * @param {MatchingController} leftMatchingController
  18. * @param {MatchingController} rightMatchingController
  19. */
  20. constructor(globalView, leftMatchingController, rightMatchingController) {
  21. assert(globalView instanceof GlobalView, globalView);
  22. assert(leftMatchingController instanceof MatchingController, leftMatchingController);
  23. assert(rightMatchingController instanceof MatchingController, rightMatchingController);
  24. this._globalView = globalView;
  25. this._leftController = leftMatchingController;
  26. this._rightController = rightMatchingController;
  27. // Set values from HTML elements
  28. this._changeAction();
  29. this._changeVerticalHorizontal();
  30. this._setEventListeners();
  31. }
  32. /**
  33. * Returns the global view.
  34. */
  35. get globalView() { return this._globalView; }
  36. /**
  37. * Returns the left controller.
  38. */
  39. get leftController() { return this._leftController; }
  40. /**
  41. * Returns the right controller.
  42. */
  43. get rightController() { return this._rightController; }
  44. /**
  45. * Fill the list of matchings with all possible perfect matchings
  46. * with the set of points of the left matching.
  47. *
  48. * If disjointMatching
  49. * then keep only perfect matching disjoint with this matching.
  50. *
  51. * If compatibleMatching
  52. * then keep only perfect matching disjoint with this matching.
  53. *
  54. * @param {Boolean} disjointMatching
  55. * @param {Boolean} compatibleMatching
  56. */
  57. _buildPerfectMatchings(disjointMatching, compatibleMatching) {
  58. assert(typeof disjointMatching === "boolean", disjointMatching);
  59. assert(typeof compatibleMatching === "boolean", compatibleMatching);
  60. const thisGlobalController = this;
  61. this._runWithWaitMessage(function () {
  62. thisGlobalController._leftController.matching.setPerfectMatchings(disjointMatching, compatibleMatching);
  63. thisGlobalController._globalView.update();
  64. });
  65. }
  66. /**
  67. * Fill the list of matchings with a shortest transformation
  68. * between left and right matchings.
  69. *
  70. * @param {Boolean} disjointTransformation
  71. */
  72. _buildTransformation(disjointTransformation) {
  73. assert(typeof disjointTransformation === "boolean", disjointTransformation);
  74. const thisGlobalController = this;
  75. this._runWithWaitMessage(function () {
  76. const found = thisGlobalController._leftController.matching.setTransformation(disjointTransformation);
  77. thisGlobalController._globalView.update();
  78. if (!found) {
  79. thisGlobalController._warningMessage("Not found!");
  80. }
  81. });
  82. }
  83. /**
  84. * Set action property for two controllers of matching.
  85. */
  86. _changeAction() {
  87. this._leftController.changeAction();
  88. this._rightController.changeAction();
  89. }
  90. /**
  91. * Set drawSegment property for two controllers of matching.
  92. */
  93. _changeDrawSegments() {
  94. this._leftController.changeDrawSegments();
  95. this._rightController.changeDrawSegments();
  96. this._globalView.update(null, false);
  97. }
  98. /**
  99. * Set vertical-horizontal property for two controllers of matching.
  100. */
  101. _changeVerticalHorizontal() {
  102. this._leftController.changeVerticalHorizontal();
  103. this._rightController.changeVerticalHorizontal();
  104. }
  105. /**
  106. * Reset the two controllers.
  107. */
  108. _clear() {
  109. this._leftController.matching.clearIntermediaryLinkedMatchings();
  110. this._leftController.clear();
  111. this._rightController.clear();
  112. }
  113. /**
  114. * Reset the list of matchings.
  115. */
  116. _clearTransformation() {
  117. this._leftController.matching.clearIntermediaryLinkedMatchings();
  118. this._globalView.update();
  119. }
  120. /**
  121. * Load matchings from a text format
  122. * (in the format such that returns by Matching.matchingsToString())
  123. * and update the global view.
  124. *
  125. * @param {String} text
  126. */
  127. _load(text) {
  128. assert(typeof text === "string", text);
  129. this._clear();
  130. const lines = text.split(/\D+/); // split for each number (ignore other stuffs)
  131. // Read points
  132. const numberPoints = parseInt(lines.shift(), 10);
  133. for (let i = 0; i < numberPoints; ++i) {
  134. const x = parseInt(lines.shift(), 10);
  135. const y = parseInt(lines.shift(), 10);
  136. this._leftController.matching.pointAdd(new Point(x, y));
  137. }
  138. // Read matchings
  139. const numberMatchings = parseInt(lines.shift(), 10);
  140. const linkedMatchings = [this._leftController.matching];
  141. for (let i = 1; i < numberMatchings - 1; ++i) {
  142. linkedMatchings.push(new Matching());
  143. }
  144. linkedMatchings.push(this._rightController.matching);
  145. for (let i = 0; i < numberMatchings; ++i) {
  146. // Read segments
  147. const numberSegments = parseInt(lines.shift(), 10);
  148. for (let j = 0; j < numberSegments; ++j) {
  149. const indexA = parseInt(lines.shift(), 10);
  150. const indexB = parseInt(lines.shift(), 10);
  151. linkedMatchings[i].segmentAdd(new Segment(this._leftController.matching.points[indexA], this._leftController.matching.points[indexB]));
  152. }
  153. }
  154. for (let linkedMatching of linkedMatchings) {
  155. linkedMatching._linkedMatchings = linkedMatchings;
  156. }
  157. this._globalView.update();
  158. }
  159. /**
  160. * Load matchings from a local text file
  161. * (in the format such that returns by Matching.matchingsToString())
  162. * and update the global view.
  163. *
  164. * @param {Event} event
  165. */
  166. _loadFile(event) {
  167. assert(event instanceof Event, event);
  168. const file = document.getElementById("button-load").files[0];
  169. const fileReader = new FileReader();
  170. const controller = this;
  171. fileReader.onload = function(event) {
  172. controller._load(event.target.result.trim());
  173. };
  174. fileReader.readAsText(file);
  175. }
  176. /**
  177. * Load matchings from a text file
  178. * (in the format such that returns by Matching.matchingsToString())
  179. * and update the global view.
  180. *
  181. * @param {String} url
  182. */
  183. _loadUrl(url) {
  184. assert(typeof url === "string", url);
  185. var xhr = new XMLHttpRequest();
  186. xhr.open("GET", url, true);
  187. xhr.setRequestHeader("Content-Type", "plain/text");
  188. const controller = this;
  189. xhr.onload = function() {
  190. if ((xhr.readyState !== 4) || (xhr.status !== 200)) {
  191. return;
  192. }
  193. controller._load(xhr.responseText);
  194. location.hash = "3-Experiment-by-yourself-1-Interactive-application";
  195. };
  196. xhr.send(null);
  197. }
  198. /**
  199. * Display a waiting message,
  200. * run the function
  201. * and then remove the waiting message.
  202. *
  203. * @param {function} func
  204. */
  205. _runWithWaitMessage(func) {
  206. assert(typeof func === "function", func);
  207. this._waitShow();
  208. const thisGlobalController = this;
  209. window.setTimeout(function () {
  210. func();
  211. window.setTimeout(thisGlobalController._waitHide, 100);
  212. }, 100);
  213. }
  214. /**
  215. * Save all matchings in a text file named "matchings.txt"
  216. * (in the format returns by Matching.matchingsToString()).
  217. *
  218. * Warning! Only integers are valid.
  219. */
  220. _saveToFile() {
  221. // Create content
  222. const text = this._leftController.matching.matchingsToString();
  223. const blob = new Blob([text], {type:"text/plain"});
  224. // Create a link to content
  225. const itemA = document.createElement("a");
  226. itemA.download = "matchings.txt";
  227. itemA.href = window.URL.createObjectURL(blob);
  228. itemA.onclick = function (event) { document.body.removeChild(event.target); };
  229. itemA.style.display = "none";
  230. document.body.appendChild(itemA);
  231. // Click to the link
  232. itemA.click();
  233. }
  234. /**
  235. * Swap segments between the left and right matchings.
  236. */
  237. _swap() {
  238. this._leftController.matching.clearIntermediaryLinkedMatchings();
  239. const tmp = this._leftController.matching._segments;
  240. this._leftController.matching._segments = this._rightController.matching._segments;
  241. this._rightController.matching._segments = tmp;
  242. this._globalView.update();
  243. }
  244. /**
  245. * Set listeners on the view.
  246. */
  247. _setEventListeners() {
  248. const controller = this;
  249. // Set canonical to left matching
  250. const canonicalLeft = function (event) {
  251. controller._leftController.matching.clearIntermediaryLinkedMatchings();
  252. controller._leftController.matching.setCanonical();
  253. controller._globalView.update();
  254. };
  255. document.getElementById("button-canonical-left").addEventListener("click", canonicalLeft, false);
  256. // Set canonical to right matching
  257. const canonicalRight = function (event) {
  258. controller._leftController.matching.clearIntermediaryLinkedMatchings();
  259. controller._rightController.matching.setCanonical();
  260. controller._globalView.update();
  261. };
  262. document.getElementById("button-canonical-right").addEventListener("click", canonicalRight, false);
  263. // Shuffle segments in left matching
  264. const shuffleLeft = function (event) {
  265. controller._leftController.matching.clearIntermediaryLinkedMatchings();
  266. controller._leftController.matching.shuffleSegments();
  267. controller._globalView.update();
  268. };
  269. document.getElementById("button-shuffle-left").addEventListener("click", shuffleLeft, false);
  270. // Shuffle segments in right matching
  271. const shuffleRight = function (event) {
  272. controller._leftController.matching.clearIntermediaryLinkedMatchings();
  273. controller._rightController.matching.shuffleSegments();
  274. controller._globalView.update();
  275. };
  276. document.getElementById("button-shuffle-right").addEventListener("click", shuffleRight, false);
  277. // Build list perfect matchings
  278. document.getElementById("button-build-list-perfect-matchings").addEventListener("click",
  279. function () { controller._buildPerfectMatchings(false, false); },
  280. false);
  281. // Build list disjoint perfect matchings
  282. document.getElementById("button-build-list-disjoint-perfect-matchings").addEventListener("click",
  283. function () { controller._buildPerfectMatchings(true, false); },
  284. false);
  285. // Build list compatible perfect matchings
  286. document.getElementById("button-build-list-compatible-perfect-matchings").addEventListener("click",
  287. function () { controller._buildPerfectMatchings(false, true); },
  288. false);
  289. // Build list disjoint compatible perfect matchings
  290. document.getElementById("button-build-list-disjoint-compatible-perfect-matchings").addEventListener("click",
  291. function () { controller._buildPerfectMatchings(true, true); },
  292. false);
  293. // Build list transformation
  294. document.getElementById("button-build-list-transformation").addEventListener("click",
  295. function () { controller._buildTransformation(false); },
  296. false);
  297. // Build list disjoint transformation
  298. document.getElementById("button-build-list-disjoint-transformation").addEventListener("click",
  299. function () { controller._buildTransformation(true); },
  300. false);
  301. // Clear list matchings
  302. document.getElementById("button-clear-list-matchings").addEventListener("click",
  303. function () { controller._clearTransformation(); },
  304. false);
  305. // Clear
  306. document.getElementById("button-clear").addEventListener("click",
  307. function () { controller._clear(); },
  308. false);
  309. // Load from file
  310. document.getElementById("button-load").addEventListener("change",
  311. function (event) { controller._loadFile(event); },
  312. false);
  313. this._setEventListenersLoadExample();
  314. // Save to file
  315. document.getElementById("button-save").addEventListener("click",
  316. function () { controller._saveToFile(); },
  317. false);
  318. // Swap
  319. document.getElementById("button-swap").addEventListener("click",
  320. function () { controller._swap(); },
  321. false);
  322. // Change action mode
  323. document.getElementById("radio-action-point").addEventListener("change",
  324. function () { controller._changeAction(); },
  325. false);
  326. document.getElementById("radio-action-segment").addEventListener("change",
  327. function () { controller._changeAction(); },
  328. false);
  329. // Change draw segments mode
  330. document.getElementById("radio-draw-segments-only").addEventListener("change",
  331. function () { controller._changeDrawSegments(); },
  332. false);
  333. document.getElementById("radio-draw-segments-consecutive").addEventListener("change",
  334. function () { controller._changeDrawSegments(); }, false);
  335. document.getElementById("radio-draw-segments-all").addEventListener("change",
  336. function () { controller._changeDrawSegments(); },
  337. false);
  338. // Change vertical-horizontal mode
  339. document.getElementById("checkbox-vertical-horizontal").addEventListener("change",
  340. function () { controller._changeVerticalHorizontal(); },
  341. false);
  342. // Resize the container of list matchings
  343. window.addEventListener("resize",
  344. function () { controller.globalView.resizeListMatchings(); },
  345. false);
  346. }
  347. /**
  348. * Set listeners on the view to load examples.
  349. */
  350. _setEventListenersLoadExample() {
  351. const controller = this;
  352. const imgs = document.getElementsByTagName('img');
  353. for (let img of imgs) {
  354. const id = img.id;
  355. if (id) {
  356. const matches = /^(.+)-example$/.exec(id);
  357. const name = matches[1];
  358. if (name) {
  359. img.title = "Click to this picture to load it to the two interactive zones.";
  360. img.addEventListener("click",
  361. function (event) { controller._loadUrl("public/examples/" + name + ".txt"); },
  362. false);
  363. }
  364. }
  365. }
  366. }
  367. /**
  368. * Hide the waiting message.
  369. */
  370. _waitHide() {
  371. document.getElementById("wait-container").style.display = "none";
  372. }
  373. /**
  374. * Show the waiting message.
  375. */
  376. _waitShow() {
  377. document.getElementById("wait-container").style.display = "flex";
  378. }
  379. /**
  380. * Show temporary the warning message.
  381. */
  382. _warningMessage(message) {
  383. assert(typeof message === "string", message);
  384. const container = document.getElementById("warning-container");
  385. container.innerHTML = message;
  386. container.style.display = "flex";
  387. window.setTimeout(function () {
  388. container.style.display = "none";
  389. }, 2000);
  390. }
  391. }