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.

hand.js 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527
  1. /**
  2. * WHITEBOPHIR
  3. *********************************************************
  4. * @licstart The following is the entire license notice for the
  5. * JavaScript code in this page.
  6. *
  7. * Copyright (C) 2013 Ophir LOJKINE
  8. *
  9. *
  10. * The JavaScript code in this page is free software: you can
  11. * redistribute it and/or modify it under the terms of the GNU
  12. * General Public License (GNU GPL) as published by the Free Software
  13. * Foundation, either version 3 of the License, or (at your option)
  14. * any later version. The code is distributed WITHOUT ANY WARRANTY;
  15. * without even the implied warranty of MERCHANTABILITY or FITNESS
  16. * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details.
  17. *
  18. * As additional permission under GNU GPL version 3 section 7, you
  19. * may distribute non-source (e.g., minimized or compacted) forms of
  20. * that code without the copy of the GNU GPL normally required by
  21. * section 4, provided you include this license notice and a URL
  22. * through which recipients can access the Corresponding Source.
  23. *
  24. * @licend
  25. */
  26. (function hand() { //Code isolation
  27. var selectorStates = {
  28. pointing: 0,
  29. selecting: 1,
  30. transform: 2
  31. }
  32. var selected = null;
  33. var selected_els = [];
  34. var selectionRect = createSelectorRect();
  35. var selectionRectTransform;
  36. var currentTransform = null;
  37. var transform_elements = [];
  38. var selectorState = selectorStates.pointing;
  39. var last_sent = 0;
  40. var blockedSelectionButtons = Tools.server_config.BLOCKED_SELECTION_BUTTONS;
  41. var selectionButtons = {};
  42. selectionButtons["delete"] = createButton("delete", "delete", 22, 22,
  43. function(me, bbox, s) {
  44. me.width.baseVal.value = me.origWidth / s;
  45. me.height.baseVal.value = me.origHeight / s;
  46. me.x.baseVal.value = bbox.r[0];
  47. me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s;
  48. me.style.display = "";
  49. },
  50. deleteSelection);
  51. selectionButtons["duplicate"] = createButton("duplicate", "duplicate", 22, 22,
  52. function(me, bbox, s) {
  53. me.width.baseVal.value = me.origWidth / s;
  54. me.height.baseVal.value = me.origHeight / s;
  55. me.x.baseVal.value = bbox.r[0] + (me.origWidth + 2) / s;
  56. me.y.baseVal.value = bbox.r[1] - (me.origHeight + 3) / s;
  57. me.style.display = "";
  58. },
  59. duplicateSelection);
  60. selectionButtons["scale"] = createButton("scaleHandle", "handle", 14, 14,
  61. function(me, bbox, s) {
  62. me.width.baseVal.value = me.origWidth / s;
  63. me.height.baseVal.value = me.origHeight / s;
  64. me.x.baseVal.value = bbox.r[0] + bbox.a[0] - me.origWidth/(2*s);
  65. me.y.baseVal.value = bbox.r[1] + bbox.b[1] - me.origHeight/(2*s);
  66. me.style.display = "";
  67. },
  68. startScalingTransform);
  69. for (i in blockedSelectionButtons) {
  70. delete selectionButtons[blockedSelectionButtons[i]];
  71. }
  72. selectionButtons = Object.keys(selectionButtons).map(function(k) {
  73. return selectionButtons[k];
  74. });
  75. var getScale = Tools.getScale;
  76. function getParentMathematics(el) {
  77. var target;
  78. var a = el;
  79. var els = [];
  80. while (a) {
  81. els.unshift(a);
  82. a = a.parentElement;
  83. }
  84. var parentMathematics = els.find(function(el) {
  85. return el.getAttribute("class") === "MathElement";
  86. });
  87. if ((parentMathematics) && parentMathematics.tagName === "svg") {
  88. target = parentMathematics;
  89. }
  90. return target || el;
  91. }
  92. function deleteSelection() {
  93. var msgs = selected_els.map(function(el) {
  94. return ({
  95. "type": "delete",
  96. "id": el.id
  97. });
  98. });
  99. var data = {
  100. _children: msgs
  101. }
  102. Tools.drawAndSend(data);
  103. selected_els = [];
  104. hideSelectionUI();
  105. }
  106. function duplicateSelection() {
  107. if (!(selectorState == selectorStates.pointing)
  108. || (selected_els.length == 0)) return;
  109. var msgs = [];
  110. var newids = [];
  111. for (var i=0; i<selected_els.length; i++) {
  112. var id = selected_els[i].id;
  113. msgs[i] = {
  114. type: "copy",
  115. id: id,
  116. newid: Tools.generateUID(id[0])
  117. };
  118. newids[i] = id;
  119. }
  120. Tools.drawAndSend({_children: msgs});
  121. selected_els = newids.map(function(id) {
  122. return Tools.svg.getElementById(id);
  123. });
  124. }
  125. function createSelectorRect() {
  126. var shape = Tools.createSVGElement("rect");
  127. shape.id = "selectionRect";
  128. shape.x.baseVal.value = 0;
  129. shape.y.baseVal.value = 0;
  130. shape.width.baseVal.value = 0;
  131. shape.height.baseVal.value = 0;
  132. shape.setAttribute("stroke", "black");
  133. shape.setAttribute("stroke-width", 1);
  134. shape.setAttribute("vector-effect", "non-scaling-stroke");
  135. shape.setAttribute("fill", "none");
  136. shape.setAttribute("stroke-dasharray", "5 5");
  137. shape.setAttribute("opacity", 1);
  138. Tools.svg.appendChild(shape);
  139. return shape;
  140. }
  141. function createButton(name, icon , width, height, drawCallback, clickCallback) {
  142. var shape = Tools.createSVGElement("image", {
  143. id: name + "Icon",
  144. href: "tools/hand/" + icon + ".svg",
  145. width: width,
  146. height: height,
  147. });
  148. shape.style.display = "none";
  149. shape.origWidth = width;
  150. shape.origHeight = height;
  151. shape.drawCallback = drawCallback;
  152. shape.clickCallback = clickCallback;
  153. Tools.svg.appendChild(shape);
  154. return shape;
  155. }
  156. function showSelectionButtons() {
  157. var scale = getScale();
  158. var selectionBBox = selectionRect.transformedBBox();
  159. for (var i = 0; i < selectionButtons.length; i++) {
  160. selectionButtons[i].drawCallback(selectionButtons[i],
  161. selectionBBox,
  162. scale);
  163. }
  164. }
  165. function hideSelectionButtons() {
  166. for (var i = 0; i < selectionButtons.length; i++) {
  167. selectionButtons[i].style.display = "none";
  168. }
  169. }
  170. function hideSelectionUI() {
  171. hideSelectionButtons();
  172. selectionRect.style.display = "none";
  173. }
  174. function startMovingElements(x, y, evt) {
  175. evt.preventDefault();
  176. selectorState = selectorStates.transform;
  177. currentTransform = moveSelection;
  178. selected = { x: x, y: y };
  179. // Some of the selected elements could have been deleted
  180. selected_els = selected_els.filter(function(el) {
  181. return Tools.svg.getElementById(el.id) !== null;
  182. });
  183. transform_elements = selected_els.map(function(el) {
  184. var tmatrix = get_transform_matrix(el);
  185. return {
  186. a: tmatrix.a, b: tmatrix.b, c: tmatrix.c,
  187. d: tmatrix.d, e: tmatrix.e, f: tmatrix.f
  188. };
  189. });
  190. var tmatrix = get_transform_matrix(selectionRect);
  191. selectionRectTransform = { x: tmatrix.e, y: tmatrix.f };
  192. }
  193. function startScalingTransform(x, y, evt) {
  194. evt.preventDefault();
  195. hideSelectionButtons();
  196. selectorState = selectorStates.transform;
  197. var bbox = selectionRect.transformedBBox();
  198. selected = {
  199. x: bbox.r[0],
  200. y: bbox.r[1],
  201. w: bbox.a[0],
  202. h: bbox.b[1],
  203. };
  204. transform_elements = selected_els.map(function(el) {
  205. var tmatrix = get_transform_matrix(el);
  206. return {
  207. a: tmatrix.a, b: tmatrix.b, c: tmatrix.c,
  208. d: tmatrix.d, e: tmatrix.e, f: tmatrix.f
  209. };
  210. });
  211. var tmatrix = get_transform_matrix(selectionRect);
  212. selectionRectTransform = {
  213. a: tmatrix.a, d: tmatrix.d,
  214. e: tmatrix.e, f: tmatrix.f
  215. };
  216. currentTransform = scaleSelection;
  217. }
  218. function startSelector(x, y, evt) {
  219. evt.preventDefault();
  220. selected = { x: x, y: y };
  221. selected_els = [];
  222. selectorState = selectorStates.selecting;
  223. selectionRect.x.baseVal.value = x;
  224. selectionRect.y.baseVal.value = y;
  225. selectionRect.width.baseVal.value = 0;
  226. selectionRect.height.baseVal.value = 0;
  227. selectionRect.style.display = "";
  228. tmatrix = get_transform_matrix(selectionRect);
  229. tmatrix.e = 0;
  230. tmatrix.f = 0;
  231. }
  232. function calculateSelection() {
  233. var selectionTBBox = selectionRect.transformedBBox();
  234. var elements = Tools.drawingArea.children;
  235. var selected = [];
  236. for (var i=0; i < elements.length; i++) {
  237. if (transformedBBoxIntersects(selectionTBBox, elements[i].transformedBBox()))
  238. selected.push(Tools.drawingArea.children[i]);
  239. }
  240. return selected;
  241. }
  242. function moveSelection(x, y) {
  243. var dx = x - selected.x;
  244. var dy = y - selected.y;
  245. var msgs = selected_els.map(function(el, i) {
  246. var oldTransform = transform_elements[i];
  247. return {
  248. type: "update",
  249. id: el.id,
  250. transform: {
  251. a: oldTransform.a,
  252. b: oldTransform.b,
  253. c: oldTransform.c,
  254. d: oldTransform.d,
  255. e: dx + oldTransform.e,
  256. f: dy + oldTransform.f
  257. }
  258. };
  259. })
  260. var msg = {
  261. _children: msgs
  262. };
  263. var tmatrix = get_transform_matrix(selectionRect);
  264. tmatrix.e = dx + selectionRectTransform.x;
  265. tmatrix.f = dy + selectionRectTransform.y;
  266. var now = performance.now();
  267. if (now - last_sent > 70) {
  268. last_sent = now;
  269. Tools.drawAndSend(msg);
  270. } else {
  271. draw(msg);
  272. }
  273. }
  274. function scaleSelection(x, y) {
  275. var rx = (x - selected.x)/(selected.w);
  276. var ry = (y - selected.y)/(selected.h);
  277. var msgs = selected_els.map(function(el, i) {
  278. var oldTransform = transform_elements[i];
  279. var x = el.transformedBBox().r[0];
  280. var y = el.transformedBBox().r[1];
  281. var a = oldTransform.a * rx;
  282. var d = oldTransform.d * ry;
  283. var e = selected.x * (1 - rx) - x * a +
  284. (x * oldTransform.a + oldTransform.e) * rx
  285. var f = selected.y * (1 - ry) - y * d +
  286. (y * oldTransform.d + oldTransform.f) * ry
  287. return {
  288. type: "update",
  289. id: el.id,
  290. transform: {
  291. a: a,
  292. b: oldTransform.b,
  293. c: oldTransform.c,
  294. d: d,
  295. e: e,
  296. f: f
  297. }
  298. };
  299. })
  300. var msg = {
  301. _children: msgs
  302. };
  303. var tmatrix = get_transform_matrix(selectionRect);
  304. tmatrix.a = rx;
  305. tmatrix.d = ry;
  306. tmatrix.e = selectionRectTransform.e +
  307. selectionRect.x.baseVal.value * (selectionRectTransform.a - rx)
  308. tmatrix.f = selectionRectTransform.f +
  309. selectionRect.y.baseVal.value * (selectionRectTransform.d - ry)
  310. var now = performance.now();
  311. if (now - last_sent > 70) {
  312. last_sent = now;
  313. Tools.drawAndSend(msg);
  314. } else {
  315. draw(msg);
  316. }
  317. }
  318. function updateRect(x, y, rect) {
  319. rect.x.baseVal.value = Math.min(x, selected.x);
  320. rect.y.baseVal.value = Math.min(y, selected.y);
  321. rect.width.baseVal.value = Math.abs(x - selected.x);
  322. rect.height.baseVal.value = Math.abs(y - selected.y);
  323. }
  324. function resetSelectionRect() {
  325. var bbox = selectionRect.transformedBBox();
  326. var tmatrix = get_transform_matrix(selectionRect);
  327. selectionRect.x.baseVal.value = bbox.r[0];
  328. selectionRect.y.baseVal.value = bbox.r[1];
  329. selectionRect.width.baseVal.value = bbox.a[0];
  330. selectionRect.height.baseVal.value = bbox.b[1];
  331. tmatrix.a = 1; tmatrix.b = 0; tmatrix.c = 0;
  332. tmatrix.d = 1; tmatrix.e = 0; tmatrix.f = 0;
  333. }
  334. function get_transform_matrix(elem) {
  335. // Returns the first translate or transform matrix or makes one
  336. var transform = null;
  337. for (var i = 0; i < elem.transform.baseVal.numberOfItems; ++i) {
  338. var baseVal = elem.transform.baseVal[i];
  339. // quick tests showed that even if one changes only the fields e and f or uses createSVGTransformFromMatrix
  340. // the brower may add a SVG_TRANSFORM_MATRIX instead of a SVG_TRANSFORM_TRANSLATE
  341. if (baseVal.type === SVGTransform.SVG_TRANSFORM_MATRIX) {
  342. transform = baseVal;
  343. break;
  344. }
  345. }
  346. if (transform == null) {
  347. transform = elem.transform.baseVal.createSVGTransformFromMatrix(Tools.svg.createSVGMatrix());
  348. elem.transform.baseVal.appendItem(transform);
  349. }
  350. return transform.matrix;
  351. }
  352. function draw(data) {
  353. if (data._children) {
  354. batchCall(draw, data._children);
  355. }
  356. else {
  357. switch (data.type) {
  358. case "update":
  359. var elem = Tools.svg.getElementById(data.id);
  360. if (!elem) throw new Error("Mover: Tried to move an element that does not exist.");
  361. var tmatrix = get_transform_matrix(elem);
  362. for (i in data.transform) {
  363. tmatrix[i] = data.transform[i]
  364. }
  365. break;
  366. case "copy":
  367. var newElement = Tools.svg.getElementById(data.id).cloneNode(true);
  368. newElement.id = data.newid;
  369. Tools.drawingArea.appendChild(newElement);
  370. break;
  371. case "delete":
  372. data.tool = "Eraser";
  373. messageForTool(data);
  374. break;
  375. default:
  376. throw new Error("Mover: 'move' instruction with unknown type. ", data);
  377. }
  378. }
  379. }
  380. function clickSelector(x, y, evt) {
  381. var scale = getScale();
  382. selectionRect = selectionRect || createSelectorRect();
  383. var button = null;
  384. for (var i=0; i<selectionButtons.length; i++) {
  385. if (selectionButtons[i].contains(evt.target)) {
  386. button = selectionButtons[i];
  387. }
  388. }
  389. if (button) {
  390. button.clickCallback(x, y, evt);
  391. } else if (pointInTransformedBBox([x, y], selectionRect.transformedBBox())) {
  392. hideSelectionButtons();
  393. startMovingElements(x, y, evt);
  394. } else if (Tools.drawingArea.contains(evt.target)) {
  395. hideSelectionUI();
  396. selected_els = [getParentMathematics(evt.target)];
  397. startMovingElements(x, y, evt);
  398. } else {
  399. hideSelectionButtons();
  400. startSelector(x, y, evt);
  401. }
  402. }
  403. function releaseSelector(x, y, evt) {
  404. if (selectorState == selectorStates.selecting) {
  405. selected_els = calculateSelection();
  406. if (selected_els.length == 0) {
  407. hideSelectionUI();
  408. }
  409. } else if (selectorState == selectorStates.transform)
  410. resetSelectionRect();
  411. if (selected_els.length != 0) showSelectionButtons();
  412. transform_elements = [];
  413. selectorState = selectorStates.pointing;
  414. }
  415. function moveSelector(x, y, evt) {
  416. if (selectorState == selectorStates.selecting) {
  417. updateRect(x, y, selectionRect);
  418. } else if (selectorState == selectorStates.transform && currentTransform) {
  419. currentTransform(x, y);
  420. }
  421. }
  422. function startHand(x, y, evt, isTouchEvent) {
  423. if (!isTouchEvent) {
  424. selected = {
  425. x: document.documentElement.scrollLeft + evt.clientX,
  426. y: document.documentElement.scrollTop + evt.clientY,
  427. }
  428. }
  429. }
  430. function moveHand(x, y, evt, isTouchEvent) {
  431. if (selected && !isTouchEvent) { //Let the browser handle touch to scroll
  432. window.scrollTo(selected.x - evt.clientX, selected.y - evt.clientY);
  433. }
  434. }
  435. function press(x, y, evt, isTouchEvent) {
  436. if (!handTool.secondary.active) startHand(x, y, evt, isTouchEvent);
  437. else clickSelector(x, y, evt, isTouchEvent);
  438. }
  439. function move(x, y, evt, isTouchEvent) {
  440. if (!handTool.secondary.active) moveHand(x, y, evt, isTouchEvent);
  441. else moveSelector(x, y, evt, isTouchEvent);
  442. }
  443. function release(x, y, evt, isTouchEvent) {
  444. move(x, y, evt, isTouchEvent);
  445. if (handTool.secondary.active) releaseSelector(x, y, evt, isTouchEvent);
  446. selected = null;
  447. }
  448. function deleteShortcut(e) {
  449. if (e.key == "Delete" &&
  450. !e.target.matches("input[type=text], textarea"))
  451. deleteSelection();
  452. }
  453. function duplicateShortcut(e) {
  454. if (e.key == "d" &&
  455. !e.target.matches("input[type=text], textarea"))
  456. duplicateSelection();
  457. }
  458. function switchTool() {
  459. selected = null;
  460. if (handTool.secondary.active) {
  461. window.addEventListener("keydown", deleteShortcut);
  462. window.addEventListener("keydown", duplicateShortcut);
  463. }
  464. else {
  465. window.removeEventListener("keydown", deleteShortcut);
  466. window.removeEventListener("keydown", duplicateShortcut);
  467. }
  468. }
  469. var handTool = { //The new tool
  470. "name": "Hand",
  471. "shortcut": "h",
  472. "listeners": {
  473. "press": press,
  474. "move": move,
  475. "release": release,
  476. },
  477. "secondary": {
  478. "name": "Selector",
  479. "icon": "tools/hand/selector.svg",
  480. "active": false,
  481. "switch": switchTool,
  482. },
  483. "draw": draw,
  484. "icon": "tools/hand/hand.svg",
  485. "mouseCursor": "move",
  486. "showMarker": true,
  487. };
  488. Tools.add(handTool);
  489. Tools.change("Hand"); // Use the hand tool by default
  490. })(); //End of code isolation