TextAlong.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. /**
  2. * Copyright (c) 2017 by Robert S. All rights reserved.
  3. *
  4. * This file is part of the QCAD project.
  5. *
  6. * QCAD is free software: you can redistribute it and/or modify
  7. * it under the terms of the GNU General Public License as published by
  8. * the Free Software Foundation, either version 3 of the License, or
  9. * (at your option) any later version.
  10. *
  11. * QCAD is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with QCAD.
  18. */
  19. /**
  20. * \defgroup ecma_draw_text TextAlong Drawing Tool
  21. * \ingroup ecma_draw
  22. *
  23. * \brief This module contains the ECMAScript implementation of the text along an entity
  24. * drawing tool.
  25. */
  26. include("scripts/Draw/Draw.js");
  27. include("scripts/WidgetFactory.js");
  28. include("scripts/Draw/Text/TextDialog/TextDialog.js");
  29. /**
  30. * \class TextAlong
  31. * \brief Insert text along an entity.
  32. * \ingroup ecma_misc_draw
  33. */
  34. function TextAlong(guiAction, mode) {
  35. if (isNull(mode)) {
  36. mode = TextDialog.Mode.Text;
  37. }
  38. Draw.call(this, guiAction);
  39. this.textData = undefined;
  40. this.mode = mode;
  41. this.shape = undefined;
  42. this.text = undefined;
  43. this.height = 1.0;
  44. this.spacing = 0.0;
  45. this.pos = undefined;
  46. this.ccw = false;
  47. this.fit = false;
  48. this.CharWidths = [];
  49. this.totalCharWidth = 0.0;
  50. this.setUiOptions("TextAlong.ui");
  51. }
  52. TextAlong.prototype = new Draw();
  53. TextAlong.State = {
  54. ChoosingEntity : 0,
  55. SettingPosition : 1
  56. };
  57. TextAlong.prototype.beginEvent = function() {
  58. Draw.prototype.beginEvent.call(this);
  59. var dialog = new TextDialog(this.mode);
  60. this.textData = dialog.show();
  61. if (isNull(this.textData)) {
  62. this.terminate();
  63. return;
  64. }
  65. var optionsToolBar = EAction.getOptionsToolBar();
  66. var contentsEdit = optionsToolBar.findChild("Contents");
  67. WidgetFactory.initLineEdit(contentsEdit, false);
  68. contentsEdit.text = this.textData.getEscapedText();
  69. var heightEdit = optionsToolBar.findChild("Height");
  70. var ht = heightEdit.getValue();
  71. this.textData.setTextHeight(ht);
  72. // generate list of char widths
  73. this.getCharWidths();
  74. this.setState(TextAlong.State.ChoosingEntity);
  75. };
  76. TextAlong.prototype.setState = function(state) {
  77. Draw.prototype.setState.call(this, state);
  78. var appWin = RMainWindowQt.getMainWindow();
  79. this.setCrosshairCursor();
  80. switch (this.state) {
  81. case TextAlong.State.ChoosingEntity :
  82. this.getDocumentInterface().setClickMode(RAction.PickEntity);
  83. this.setCommandPrompt(qsTr("Select Entity"));
  84. this.setLeftMouseTip(qsTr("Select Entity"));
  85. this.setRightMouseTip(EAction.trCancel);
  86. EAction.showSnapTools();
  87. break;
  88. case TextAlong.State.SettingPosition :
  89. this.getDocumentInterface().setClickMode(RAction.PickCoordinate);
  90. this.setCommandPrompt(qsTr("Position"));
  91. this.setLeftMouseTip(qsTr("Position"));
  92. this.setRightMouseTip(EAction.trCancel);
  93. EAction.showSnapTools();
  94. break;
  95. }
  96. };
  97. TextAlong.prototype.pickEntity = function(event, preview) {
  98. var di = this.getDocumentInterface();
  99. var doc = this.getDocument();
  100. var entityId = event.getEntityId();
  101. var entity = doc.queryEntity(entityId);
  102. this.pos = event.getModelPosition();
  103. if (isNull(entity)) {
  104. this.shape = undefined;
  105. return;
  106. } else {
  107. if (!preview) {
  108. this.shape = getClosestSimpleShape(entity, this.pos);
  109. if (!isNull(this.shape)) {
  110. var type = this.shape.getShapeType();
  111. if (type !== RShape.Line && type !== RShape.Arc && type !== RShape.Circle) {
  112. EAction.warnNotLineArcCircle();
  113. return;
  114. }
  115. this.setState(TextAlong.State.SettingPosition);
  116. }
  117. }
  118. }
  119. };
  120. TextAlong.prototype.pickCoordinate = function(event, preview) {
  121. var di = this.getDocumentInterface();
  122. this.pos = event.getModelPosition();
  123. if (!preview) {
  124. di.setRelativeZero(this.pos);
  125. }
  126. this.textData.setAlignmentPoint(this.pos);
  127. this.text = new RTextEntity(this.getDocument(), this.textData);
  128. var op = this.getOperation(preview);
  129. if (preview) {
  130. di.previewOperation(op);
  131. }
  132. else {
  133. if (!isNull(op)) {
  134. di.applyOperation(op);
  135. }
  136. }
  137. };
  138. TextAlong.prototype.getOperation = function(preview) {
  139. var di = this.getDocumentInterface();
  140. var type;
  141. if (!isNull(this.shape)) {
  142. type = this.shape.getShapeType();
  143. } else {
  144. return undefined;
  145. }
  146. var op = undefined;
  147. var arc;
  148. switch(type) {
  149. case RShape.Line:
  150. op = this.alongLine();
  151. break;
  152. case RShape.Arc:
  153. // 'this.shape' is a pointer to the arc, so get the actual arc object
  154. arc = this.shape;
  155. op = this.alongArc(arc);
  156. break;
  157. case RShape.Circle:
  158. // create a circular arc and use 'alongArc'.
  159. // when using 'fit', add one or more spaces to the start or end of the text
  160. arc = new RArc(this.shape.getCenter(), this.shape.getRadius(), 0.0, 0.0);
  161. op = this.alongArc(arc);
  162. break;
  163. default :
  164. break;
  165. }
  166. return op;
  167. }
  168. TextAlong.prototype.getCharWidths = function() {
  169. if (isNull(this.textData)) {
  170. return;
  171. }
  172. this.text = new RTextEntity(this.getDocument(), this.textData);
  173. var txt = this.text.getPlainText();
  174. var num = txt.length;
  175. this.CharWidths = [];
  176. this.totalCharWidth = 0.0;
  177. for (var i = 0; i < num; i++) {
  178. var char = txt[i];
  179. // A space character is returned as zero width
  180. if (char === " ") {
  181. char = "-";
  182. }
  183. var td = this.getTextData(char);
  184. var wid = td.getWidth();
  185. this.CharWidths.push(wid + this.spacing);
  186. this.totalCharWidth += wid + this.spacing;
  187. }
  188. }
  189. TextAlong.prototype.getTextData = function(txt) {
  190. var td = new RTextData();
  191. if (this.pos !== undefined) {
  192. td.setAlignmentPoint(this.pos);
  193. }
  194. td.setVAlign(this.textData.getVAlign());
  195. td.setHAlign(this.textData.getHAlign());
  196. td.setText(txt);
  197. td.setTextHeight(this.textData.getTextHeight());
  198. td.setTextWidth(this.textData.getTextWidth());
  199. td.setFontName(this.textData.getFontName());
  200. td.setAngle(this.textData.getAngle());
  201. td.setBold(this.textData.isBold());
  202. td.setItalic(this.textData.isItalic());
  203. td.setLineSpacingFactor(this.textData.getLineSpacingFactor());
  204. return td;
  205. }
  206. TextAlong.prototype.slotContentsChanged = function(value) {
  207. if (isNull(this.textData)) {
  208. return;
  209. }
  210. this.textData.setText(value);
  211. // change list of char widths
  212. this.getCharWidths();
  213. };
  214. TextAlong.prototype.slotHeightChanged = function(value) {
  215. if (isNull(this.textData)) {
  216. return;
  217. }
  218. this.height = value;
  219. this.textData.setTextHeight(this.height);
  220. // change list of char widths
  221. this.getCharWidths();
  222. };
  223. TextAlong.prototype.slotSpacingChanged = function(value) {
  224. this.spacing = value;
  225. this.getCharWidths();
  226. };
  227. TextAlong.prototype.slotDirectionChanged = function(button) {
  228. if (!isQObject(button) || button.checked !== true) {
  229. return;
  230. }
  231. if (button.objectName === "Clockwise") {
  232. this.ccw = false;
  233. }
  234. else {
  235. this.ccw = true;
  236. }
  237. };
  238. TextAlong.prototype.slotFitChanged = function(value) {
  239. this.fit = value;
  240. };
  241. TextAlong.prototype.alongLine = function() {
  242. var ang, len, op, h, v;
  243. ang = this.shape.getAngle();
  244. len = this.shape.getLength();
  245. if (!isNull(this.textData)) {
  246. if (this.ccw) {
  247. this.textData.setAngle(ang + Math.PI);
  248. } else {
  249. this.textData.setAngle(ang);
  250. }
  251. }
  252. op = new RAddObjectsOperation();
  253. if (this.fit) {
  254. var txt = this.text.getPlainText();
  255. var num = txt.length;
  256. var firstCharWidth = this.CharWidths[0];
  257. var lastCharWidth = this.CharWidths[num - 1];
  258. var dist = (len - firstCharWidth) / (num - 1);
  259. // now position 'this.pos'
  260. v = new RVector();
  261. h = this.textData.getHAlign();
  262. if (h === RS.HAlignRight) {
  263. v.setPolar(len, this.textData.getAngle() + Math.PI);
  264. this.pos = this.pos.operator_add(v);
  265. } else if (h === RS.HAlignCenter) {
  266. v.setPolar(len / 2.0, this.textData.getAngle() + Math.PI);
  267. this.pos = this.pos.operator_add(v);
  268. }
  269. for (var i = 0; i < num; i++) {
  270. var td = this.getTextData(txt[i]);
  271. var te = new RTextEntity(this.getDocument(), td);
  272. te.setSimple(true); // This is required so 'base' and 'bottom' points are correct
  273. te.setVAlign(this.textData.getVAlign());
  274. te.setHAlign(RS.HAlignCenter);
  275. v.setPolar((dist * i) + (firstCharWidth / 2.0), te.getAngle());
  276. var pt = this.pos.operator_add(v);
  277. te.setAlignmentPoint(pt);
  278. op.addObject(te);
  279. }
  280. } else {
  281. op.addObject(this.text);
  282. }
  283. return op;
  284. }
  285. TextAlong.prototype.alongArc = function(arc) {
  286. var ang, op;
  287. op = new RAddObjectsOperation();
  288. var txt = this.text.getPlainText();
  289. var num = txt.length;
  290. // use distance from 'this.pos' to centre point of arc, as radius
  291. arc.setRadius(arc.center.getDistanceTo(this.pos));
  292. var pt = this.pos;
  293. ang = arc.center.getAngleTo(this.pos);
  294. var arcrad = arc.getRadius();
  295. // calculate spacing for 'Fit' option
  296. if (this.fit) {
  297. // subtract half the width of first character and last character
  298. var firstcharAngle = ((this.CharWidths[0]) / arcrad) / 2.0;
  299. var lastcharAngle = ((this.CharWidths[num - 1]) / arcrad) / 2.0;
  300. var fitAngle = (arc.getSweep() - firstcharAngle - lastcharAngle) / (num - 1);
  301. }
  302. var td = this.getTextData(txt[0]);
  303. var te = new RTextEntity(this.getDocument(), td);
  304. var h = te.getHAlign();
  305. var v = te.getVAlign();
  306. // set rotation angle for first character
  307. var rotateAngle, sweep;
  308. var width = this.totalCharWidth;
  309. if (this.fit) {
  310. sweep = arc.getSweep();
  311. } else {
  312. sweep = width / arcrad;
  313. }
  314. if (h === RS.HAlignLeft) {
  315. rotateAngle = 0.0;
  316. } else if (h === RS.HAlignCenter) {
  317. rotateAngle = sweep / 2.0;
  318. } else {
  319. rotateAngle = sweep;
  320. }
  321. // get angle for half the first characters width
  322. // and subtract from rotateAngle
  323. var halfAngle = ((this.CharWidths[0]) / arcrad) / 2.0;
  324. rotateAngle -= halfAngle;
  325. if (this.ccw) {
  326. rotateAngle = -rotateAngle;
  327. }
  328. for (var i = 0; i < num; i++) {
  329. td = this.getTextData(txt[i]);
  330. te = new RTextEntity(this.getDocument(), td);
  331. h = te.getHAlign();
  332. v = te.getVAlign();
  333. te.setHAlign(RS.HAlignCenter);
  334. te.setSimple(true); // This is required so 'base' and 'bottom' points are correct
  335. te.setAlignmentPoint(pt);
  336. if (this.ccw) {
  337. te.setAngle(ang + (Math.PI / 2.0));
  338. } else {
  339. te.setAngle(ang - (Math.PI / 2.0));
  340. }
  341. // draw each character at 'this.pos', and rotate to match alignment point
  342. te.rotate(rotateAngle, arc.center);
  343. op.addObject(te);
  344. // adjust point and angle for next character
  345. // function 'getCharWidths' handles spacing
  346. var currentcharAngle = (this.CharWidths[i]) / arcrad;
  347. var nextcharAngle = (this.CharWidths[i + 1]) / arcrad;
  348. var charAngle = (currentcharAngle / 2.0) + (nextcharAngle / 2.0);
  349. if (this.ccw) {
  350. if (this.fit) {
  351. rotateAngle = rotateAngle + fitAngle;
  352. } else {
  353. rotateAngle = rotateAngle + charAngle;
  354. }
  355. } else {
  356. if (this.fit) {
  357. rotateAngle = rotateAngle - fitAngle;
  358. } else {
  359. rotateAngle = rotateAngle - charAngle;
  360. }
  361. }
  362. }
  363. return op;
  364. }