TextDialog.js 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102
  1. /**
  2. * Copyright (c) 2011-2018 by Andrew Mustun. 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. include("scripts/library.js");
  20. include("scripts/map.js");
  21. include("scripts/WidgetFactory.js");
  22. include("CharacterMapDialog.js");
  23. /**
  24. * \class SymbolMenuData
  25. * \brief Helper class for symbol popup menu.
  26. * \ingroup ecma_draw_text
  27. */
  28. function SymbolMenuData(textWidget, data) {
  29. this.textWidget = textWidget;
  30. this.data = data;
  31. }
  32. SymbolMenuData.prototype.triggered = function() {
  33. var cursor = this.textWidget.textCursor();
  34. cursor.insertText(this.data);
  35. };
  36. /**
  37. * \class TextDialog
  38. * \brief Text creation or editing dialog.
  39. * \ingroup ecma_draw_text
  40. */
  41. function TextDialog(mode) {
  42. if (isNull(mode)) {
  43. mode = TextDialog.Mode.Text;
  44. }
  45. this.mode = mode;
  46. this.dialog = undefined;
  47. this.textEdit = undefined;
  48. this.sourceEdit = undefined;
  49. this.actionTextBold = undefined;
  50. this.actionTextItalic = undefined;
  51. this.actionTextUnderline = undefined;
  52. this.actionSubscript = undefined;
  53. this.actionSuperscript = undefined;
  54. this.actionTextColor = undefined;
  55. //this.actionZoomIn = undefined;
  56. //this.actionZoomOut = undefined;
  57. this.actionTextSymbol = undefined;
  58. this.actionUndo = undefined;
  59. this.actionRedo = undefined;
  60. this.actionCut = undefined;
  61. this.actionCopy = undefined;
  62. this.actionPaste = undefined;
  63. this.comboFont = undefined;
  64. this.comboSize = undefined;
  65. // display text in text edit with this factor, compared to drawing unit:
  66. this.fontHeightFactor = 10.0;
  67. this.mainFont = undefined;
  68. this.initialColor = new RColor();
  69. }
  70. TextDialog.prototype = {};
  71. TextDialog.Mode = {
  72. Text : 0,
  73. Attribute : 1,
  74. AttributeDefinition : 2
  75. };
  76. TextDialog.prototype.setMode = function(mode) {
  77. this.mode = mode;
  78. };
  79. TextDialog.prototype.getTextDocument = function() {
  80. if (RSettings.getQtVersion() >= 0x050000) {
  81. return this.textEdit.document;
  82. }
  83. else {
  84. return this.textEdit.document();
  85. }
  86. };
  87. TextDialog.prototype.getSourceDocument = function() {
  88. return this.sourceEdit.document();
  89. };
  90. TextDialog.prototype.show = function(textDataIn) {
  91. if (!isNull(textDataIn)) {
  92. if (isOfType(textDataIn, RAttributeData)) {
  93. this.mode = TextDialog.Mode.Attribute;
  94. }
  95. else if (isOfType(textDataIn, RAttributeDefinitionData)) {
  96. this.mode = TextDialog.Mode.AttributeDefinition;
  97. }
  98. else {
  99. this.mode = TextDialog.Mode.Text;
  100. }
  101. }
  102. this.dialog = WidgetFactory.createDialog(TextDialog.basePath, "TextDialog.ui", EAction.getMainWindow());
  103. // initialize dialog controls:
  104. this.textEdit = this.dialog.findChild("Text");
  105. this.sourceEdit = this.dialog.findChild("Source");
  106. this.tabWidget = this.dialog.findChild("TabWidget");
  107. // main font combo box:
  108. var comboMainFont = this.dialog.findChild("MainFont");
  109. var comboFont = this.dialog.findChild("Font");
  110. initFontComboBox(comboMainFont);
  111. initFontComboBox(comboFont);
  112. comboMainFont.currentFontChanged.connect(this, this.mainFontChanged);
  113. // main font height:
  114. var editMainHeight = this.dialog.findChild("MainHeight");
  115. editMainHeight.valueChanged.connect(this, this.mainFontChanged);
  116. // main font bold:
  117. var checkMainBold = this.dialog.findChild("MainBold");
  118. checkMainBold.toggled.connect(this, this.mainFontChanged);
  119. // main font italic:
  120. var checkMainItalic = this.dialog.findChild("MainItalic");
  121. checkMainItalic.toggled.connect(this, this.mainFontChanged);
  122. // default line spacing:
  123. //var checkBoxDefaultLineSpacing = this.dialog.findChild("DefaultLineSpacing");
  124. //checkBoxDefaultLineSpacing.toggled.connect(this, this.useDefaultLineSpacing);
  125. // line spacing:
  126. var editLineSpacingFactor = this.dialog.findChild("LineSpacingFactor");
  127. // angle:
  128. var editAngle = this.dialog.findChild("Angle");
  129. /*
  130. var buttonNew = this.dialog.findChild("New");
  131. buttonNew.clicked.connect(this.textEdit, "clear");
  132. var buttonOpen = this.dialog.findChild("Open");
  133. var buttonSaveAs = this.dialog.findChild("SaveAs");
  134. */
  135. this.setupEditActions();
  136. this.setupTextActions();
  137. // update status buttons bold / italic / underline:
  138. this.textEdit.currentCharFormatChanged.connect(this, this.currentCharFormatChanged);
  139. this.textEdit.cursorPositionChanged.connect(this, this.cursorPositionChanged);
  140. var td = this.getTextDocument();
  141. td.undoAvailable.connect(this.actionUndo, this.actionUndo.setEnabled);
  142. td.redoAvailable.connect(this.actionRedo, this.actionRedo.setEnabled);
  143. this.actionUndo.setEnabled(td.isUndoAvailable());
  144. this.actionRedo.setEnabled(td.isRedoAvailable());
  145. this.actionUndo.triggered.connect(this.textEdit, this.textEdit.undo);
  146. this.actionRedo.triggered.connect(this.textEdit, this.textEdit.redo);
  147. this.actionCut.setEnabled(false);
  148. this.actionCopy.setEnabled(false);
  149. this.actionCut.triggered.connect(this.textEdit, this.textEdit.cut);
  150. this.actionCopy.triggered.connect(this.textEdit, this.textEdit.copy);
  151. this.actionPaste.triggered.connect(this.textEdit, this.textEdit.paste);
  152. this.textEdit.copyAvailable.connect(this.actionCut, this.actionCut.setEnabled);
  153. this.textEdit.copyAvailable.connect(this.actionCopy, this.actionCopy.setEnabled);
  154. var cbSimpleText = this.dialog.findChild("SimpleText");
  155. cbSimpleText.toggled.connect(this, this.simpleTextToggled);
  156. var buttonTopLeft = this.dialog.findChild("AlignTopLeft");
  157. var buttonTopCenter = this.dialog.findChild("AlignTopCenter");
  158. var buttonTopRight = this.dialog.findChild("AlignTopRight");
  159. var buttonMiddleLeft = this.dialog.findChild("AlignMiddleLeft");
  160. var buttonMiddleCenter = this.dialog.findChild("AlignMiddleCenter");
  161. var buttonMiddleRight = this.dialog.findChild("AlignMiddleRight");
  162. var buttonBaseLeft = this.dialog.findChild("AlignBaseLeft");
  163. var buttonBaseCenter = this.dialog.findChild("AlignBaseCenter");
  164. var buttonBaseRight = this.dialog.findChild("AlignBaseRight");
  165. var buttonBottomLeft = this.dialog.findChild("AlignBottomLeft");
  166. var buttonBottomCenter = this.dialog.findChild("AlignBottomCenter");
  167. var buttonBottomRight = this.dialog.findChild("AlignBottomRight");
  168. getClipboard().dataChanged.connect(this, this.clipboardDataChanged);
  169. // change dialog into attribute / attribute definition dialog:
  170. if (this.mode === TextDialog.Mode.AttributeDefinition ||
  171. this.mode === TextDialog.Mode.Attribute) {
  172. this.dialog.findChild("Separator").visible = false;
  173. this.dialog.findChild("LineSpacingFactorLabel").visible = false;
  174. editLineSpacingFactor.visible = false;
  175. cbSimpleText.visible = false;
  176. this.tabWidget.removeTab(1);
  177. this.tabWidget.removeTab(0);
  178. this.dialog.resize(0,0);
  179. if (this.mode === TextDialog.Mode.AttributeDefinition) {
  180. this.dialog.windowTitle = entityTypeToString(RS.EntityAttributeDefinition, false);
  181. }
  182. else if (this.mode === TextDialog.Mode.Attribute) {
  183. this.dialog.windowTitle = entityTypeToString(RS.EntityAttribute, false);
  184. }
  185. }
  186. else {
  187. this.tabWidget.removeTab(2);
  188. }
  189. // sync rich text / source:
  190. this.tabWidget["currentChanged(int)"].connect(this, this.tabChanged);
  191. if (this.mode === TextDialog.Mode.AttributeDefinition) {
  192. this.dialog.findChild("ValueLabel").text = qsTr("Default value:");
  193. }
  194. // initialize dialog from given text data (textDataIn):
  195. if (!isNull(textDataIn)) {
  196. cbSimpleText.checked = textDataIn.isSimple();
  197. this.initialColor = textDataIn.getColor();
  198. activateFont(comboMainFont, textDataIn.getFontName());
  199. checkMainBold.checked = textDataIn.isBold();
  200. checkMainItalic.checked = textDataIn.isItalic();
  201. editMainHeight.setValue(textDataIn.getTextHeight());
  202. this.sourceEdit.setPlainText(textDataIn.getEscapedText());
  203. this.updateRichText(true);
  204. //qDebug("this.sourceEdit:", this.sourceEdit.plainText);
  205. if (textDataIn.getVAlign()==RS.VAlignTop) {
  206. if (textDataIn.getHAlign()==RS.HAlignLeft || textDataIn.getHAlign()==RS.HAlignFit || textDataIn.getHAlign()==RS.HAlignAlign) {
  207. buttonTopLeft.checked = true;
  208. }
  209. else if (textDataIn.getHAlign()==RS.HAlignCenter) {
  210. buttonTopCenter.checked = true;
  211. }
  212. else if (textDataIn.getHAlign()==RS.HAlignRight) {
  213. buttonTopRight.checked = true;
  214. }
  215. }
  216. else if (textDataIn.getVAlign()==RS.VAlignMiddle) {
  217. if (textDataIn.getHAlign()==RS.HAlignLeft || textDataIn.getHAlign()==RS.HAlignFit || textDataIn.getHAlign()==RS.HAlignAlign) {
  218. buttonMiddleLeft.checked = true;
  219. }
  220. else if (textDataIn.getHAlign()==RS.HAlignCenter || textDataIn.getHAlign()==RS.HAlignMid) {
  221. buttonMiddleCenter.checked = true;
  222. }
  223. else if (textDataIn.getHAlign()==RS.HAlignRight) {
  224. buttonMiddleRight.checked = true;
  225. }
  226. }
  227. else if (textDataIn.getVAlign()==RS.VAlignBase) {
  228. if (textDataIn.getHAlign()==RS.HAlignLeft || textDataIn.getHAlign()==RS.HAlignFit || textDataIn.getHAlign()==RS.HAlignAlign) {
  229. buttonBaseLeft.checked = true;
  230. }
  231. else if (textDataIn.getHAlign()==RS.HAlignCenter) {
  232. buttonBaseCenter.checked = true;
  233. }
  234. else if (textDataIn.getHAlign()==RS.HAlignRight) {
  235. buttonBaseRight.checked = true;
  236. }
  237. }
  238. else if (textDataIn.getVAlign()==RS.VAlignBottom) {
  239. if (textDataIn.getHAlign()==RS.HAlignLeft || textDataIn.getHAlign()==RS.HAlignFit || textDataIn.getHAlign()==RS.HAlignAlign) {
  240. buttonBottomLeft.checked = true;
  241. }
  242. else if (textDataIn.getHAlign()==RS.HAlignCenter) {
  243. buttonBottomCenter.checked = true;
  244. }
  245. else if (textDataIn.getHAlign()==RS.HAlignRight) {
  246. buttonBottomRight.checked = true;
  247. }
  248. }
  249. editLineSpacingFactor.setValue(textDataIn.getLineSpacingFactor());
  250. editAngle.setValue(textDataIn.getAngle());
  251. // init values specific to attributes / attribute definitions:
  252. if (this.mode === TextDialog.Mode.Attribute ||
  253. this.mode === TextDialog.Mode.AttributeDefinition) {
  254. this.dialog.findChild("AttributeTag").text = textDataIn.getTag();
  255. this.dialog.findChild("AttributeValue").text = textDataIn.getText();
  256. if (this.mode === TextDialog.Mode.AttributeDefinition) {
  257. this.dialog.findChild("AttributePrompt").text = textDataIn.getPrompt();
  258. }
  259. else {
  260. this.dialog.findChild("AttributePrompt").visible = false;
  261. this.dialog.findChild("PromptLabel").visible = false;
  262. this.dialog.findChild("AttributeTag").enabled = false;
  263. this.dialog.findChild("AttributeValue").selectAll();
  264. }
  265. }
  266. }
  267. else {
  268. WidgetFactory.restoreState(this.dialog);
  269. cbSimpleText.checked = RSettings.getBoolValue("TextDialog/SimpleText", false);
  270. // restore widgets that are not supported by WidgetFactory:
  271. var mainFontName = RSettings.getStringValue("TextDialog/MainFont", "Arial");
  272. var mainFontIndex = comboMainFont.findText(mainFontName);
  273. if (mainFontIndex>0) {
  274. comboMainFont.currentIndex = mainFontIndex;
  275. }
  276. this.sourceEdit.setPlainText(RSettings.getStringValue("TextDialog/Source", ""));
  277. this.updateRichText(true);
  278. var vAlignment = RSettings.getIntValue("TextDialog/VAlignment", RS.VAlignTop);
  279. var hAlignment = RSettings.getIntValue("TextDialog/HAlignment", RS.HAlignLeft);
  280. if (vAlignment==RS.VAlignTop) {
  281. if (hAlignment==RS.HAlignLeft || hAlignment==RS.HAlignFit || hAlignment==RS.HAlignAlign) {
  282. buttonTopLeft.checked = true;
  283. }
  284. else if (hAlignment==RS.HAlignCenter) {
  285. buttonTopCenter.checked = true;
  286. }
  287. else if (hAlignment==RS.HAlignRight) {
  288. buttonTopRight.checked = true;
  289. }
  290. }
  291. else if (vAlignment==RS.VAlignMiddle) {
  292. if (hAlignment==RS.HAlignLeft || hAlignment==RS.HAlignFit || hAlignment==RS.HAlignAlign) {
  293. buttonMiddleLeft.checked = true;
  294. }
  295. else if (hAlignment==RS.HAlignCenter) {
  296. buttonMiddleCenter.checked = true;
  297. }
  298. else if (hAlignment==RS.HAlignRight) {
  299. buttonMiddleRight.checked = true;
  300. }
  301. }
  302. else if (vAlignment==RS.VAlignBase) {
  303. if (hAlignment==RS.HAlignLeft || hAlignment==RS.HAlignFit || hAlignment==RS.HAlignAlign) {
  304. buttonBaseLeft.checked = true;
  305. }
  306. else if (hAlignment==RS.HAlignCenter) {
  307. buttonBaseCenter.checked = true;
  308. }
  309. else if (hAlignment==RS.HAlignRight) {
  310. buttonBaseRight.checked = true;
  311. }
  312. }
  313. else if (vAlignment==RS.VAlignBottom) {
  314. if (hAlignment==RS.HAlignLeft || hAlignment==RS.HAlignFit || hAlignment==RS.HAlignAlign) {
  315. buttonBottomLeft.checked = true;
  316. }
  317. else if (hAlignment==RS.HAlignCenter) {
  318. buttonBottomCenter.checked = true;
  319. }
  320. else if (hAlignment==RS.HAlignRight) {
  321. buttonBottomRight.checked = true;
  322. }
  323. }
  324. var di = EAction.getDocumentInterface();
  325. if (!isNull(di)) {
  326. // initial text color is current color (e.g. ByLayer, red, ..):
  327. this.initialColor = di.getCurrentColor();
  328. }
  329. else {
  330. // initial color is invalid (no document, never reached):
  331. this.initialColor = new RColor();
  332. debugger;
  333. }
  334. }
  335. this.mainFontChanged();
  336. this.colorChanged(this.textEdit.textColor());
  337. this.colorChanged(this.initialColor);
  338. if (cbSimpleText.checked) {
  339. this.sourceEdit.setFocus(Qt.OtherFocusReason);
  340. this.sourceEdit.selectAll();
  341. }
  342. else {
  343. this.textEdit.setFocus(Qt.OtherFocusReason);
  344. this.textEdit.selectAll();
  345. }
  346. this.currentCharFormatChanged(this.textEdit.currentCharFormat());
  347. //this.colorChanged();
  348. // exec text dialog:
  349. var res = this.dialog.exec();
  350. if (this.tabWidget.currentIndex===0) {
  351. this.updateSource(true);
  352. }
  353. if (RSettings.getQtVersion() >= 0x060000) {
  354. // disconnect clipboard from changed event handler:
  355. getClipboard().dataChanged.disconnect(this, this.clipboardDataChanged);
  356. }
  357. // analyze dialog input:
  358. var textDataOut;
  359. if (isNull(textDataIn)) {
  360. if (this.mode === TextDialog.Mode.Attribute) {
  361. textDataOut = new RAttributeData();
  362. }
  363. else if (this.mode === TextDialog.Mode.AttributeDefinition) {
  364. textDataOut = new RAttributeDefinitionData();
  365. }
  366. else {
  367. textDataOut = new RTextData();
  368. }
  369. }
  370. else {
  371. textDataOut = textDataIn;
  372. }
  373. textDataOut.setFontName(comboMainFont.currentFont.family());
  374. textDataOut.setBold(checkMainBold.checked);
  375. textDataOut.setItalic(checkMainItalic.checked);
  376. textDataOut.setTextHeight(editMainHeight.getValue());
  377. if (buttonTopLeft.checked || buttonTopCenter.checked || buttonTopRight.checked) {
  378. textDataOut.setVAlign(RS.VAlignTop);
  379. }
  380. else if (buttonMiddleLeft.checked || buttonMiddleCenter.checked || buttonMiddleRight.checked) {
  381. textDataOut.setVAlign(RS.VAlignMiddle);
  382. }
  383. else if (buttonBaseLeft.checked || buttonBaseCenter.checked || buttonBaseRight.checked) {
  384. textDataOut.setVAlign(RS.VAlignBase);
  385. }
  386. else if (buttonBottomLeft.checked || buttonBottomCenter.checked || buttonBottomRight.checked) {
  387. textDataOut.setVAlign(RS.VAlignBottom);
  388. }
  389. if (buttonTopLeft.checked || buttonMiddleLeft.checked || buttonBaseLeft.checked || buttonBottomLeft.checked) {
  390. textDataOut.setHAlign(RS.HAlignLeft);
  391. }
  392. else if (buttonTopCenter.checked || buttonMiddleCenter.checked || buttonBaseCenter.checked || buttonBottomCenter.checked) {
  393. textDataOut.setHAlign(RS.HAlignCenter);
  394. }
  395. else if (buttonTopRight.checked || buttonMiddleRight.checked || buttonBaseRight.checked || buttonBottomRight.checked) {
  396. textDataOut.setHAlign(RS.HAlignRight);
  397. }
  398. //textDataOut.setDrawingDirection();
  399. //textDataOut.setLineSpacingStyle();
  400. textDataOut.setLineSpacingFactor(editLineSpacingFactor.getValue());
  401. textDataOut.setAngle(editAngle.getValue());
  402. // init values specific to attributes / attribute definitions:
  403. if (this.mode === TextDialog.Mode.Attribute ||
  404. this.mode === TextDialog.Mode.AttributeDefinition) {
  405. textDataOut.setSimple(true);
  406. textDataOut.setTag(this.dialog.findChild("AttributeTag").text);
  407. if (this.mode === TextDialog.Mode.AttributeDefinition) {
  408. textDataOut.setPrompt(this.dialog.findChild("AttributePrompt").text);
  409. }
  410. textDataOut.setText(this.dialog.findChild("AttributeValue").text);
  411. }
  412. else {
  413. textDataOut.setSimple(cbSimpleText.checked);
  414. textDataOut.setText(this.sourceEdit.toPlainText());
  415. }
  416. WidgetFactory.saveState(this.dialog);
  417. // save state of unsupported widgets:
  418. RSettings.setValue("TextDialog/VAlignment", textDataOut.getVAlign());
  419. RSettings.setValue("TextDialog/HAlignment", textDataOut.getHAlign());
  420. RSettings.setValue("TextDialog/MainFont", textDataOut.getFontName());
  421. destr(this.dialog);
  422. EAction.activateMainWindow();
  423. if (res) {
  424. return textDataOut;
  425. }
  426. else {
  427. return undefined;
  428. }
  429. };
  430. TextDialog.basePath = includeBasePath;
  431. TextDialog.prototype.setupEditActions = function() {
  432. var a;
  433. a = this.actionUndo = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Undo.svg")), qsTr("&Undo"), this.dialog);
  434. a.shortcut = QKeySequence.Undo;
  435. this.dialog.findChild("Undo").setDefaultAction(a);
  436. a = this.actionRedo = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Redo.svg")), qsTr("&Redo"), this.dialog);
  437. a.shortcut = QKeySequence.Redo;
  438. this.dialog.findChild("Redo").setDefaultAction(a);
  439. a = this.actionCut = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Cut.svg")), qsTr("Cu&t"), this.dialog);
  440. a.shortcut = QKeySequence.Cut;
  441. this.dialog.findChild("Cut").setDefaultAction(a);
  442. a = this.actionCopy = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Copy.svg")), qsTr("&Copy"), this.dialog);
  443. a.shortcut = QKeySequence.Copy;
  444. this.dialog.findChild("Copy").setDefaultAction(a);
  445. a = this.actionPaste = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Paste.svg")), qsTr("&Paste"), this.dialog);
  446. a.shortcut = QKeySequence.Paste;
  447. this.dialog.findChild("Paste").setDefaultAction(a);
  448. this.actionPaste.setEnabled(!getClipboard().text().isEmpty());
  449. };
  450. TextDialog.prototype.setupTextActions = function() {
  451. this.actionTextBold = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Bold.svg")), qsTr("&Bold"), this.dialog);
  452. this.actionTextBold.shortcut = new QKeySequence("Ctrl+B");
  453. var bold = new QFont();
  454. bold.setBold(true);
  455. this.actionTextBold.font = bold;
  456. this.actionTextBold.triggered.connect(this, this.textBold);
  457. this.actionTextBold.checkable = true;
  458. this.dialog.findChild("Bold").setDefaultAction(this.actionTextBold);
  459. this.actionTextItalic = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Italic.svg")), qsTr("&Italic"), this.dialog);
  460. this.actionTextItalic.shortcut = new QKeySequence("Ctrl+I");
  461. var italic = new QFont();
  462. italic.setItalic(true);
  463. this.actionTextItalic.font = italic;
  464. this.actionTextItalic.triggered.connect(this, this.textItalic);
  465. this.actionTextItalic.checkable = true;
  466. this.dialog.findChild("Italic").setDefaultAction(this.actionTextItalic);
  467. this.actionTextUnderline = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Underline.svg")), qsTr("&Underline"), this.dialog);
  468. this.actionTextUnderline.shortcut = new QKeySequence("Ctrl+U");
  469. var underline = new QFont();
  470. underline.setUnderline(true);
  471. this.actionTextUnderline.font = underline;
  472. this.actionTextUnderline.triggered.connect(this, this.textUnderline);
  473. this.actionTextUnderline.checkable = true;
  474. this.dialog.findChild("Underline").setDefaultAction(this.actionTextUnderline);
  475. this.actionSubscript = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Subscript.svg")), qsTr("&Subscript"), this.dialog);
  476. this.actionSubscript.shortcut = new QKeySequence("Shift+Ctrl+B");
  477. this.actionSubscript.triggered.connect(this, this.textSubscript);
  478. this.actionSubscript.checkable = true;
  479. this.dialog.findChild("Subscript").setDefaultAction(this.actionSubscript);
  480. this.actionSuperscript = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Superscript.svg")), qsTr("&Superscript"), this.dialog);
  481. this.actionSuperscript.shortcut = new QKeySequence("Shift+Ctrl+P");
  482. this.actionSuperscript.triggered.connect(this, this.textSuperscript);
  483. this.actionSuperscript.checkable = true;
  484. this.dialog.findChild("Superscript").setDefaultAction(this.actionSuperscript);
  485. // this.actionZoomIn = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/ZoomIn.svg")), qsTr("Zoom &In"), this.dialog);
  486. // this.actionZoomIn.shortcut = new QKeySequence(Qt.ControlModifier | Qt.Key_Plus);
  487. // this.actionZoomIn.triggered.connect(this.textEdit, "zoomIn");
  488. // this.dialog.findChild("ZoomIn").setDefaultAction(this.actionZoomIn);
  489. // this.actionZoomOut = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/ZoomOut.svg")), qsTr("Zoom &Out"), this.dialog);
  490. // this.actionZoomOut.shortcut = new QKeySequence(Qt.ControlModifier | Qt.Key_Minus);
  491. // this.actionZoomOut.triggered.connect(this.textEdit, "zoomOut");
  492. // this.dialog.findChild("ZoomOut").setDefaultAction(this.actionZoomOut);
  493. var pix = new QPixmap(16, 16);
  494. pix.fill(new QColor(Qt.black));
  495. this.actionTextColor = new QAction(new QIcon(pix), qsTr("&Color..."), this.dialog);
  496. this.actionTextColor.triggered.connect(this, this.textColor);
  497. this.dialog.findChild("Color").setDefaultAction(this.actionTextColor);
  498. this.actionTextSymbol = new QAction(new QIcon(autoIconPath(TextDialog.basePath + "/Symbol.svg")), qsTr("&Special Characters..."), this.dialog);
  499. this.actionTextSymbol.triggered.connect(this, this.textSymbol);
  500. this.dialog.findChild("Symbol").setDefaultAction(this.actionTextSymbol);
  501. this.comboFont = this.dialog.findChild("Font");
  502. if (RSettings.getQtVersion() >= 0x060000) {
  503. this.comboFont.textActivated.connect(this, this.textFamily);
  504. }
  505. else {
  506. this.comboFont["activated(QString)"].connect(this, this.textFamily);
  507. }
  508. this.comboSize = this.dialog.findChild("Height");
  509. this.comboSize.editable = true;
  510. if (RSettings.getQtVersion() >= 0x060000) {
  511. this.comboSize.textActivated.connect(this, this.textSize);
  512. }
  513. else {
  514. this.comboSize["activated(QString)"].connect(this, this.textSize);
  515. }
  516. this.comboSize.setEditText("1.0");
  517. this.comboSize.editTextChanged.connect(this, this.textSize);
  518. };
  519. /**
  520. * \return Main font to use with main height set in in drawing units
  521. * as points. E.g. a font with point size 15.0 means the font height
  522. * is really 15 drawing units.
  523. */
  524. TextDialog.prototype.getMainFont = function() {
  525. // get main font with main height:
  526. var comboMainFont = this.dialog.findChild("MainFont");
  527. var font = comboMainFont.currentFont;
  528. var editMainHeight = this.dialog.findChild("MainHeight");
  529. var h = editMainHeight.getValue();
  530. if (isNumber(h) && h>0.0) {
  531. font.setPointSizeF(h);
  532. }
  533. var checkMainBold = this.dialog.findChild("MainBold");
  534. font.setBold(checkMainBold.checked);
  535. var checkMainItalic = this.dialog.findChild("MainItalic");
  536. font.setItalic(checkMainItalic.checked);
  537. return font;
  538. };
  539. /**
  540. * Called when user changes the main font (initial font for text).
  541. */
  542. TextDialog.prototype.mainFontChanged = function() {
  543. var font = this.getMainFont();
  544. if (font.pointSizeF()>RS.PointTolerance) {
  545. this.fontHeightFactor = RSettings.getDoubleValue("Text/PreviewFontSize", 14.0) / font.pointSizeF();
  546. }
  547. else {
  548. this.fontHeightFactor = 10.0;
  549. }
  550. if (this.tabWidget.currentIndex===1) {
  551. this.updateRichText(true);
  552. }
  553. var document = this.getTextDocument();
  554. var html = document.toHtml();
  555. // qDebug("\n\nmainFontChanged: html before fix: ", html, "\n\n");
  556. html = this.fixHtml(html);
  557. // qDebug("\n\nmainFontChanged: html after fix: ", html, "\n\n");
  558. var previousMainFont = this.mainFont;
  559. this.mainFont = font;
  560. font.setPointSizeF(font.pointSizeF() * this.fontHeightFactor);
  561. this.textEdit.font = font;
  562. document.defaultFont = font;
  563. //qDebug("\n\nHTML after setting default font: \n", html, "\n\n");
  564. // replace all fonts that matched the previous main font with new
  565. // main font. this is _most likely_ what the user expects:
  566. if (!isNull(previousMainFont)) {
  567. html.replace("font-family:'%1';".arg(previousMainFont.family()),
  568. "font-family:'%1';".arg(font.family()));
  569. }
  570. // qDebug("\n\nmainFontChanged: html after font replace: ", html, "\n\n");
  571. this.textEdit.setHtml(html);
  572. // qDebug("\n\nnew html: ", this.getTextDocument().toHtml(), "\n\n");
  573. this.updateSource(true);
  574. };
  575. TextDialog.prototype.fixHtml = function(html) {
  576. var font = this.getMainFont();
  577. //html = html.replace(/>[ ]*</, "><");
  578. var htmlNbsp = "";
  579. var cdata = false;
  580. for (var i=0; i<html.length; i++) {
  581. if (html[i]==='<') {
  582. cdata = false;
  583. }
  584. else if (html[i]==='>') {
  585. cdata = true;
  586. }
  587. // create nbsp from all spaces. QTextEdit will simplify white space
  588. // to one single space otherwise.
  589. else if (cdata && (html[i]===' ' || html[i]==='\u00A0')) {
  590. htmlNbsp += '&nbsp;';
  591. continue;
  592. }
  593. htmlNbsp += html[i];
  594. }
  595. // change font in HTML directly. changing default font of document
  596. // keeps content formatting by adding font style with previous
  597. // default font to all content:
  598. var dom = new QDomDocument();
  599. if (RSettings.getQtVersion()>=0x060500) {
  600. dom.setContent(htmlNbsp);
  601. }
  602. else {
  603. dom.setContent(htmlNbsp, false);
  604. }
  605. var docElem = dom.documentElement();
  606. //qDebug("initial font color: ", this.initialColor);
  607. var n = docElem.firstChild();
  608. while (!n.isNull()) {
  609. var e = n.toElement();
  610. if(!e.isNull()) {
  611. if (e.tagName().toLowerCase()==="body") {
  612. var qcol = this.initialColor.toCompat();
  613. var bodyStyle = ("font-family:'%1';" +
  614. "font-size:%2pt;" +
  615. "font-weight:%3;" +
  616. "font-style:%4;" +
  617. "color:%5;")
  618. .arg(font.family())
  619. .arg(font.pointSizeF() * this.fontHeightFactor)
  620. .arg(font.weight()<=QFont.Normal ? "normal" : "bold")
  621. .arg(font.italic() ? "italic" : "normal")
  622. .arg(qcol.name());
  623. // brighter background of text area in macOS dark mode:
  624. if (RSettings.hasDarkGuiBackground() && RS.getSystemId()==="osx") {
  625. bodyStyle += "background-color:#dddddd;";
  626. }
  627. e.setAttribute("style", bodyStyle);
  628. }
  629. }
  630. n = n.nextSibling();
  631. }
  632. return dom.toString(-1);
  633. };
  634. TextDialog.prototype.simpleTextToggled = function(enable) {
  635. if (this.mode!==TextDialog.Mode.Text) {
  636. return;
  637. }
  638. var editLineSpacingFactor = this.dialog.findChild("LineSpacingFactor");
  639. if (enable) {
  640. this.tabWidget.setTabEnabled(0, false);
  641. this.tabWidget.currentIndex = 1;
  642. editLineSpacingFactor.enabled = false;
  643. }
  644. else {
  645. this.tabWidget.setTabEnabled(0, true);
  646. this.tabWidget.currentIndex = 0;
  647. editLineSpacingFactor.enabled = true;
  648. }
  649. };
  650. TextDialog.prototype.mergeFormatOnWordOrSelection = function(format) {
  651. var cursor = this.textEdit.textCursor();
  652. if (!cursor.hasSelection()) {
  653. cursor.select(QTextCursor.WordUnderCursor);
  654. }
  655. cursor.mergeCharFormat(format);
  656. this.textEdit.mergeCurrentCharFormat(format);
  657. };
  658. /**
  659. * Called when user clicks bold button.
  660. */
  661. TextDialog.prototype.textBold = function() {
  662. var fmt = new QTextCharFormat();
  663. fmt.setFontWeight(this.actionTextBold.checked ? QFont.Bold : QFont.Normal);
  664. this.mergeFormatOnWordOrSelection(fmt);
  665. };
  666. /**
  667. * Called when user clicks italic button.
  668. */
  669. TextDialog.prototype.textItalic = function() {
  670. var fmt = new QTextCharFormat();
  671. fmt.setFontItalic(this.actionTextItalic.checked);
  672. this.mergeFormatOnWordOrSelection(fmt);
  673. };
  674. /**
  675. * Called when user clicks underline button.
  676. */
  677. TextDialog.prototype.textUnderline = function() {
  678. var fmt = new QTextCharFormat();
  679. fmt.setFontUnderline(this.actionTextUnderline.checked);
  680. this.mergeFormatOnWordOrSelection(fmt);
  681. };
  682. /**
  683. * Called when user clicks subscript button.
  684. */
  685. TextDialog.prototype.textSubscript = function() {
  686. var fmt = new QTextCharFormat();
  687. fmt.setVerticalAlignment(this.actionSubscript.checked ? QTextCharFormat.AlignSubScript : QTextCharFormat.AlignNormal);
  688. this.mergeFormatOnWordOrSelection(fmt);
  689. };
  690. /**
  691. * Called when user clicks superscript button.
  692. */
  693. TextDialog.prototype.textSuperscript = function() {
  694. var fmt = new QTextCharFormat();
  695. fmt.setVerticalAlignment(this.actionSuperscript.checked ? QTextCharFormat.AlignSuperScript : QTextCharFormat.AlignNormal);
  696. this.mergeFormatOnWordOrSelection(fmt);
  697. };
  698. /**
  699. * Called when user changes font.
  700. */
  701. TextDialog.prototype.textFamily = function(f) {
  702. var fmt = new QTextCharFormat();
  703. // Qt 5.13, 5.14, 5.15 bug: appends the font as fall-back instead of setting the font:
  704. //fmt.setFontFamily(f);
  705. fmt.setFontFamilies([f]);
  706. this.mergeFormatOnWordOrSelection(fmt);
  707. };
  708. /**
  709. * Called when user changes font size.
  710. */
  711. TextDialog.prototype.textSize = function(p) {
  712. var pointSize = parseFloat(p);
  713. if (pointSize > 0) {
  714. var fmt = new QTextCharFormat();
  715. fmt.setFontPointSize(pointSize * this.fontHeightFactor);
  716. this.mergeFormatOnWordOrSelection(fmt);
  717. }
  718. };
  719. /**
  720. * Called when user changes font color.
  721. */
  722. TextDialog.prototype.textColor = function() {
  723. var colorNameList = RColor.getNameList(false);
  724. var colorList = RColor.getColorList(false);
  725. if (colorNameList.length!==colorList.length) {
  726. qWarning("Not same number of colors as color names. Aborting...");
  727. return;
  728. }
  729. var menu = new QMenu(this.dialog);
  730. for (var i=0; i < colorNameList.length; ++i) {
  731. var label = colorNameList[i];
  732. var color = colorList[i];
  733. if (label==="---") {
  734. menu.addSeparator();
  735. }
  736. else {
  737. var action = menu.addAction(RColor.getIcon(color, new QSize(16,16)), label);
  738. action.setProperty("color", color);
  739. action.iconVisibleInMenu = true;
  740. }
  741. }
  742. var chosenAction = menu.exec(QCursor.pos());
  743. if (isNull(chosenAction)) {
  744. return;
  745. }
  746. var chosenColor = chosenAction.property("color");
  747. if (chosenColor.isValid()) {
  748. if (chosenColor.isByLayer()) {
  749. chosenColor = RColor.CompatByLayer;
  750. }
  751. else if (chosenColor.isByBlock()) {
  752. chosenColor = RColor.CompatByBlock;
  753. }
  754. else {
  755. chosenColor = new QColor(chosenColor.red(), chosenColor.green(), chosenColor.blue(), chosenColor.alpha());
  756. }
  757. }
  758. else {
  759. // custom color: show color dialog:
  760. chosenColor = QColorDialog.getColor(this.textEdit.textColor(), this.dialog);
  761. if (!chosenColor.isValid()) {
  762. // color dialog canceled:
  763. return;
  764. }
  765. }
  766. var fmt = new QTextCharFormat();
  767. fmt.setForeground(new QBrush(chosenColor));
  768. this.mergeFormatOnWordOrSelection(fmt);
  769. this.colorChanged(chosenColor);
  770. };
  771. /**
  772. * Called when user is about to insert a symbol (special character).
  773. */
  774. TextDialog.prototype.textSymbol = function() {
  775. var action;
  776. var menu = new QMenu(this.dialog);
  777. // common special characters
  778. var textWidget = this.dialog.findChild("Text");
  779. var charsMap = this.getSpecialCharsMap();
  780. var keys = charsMap.getKeys();
  781. for (var i = 0; i < keys.length; ++i) {
  782. var str = charsMap.get(keys[i])[0];
  783. var character = charsMap.get(keys[i])[1];
  784. action = menu.addAction(str + "\t" + character);
  785. var menuData = new SymbolMenuData(textWidget, character);
  786. action.triggered.connect(menuData, menuData.triggered);
  787. }
  788. // other characters
  789. menu.addSeparator();
  790. action = menu.addAction(qsTr("Others..."));
  791. action.triggered.connect(this, function(){
  792. var dlg = new CharacterMapDialog(this);
  793. dlg.show();
  794. });
  795. menu.exec(QCursor.pos());
  796. };
  797. /**
  798. * Character format under cursor changed. Update relevant controls.
  799. */
  800. TextDialog.prototype.currentCharFormatChanged = function(format) {
  801. this.fontChanged(format.font());
  802. this.colorChanged(format.foreground().color());
  803. this.actionSubscript.setChecked(format.verticalAlignment()==QTextCharFormat.AlignSubScript);
  804. this.actionSuperscript.setChecked(format.verticalAlignment()==QTextCharFormat.AlignSuperScript);
  805. };
  806. /**
  807. * Called when user changes the current font or the font under the cursor changed.
  808. */
  809. TextDialog.prototype.fontChanged = function(font) {
  810. activateFont(this.comboFont, font.family());
  811. this.comboSize.blockSignals(true);
  812. this.comboSize.setEditText("%1".arg(font.pointSizeF() / this.fontHeightFactor));
  813. this.comboSize.blockSignals(false);
  814. this.actionTextBold.setChecked(font.bold());
  815. this.actionTextItalic.setChecked(font.italic());
  816. this.actionTextUnderline.setChecked(font.underline());
  817. };
  818. /**
  819. * Called when user changes the current color or the color under the cursor changed.
  820. */
  821. TextDialog.prototype.colorChanged = function(c) {
  822. if (!RSettings.hasDarkGuiBackground()) {
  823. // make sure that very bright text is readable:
  824. if (c.lightness()>240) {
  825. var p = this.textEdit.palette;
  826. p.setColor(QPalette.Active, QPalette.Base, new QColor(Qt.lightGray));
  827. this.textEdit.palette = p;
  828. }
  829. }
  830. var pix = new QPixmap(16, 16);
  831. pix.fill(c);
  832. this.actionTextColor.icon = new QIcon(pix);
  833. };
  834. TextDialog.prototype.cursorPositionChanged = function() {
  835. //alignmentChanged(textEdit->alignment());
  836. };
  837. /**
  838. * Called to switch to default line spacing (factor 1.0).
  839. */
  840. /*
  841. TextDialog.prototype.useDefaultLineSpacing = function(checked) {
  842. var editLineSpacingFactor = this.dialog.findChild("LineSpacingFactor");
  843. if (checked) {
  844. editLineSpacingFactor.text = "1.0";
  845. }
  846. editLineSpacingFactor.enabled = !checked;
  847. };
  848. */
  849. /**
  850. * Called when clipboard text becomes available / unavailable.
  851. */
  852. TextDialog.prototype.clipboardDataChanged = function() {
  853. if (isDeleted(this.actionPaste)) {
  854. return;
  855. }
  856. this.actionPaste.setEnabled(!getClipboard().text().isEmpty());
  857. };
  858. TextDialog.prototype.tabChanged = function(index) {
  859. switch (index) {
  860. case 0:
  861. this.updateRichText(false);
  862. break;
  863. case 1:
  864. this.updateSource(false);
  865. break;
  866. }
  867. };
  868. /**
  869. * Updates the source based on the rich text edit.
  870. */
  871. TextDialog.prototype.updateSource = function(force) {
  872. if (this.getTextDocument().modified || force===true) {
  873. var html = this.textEdit.html;
  874. html = this.fixHtml(html);
  875. var textDocument = new QTextDocument();
  876. textDocument.setHtml(html);
  877. textDocument.defaultFont = this.getTextDocument().defaultFont;
  878. var cbSimpleText = this.dialog.findChild("SimpleText");
  879. var source = RTextBasedData.toEscapedText(textDocument, this.initialColor, this.fontHeightFactor, cbSimpleText.checked);
  880. //qDebug("source: \n\n", source, "\n\n");
  881. this.sourceEdit.setPlainText(source);
  882. this.getTextDocument().modified = false;
  883. }
  884. };
  885. /**
  886. * Updates the rich text edit based on the source.
  887. */
  888. TextDialog.prototype.updateRichText = function(force) {
  889. if (this.getSourceDocument().modified || force===true) {
  890. var source = this.getSourceDocument().toPlainText();
  891. source = source.replace("\n", "\\P");
  892. var richText = RTextBasedData.toRichText(source, this.getMainFont(), this.fontHeightFactor);
  893. //richText = richText.replace("<html>", "<html xmlns='http://www.w3.org/1999/xhtml' xml:space='preserve'>");
  894. //qDebug("richText: \n\n", richText, "\n\n");
  895. // TODO: preserve tabs (img with arrow?)
  896. this.textEdit.setHtml(richText);
  897. //qDebug("HTML from text edit: \n\n", this.textEdit.html, "\n\n");
  898. this.getSourceDocument().modified = false;
  899. }
  900. };
  901. //TextDialog.prototype.activateFont = function(fontComboBox, fontName) {
  902. // var index = fontComboBox.findText(fontName, Qt.MatchFixedString);
  903. // fontComboBox.setCurrentIndex(index);
  904. //};
  905. TextDialog.prototype.getSpecialCharsMap = function() {
  906. if (isNull(TextDialog.specialChars)) {
  907. TextDialog.specialChars = new Map();
  908. TextDialog.specialChars.put("degree", [ qsTr("Degree"), "\u00B0" ]);
  909. TextDialog.specialChars.put("plus_minus", [ qsTr("Plus-Minus"), "\u00B1" ]);
  910. //TextDialog.specialChars.put("diameter", [ qsTr("Diameter"), "\u2300" ]);
  911. TextDialog.specialChars.put("diameter", [ qsTr("Diameter"), "\u00F8" ]);
  912. TextDialog.specialChars.put("allmost_equal", [ qsTr("Almost equal"), "\u2248" ]);
  913. TextDialog.specialChars.put("multiplication", [ qsTr("Multiplication"), "\u00D7" ]);
  914. TextDialog.specialChars.put("division", [ qsTr("Division"), "\u00F7" ]);
  915. TextDialog.specialChars.put("delta", [ qsTr("Delta"), "\u0394" ]);
  916. TextDialog.specialChars.put("pi", [ qsTr("Pi"), "\u03C0" ]);
  917. TextDialog.specialChars.put("identity", [ qsTr("Identity"), "\u2261" ]);
  918. TextDialog.specialChars.put("not_equal", [ qsTr("Not equal"), "\u2260" ]);
  919. TextDialog.specialChars.put("ohm", [ qsTr("Ohm"), "\u2126" ]);
  920. TextDialog.specialChars.put("omega", [ qsTr("Omega"), "\u03A9" ]);
  921. TextDialog.specialChars.put("squared", [ qsTr("Squared"), "\u00B2" ]);
  922. TextDialog.specialChars.put("cubed", [ qsTr("Cubed"), "\u00B3" ]);
  923. TextDialog.specialChars.put("nbsp", [ qsTr("No-break space"), "&nbsp;" ]);
  924. }
  925. return TextDialog.specialChars;
  926. };