DrawPolyline.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535
  1. /**
  2. * Copyright (c) 2011-2018 by Andrew Mustun. All rights reserved.
  3. *
  4. * Various changes added 2014 by Robert S.
  5. *
  6. * This file is part of the QCAD project.
  7. *
  8. * QCAD is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * QCAD is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with QCAD.
  20. */
  21. include("../Polyline.js");
  22. include("scripts/Snap/RestrictAngleLength/RestrictAngleLength.js");
  23. /**
  24. * \class DrawPolyline
  25. * \brief Draw polyline, segment by segment.
  26. * This is also the base class for PolylineAppendNode.
  27. * \ingroup ecma_draw_polyline
  28. */
  29. function DrawPolyline(guiAction) {
  30. Polyline.call(this, guiAction);
  31. this.polylineEntity = undefined;
  32. this.arcSegment = false;
  33. this.radius = 1.0;
  34. this.prepend = false;
  35. this.segment = undefined;
  36. this.redoList = [];
  37. this.sweep = 0.0;
  38. this.setUiOptions(DrawPolyline.includeBasePath + "/DrawPolyline.ui");
  39. }
  40. DrawPolyline.prototype = new Polyline();
  41. DrawPolyline.includeBasePath = includeBasePath;
  42. DrawPolyline.State = {
  43. SettingFirstVertex : 0,
  44. SettingNextVertex : 1
  45. };
  46. DrawPolyline.prototype.beginEvent = function() {
  47. Polyline.prototype.beginEvent.call(this);
  48. this.setState(DrawPolyline.State.SettingFirstVertex);
  49. this.updateButtonStates();
  50. };
  51. DrawPolyline.prototype.initState = function(state) {
  52. Polyline.prototype.initState.call(this, state);
  53. this.setCrosshairCursor();
  54. switch (this.state) {
  55. case DrawPolyline.State.SettingFirstVertex:
  56. // do not move this outside of the case, since inheriting actions
  57. // might add more states:
  58. this.getDocumentInterface().setClickMode(RAction.PickCoordinate);
  59. var trFirstVertex = qsTr("First vertex");
  60. this.setCommandPrompt(trFirstVertex);
  61. this.setLeftMouseTip(trFirstVertex);
  62. this.setRightMouseTip(EAction.trCancel);
  63. this.segment = undefined;
  64. EAction.showSnapTools();
  65. this.redoList = [];
  66. break;
  67. case DrawPolyline.State.SettingNextVertex:
  68. this.getDocumentInterface().setClickMode(RAction.PickCoordinate);
  69. var trNextVertex = qsTr("Next vertex");
  70. this.setCommandPrompt(trNextVertex);
  71. this.setLeftMouseTip(trNextVertex);
  72. this.setRightMouseTip(EAction.trDone);
  73. EAction.showSnapTools();
  74. break;
  75. }
  76. };
  77. DrawPolyline.prototype.escapeEvent = function() {
  78. var di = this.getDocumentInterface();
  79. switch (this.state) {
  80. case DrawPolyline.State.SettingFirstVertex:
  81. EAction.prototype.escapeEvent.call(this);
  82. break;
  83. case DrawPolyline.State.SettingNextVertex:
  84. if (!isNull(this.polylineEntity)) {
  85. // remove polyline with one or zero vertices:
  86. if (this.polylineEntity.countVertices()<=1) {
  87. var op = new RDeleteObjectOperation(this.polylineEntity, false);
  88. this.applyOperation(op);
  89. }
  90. }
  91. this.setState(DrawPolyline.State.SettingFirstVertex);
  92. this.polylineEntity = undefined;
  93. this.updateButtonStates();
  94. break;
  95. }
  96. };
  97. DrawPolyline.prototype.keyPressEvent = function(event) {
  98. var di = this.getDocumentInterface();
  99. if ((event.key() === Qt.Key_Enter.valueOf()) || (event.key() === Qt.Key_Return.valueOf())) {
  100. if (this.state === DrawPolyline.State.SettingFirstVertex) {
  101. var view = di.getLastKnownViewWithFocus();
  102. var pos = di.getLastPosition();
  103. var e = new RCoordinateEvent(pos, view.getScene(), getRGraphicsView(view));
  104. this.pickCoordinate(e, false);
  105. }
  106. } else {
  107. EAction.prototype.keyPressEvent(event);
  108. }
  109. };
  110. DrawPolyline.prototype.showUiOptions = function(resume) {
  111. Draw.prototype.showUiOptions.call(this, resume);
  112. var optionsToolBar = EAction.getOptionsToolBar();
  113. var w = optionsToolBar.findChild("Restrict");
  114. var guiAction = RGuiAction.getByScriptFile("scripts/Snap/RestrictAngleLength/RestrictAngleLength.js");
  115. w.setDefaultAction(guiAction);
  116. this.updateButtonStates();
  117. };
  118. DrawPolyline.prototype.pickCoordinate = function(event, preview) {
  119. var point, op;
  120. var di = this.getDocumentInterface();
  121. var document = this.getDocument();
  122. switch (this.state) {
  123. // set first vertex, create polyline entity:
  124. case DrawPolyline.State.SettingFirstVertex:
  125. if (!preview) {
  126. point = event.getModelPosition();
  127. var pl = new RPolylineEntity(document, new RPolylineData());
  128. pl.appendVertex(point);
  129. op = new RAddObjectOperation(pl, this.getToolTitle());
  130. this.applyOperation(op);
  131. di.setRelativeZero(point);
  132. if (!isNull(this.polylineEntity)) {
  133. this.setState(DrawPolyline.State.SettingNextVertex);
  134. }
  135. }
  136. break;
  137. // set next vertex:
  138. case DrawPolyline.State.SettingNextVertex:
  139. point = event.getModelPosition();
  140. // number of existing vertices:
  141. var numberOfVertices;
  142. if (isNull(this.polylineEntity)) {
  143. numberOfVertices = 0;
  144. }
  145. else {
  146. numberOfVertices = this.polylineEntity.countVertices();
  147. }
  148. if (numberOfVertices>0) {
  149. var bulge;
  150. var vertex;
  151. var appendPoint;
  152. if (this.prepend) {
  153. appendPoint = this.polylineEntity.getStartPoint();
  154. }
  155. else {
  156. appendPoint = this.polylineEntity.getEndPoint();
  157. }
  158. if (this.arcSegment===true) {
  159. var dir = 0.0;
  160. if (numberOfVertices>1) {
  161. if (this.prepend) {
  162. dir = this.polylineEntity.getDirection1() + Math.PI;
  163. }
  164. else {
  165. dir = this.polylineEntity.getDirection2() + Math.PI;
  166. }
  167. }
  168. this.segment = RArc.createTangential(
  169. appendPoint,
  170. point,
  171. dir,
  172. this.radius);
  173. // handle sweep here
  174. // change end angle
  175. // if sweep equals zero then do nothing
  176. if (this.sweep < 0.0 || this.sweep > 360.0) {
  177. // print error message
  178. this.sweep = 0.0;
  179. }
  180. if (this.sweep !== 0.0) {
  181. var curSweep = this.segment.getSweep();
  182. if (curSweep !== this.sweep) {
  183. var startAngle = RMath.rad2deg(this.segment.getStartAngle());
  184. var reversed = this.segment.isReversed();
  185. if (reversed) {
  186. var endAngle = startAngle - this.sweep;
  187. if (endAngle < 0.0) {
  188. endAngle = endAngle + 360.0;
  189. }
  190. } else {
  191. endAngle = startAngle + this.sweep;
  192. if (endAngle > 360.0) {
  193. endAngle = endAngle - 360.0;
  194. }
  195. }
  196. this.segment.setEndAngle(RMath.deg2rad(endAngle));
  197. }
  198. }
  199. vertex = this.segment.getEndPoint();
  200. if (this.prepend) {
  201. this.segment.reverse();
  202. }
  203. bulge = this.segment.getBulge();
  204. this.center = this.segment.getCenter();
  205. this.angle = this.segment.getEndAngle();
  206. }
  207. else {
  208. this.segment = new RLine(appendPoint, point);
  209. bulge = 0.0;
  210. vertex = point;
  211. this.center = undefined;
  212. this.angle = undefined;
  213. }
  214. // append or prepend vertex:
  215. if (!preview) {
  216. if (this.prepend) {
  217. this.polylineEntity.prependVertex(vertex, bulge);
  218. }
  219. else {
  220. this.polylineEntity.setBulgeAt(numberOfVertices-1, bulge);
  221. this.polylineEntity.appendVertex(vertex, 0.0);
  222. }
  223. }
  224. }
  225. if (preview) {
  226. this.updatePreview();
  227. }
  228. else {
  229. op = this.getOperation(false);
  230. if (!isNull(op)) {
  231. this.applyOperation(op);
  232. di.setRelativeZero(vertex);
  233. this.uncheckArcSegment();
  234. }
  235. }
  236. break;
  237. }
  238. if (!preview) {
  239. this.updateButtonStates();
  240. }
  241. };
  242. DrawPolyline.prototype.getOperation = function(preview) {
  243. if (isNull(this.polylineEntity)) {
  244. return undefined;
  245. }
  246. // for preview, only add current segment:
  247. if (preview) {
  248. var entity;
  249. if (isLineShape(this.segment)) {
  250. entity = new RLineEntity(
  251. this.getDocument(),
  252. new RLineData(this.segment)
  253. );
  254. }
  255. else if (isArcShape(this.segment)) {
  256. entity = new RArcEntity(
  257. this.getDocument(),
  258. new RArcData(this.segment)
  259. );
  260. }
  261. if (!isEntity(entity)) {
  262. return undefined;
  263. }
  264. var e;
  265. if (isFunction(this.polylineEntity.data)) {
  266. e = this.polylineEntity.data();
  267. }
  268. else {
  269. e = this.polylineEntity;
  270. }
  271. entity.copyAttributesFrom(e);
  272. return new RAddObjectOperation(entity, false);
  273. }
  274. else {
  275. return new RAddObjectOperation(this.polylineEntity, this.getToolTitle(), true, false);
  276. }
  277. };
  278. /**
  279. * Updates the polyline in storage and makes sure that this.polylineEntity
  280. * points to new new clone of the original entity.
  281. */
  282. DrawPolyline.prototype.applyOperation = function(op) {
  283. this.polylineEntity = undefined;
  284. var di = this.getDocumentInterface();
  285. var document = this.getDocument();
  286. var transaction = di.applyOperation(op);
  287. // find out ID of polyline, that was added to the document:
  288. var ids = transaction.getAffectedObjects();
  289. for (var i=0; i<ids.length; ++i) {
  290. var id = ids[i];
  291. var entity = document.queryEntity(id);
  292. if (isPolylineEntity(entity)) {
  293. this.polylineEntity = entity;
  294. break;
  295. }
  296. }
  297. };
  298. /**
  299. * Called when user clicks the 'Close' button to close the polyline.
  300. */
  301. DrawPolyline.prototype.slotClose = function() {
  302. if (isNull(this.polylineEntity)) {
  303. return;
  304. }
  305. if (this.polylineEntity.countVertices() >= 3) {
  306. var di = this.getDocumentInterface();
  307. di.setRelativeZero(this.polylineEntity.getVertexAt(0));
  308. this.polylineEntity.setClosed(true);
  309. var op = this.getOperation(false);
  310. if (!isNull(op)) {
  311. this.applyOperation(op);
  312. }
  313. this.setState(DrawPolyline.State.SettingFirstVertex);
  314. this.polylineEntity = undefined;
  315. }
  316. this.updateButtonStates();
  317. };
  318. /**
  319. * Called when user clicks the 'Undo' button to remove the last added vertex.
  320. */
  321. DrawPolyline.prototype.slotUndo = function() {
  322. if (isNull(this.polylineEntity)) {
  323. return;
  324. }
  325. if (this.polylineEntity.countVertices() >= 2) {
  326. var di = this.getDocumentInterface();
  327. var bulge = this.polylineEntity.getBulgeAt(this.polylineEntity.countVertices() - 1);
  328. var vertex = this.polylineEntity.getVertexAt(this.polylineEntity.countVertices() - 1);
  329. this.polylineEntity.removeLastVertex();
  330. di.setRelativeZero(this.polylineEntity.getVertexAt(this.polylineEntity.countVertices() - 1));
  331. di.clearPreview();
  332. var op = this.getOperation(false);
  333. if (!isNull(op)) {
  334. this.applyOperation(op);
  335. // add last vertex and bulge to redoList
  336. this.redoList.push(bulge);
  337. this.redoList.push(vertex);
  338. }
  339. this.simulateMouseMoveEvent();
  340. }
  341. this.updateButtonStates();
  342. };
  343. /**
  344. * Called when user clicks the 'Redo' button to redo the last vertex.
  345. */
  346. DrawPolyline.prototype.slotRedo = function() {
  347. if (isNull(this.polylineEntity)) {
  348. return;
  349. }
  350. if (this.redoList.length >= 2) {
  351. var di = this.getDocumentInterface();
  352. // redo last vertex and bulge
  353. var vertex = this.redoList.pop();
  354. var bulge = this.redoList.pop();
  355. this.polylineEntity.appendVertex(vertex, bulge);
  356. di.clearPreview();
  357. var op = this.getOperation(false);
  358. if (!isNull(op)) {
  359. this.applyOperation(op);
  360. }
  361. di.setRelativeZero(vertex);
  362. this.simulateMouseMoveEvent();
  363. }
  364. this.updateButtonStates();
  365. };
  366. /**
  367. * Called when user toggles 'Arc segment' check box to indicate if the next
  368. * segment is a line or an arc segment.
  369. */
  370. DrawPolyline.prototype.slotArcSegmentChanged = function(value) {
  371. this.arcSegment = value;
  372. };
  373. /**
  374. * Called when user enters a radius for the next arc segment.
  375. */
  376. DrawPolyline.prototype.slotRadiusChanged = function(value) {
  377. this.radius = value;
  378. };
  379. /**
  380. * Called when user enters a sweep for the next arc segment.
  381. */
  382. DrawPolyline.prototype.slotSweepChanged = function(value) {
  383. this.sweep = value;
  384. };
  385. /**
  386. * Called internally to automatically uncheck the 'Arc segment' check box.
  387. */
  388. DrawPolyline.prototype.uncheckArcSegment = function() {
  389. var w = objectFromPath("MainWindow::Options::ArcSegment");
  390. w.checked = false;
  391. };
  392. /**
  393. * Updates the state (enabled / disabled) of the undo and the close buttons
  394. * depending on the current progress.
  395. */
  396. DrawPolyline.prototype.updateButtonStates = function() {
  397. var w;
  398. if (this.state === DrawPolyline.State.SettingFirstVertex) {
  399. this.uncheckArcSegment();
  400. }
  401. var optionsToolBar = EAction.getOptionsToolBar();
  402. w = optionsToolBar.findChild("Close");
  403. if (!isNull(this.polylineEntity)) {
  404. w.enabled = (this.polylineEntity.countVertices() >= 3) ? true : false;
  405. }
  406. else {
  407. w.enabled = false;
  408. }
  409. w = optionsToolBar.findChild("Undo");
  410. if (!isNull(this.polylineEntity)) {
  411. w.enabled = (this.polylineEntity.countVertices() >= 2) ? true : false;
  412. }
  413. else {
  414. w.enabled = false;
  415. }
  416. w = optionsToolBar.findChild("Redo");
  417. w.enabled = (this.redoList.length >= 2) ? true : false;
  418. };
  419. DrawPolyline.prototype.getAuxPreview = function() {
  420. var ret = [];
  421. if (this.state === DrawPolyline.State.SettingNextVertex) {
  422. if (!isNull(this.center) && !isNull(this.angle)) {
  423. var v = RVector.createPolar(this.radius, this.angle);
  424. ret.push(new RLine(this.center, this.center.operator_add(v)));
  425. }
  426. }
  427. return ret;
  428. };
  429. /**
  430. * Allows commands to be entered in command line
  431. * Using the 'startsWith' function allows the user to enter only as many characters
  432. * as needed to distinguish between commands
  433. * In this case only the first character is needed. (But entering 'c', 'cl', 'clo', 'clos'
  434. * or 'close' would all invoke the close command. Similarly with undo and redo)
  435. */
  436. DrawPolyline.prototype.commandEvent = function(event) {
  437. var str;
  438. var cmd = event.getCommand();
  439. cmd = cmd.toLowerCase();
  440. str = qsTr("close");
  441. if (str.startsWith(cmd)) {
  442. this.slotClose();
  443. event.accept();
  444. return;
  445. }
  446. str = qsTr("undo");
  447. if (str.startsWith(cmd)) {
  448. this.slotUndo();
  449. event.accept();
  450. return;
  451. }
  452. str = qsTr("redo");
  453. if (str.startsWith(cmd)) {
  454. this.slotRedo();
  455. event.accept();
  456. return;
  457. }
  458. str = qsTr("arc");
  459. if (str.startsWith(cmd)) {
  460. var ob = objectFromPath("MainWindow::Options::ArcSegment");
  461. ob.checked = !ob.checked
  462. this.slotArcSegmentChanged(ob.checked);
  463. event.accept();
  464. return;
  465. }
  466. };