AbstractPreferences.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646
  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("../AddOn.js");
  20. include("Edit.js");
  21. function AbstractPreferences(guiAction, appPreferences, initialClassName) {
  22. Edit.call(this, guiAction);
  23. if (isNull(appPreferences)) {
  24. appPreferences = true;
  25. }
  26. this.appPreferences = appPreferences;
  27. this.restartWarningShown = false;
  28. this.initialClassName = initialClassName;
  29. }
  30. AbstractPreferences.prototype = new Edit();
  31. AbstractPreferences.includeBasePath = includeBasePath;
  32. AbstractPreferences.prototype.beginEvent = function() {
  33. Edit.prototype.beginEvent.call(this);
  34. this.dialog = this.createWidget(AbstractPreferences.includeBasePath + "/AbstractPreferences.ui");
  35. // TODO: Qt 5: add this flag (?)
  36. //var flags = new Qt.WindowFlags(Qt.WindowTitleHint);
  37. //this.dialog.setWindowFlags(flags);
  38. this.treeWidget = this.dialog.findChild("twCategory");
  39. var title;
  40. if (this.appPreferences) {
  41. title = qsTr("Application Preferences");
  42. } else {
  43. title = qsTr("Drawing Preferences");
  44. }
  45. this.dialog.setWindowTitle(title);
  46. this.treeWidget.setHeaderLabel(title);
  47. this.pageWidget = this.dialog.findChild("stwPage");
  48. this.filterWidget = this.dialog.findChild("leFilter");
  49. this.titleWidget = this.dialog.findChild("lbTitle");
  50. var splitter = this.dialog.findChild("splitter");
  51. splitter.setStretchFactor(0, 1);
  52. splitter.setStretchFactor(1, 4);
  53. // connections:
  54. this.treeWidget.itemSelectionChanged.connect(this, this.showPage);
  55. this.filterWidget.textChanged.connect(this, this.filterTree);
  56. var btApply = this.dialog.findChild("buttonBox").button(QDialogButtonBox.Apply);
  57. btApply.clicked.connect(this, this.apply);
  58. this.addOns = AddOn.getAddOns();
  59. AbstractPreferences.fillTreeWidget(this.addOns, this.treeWidget, this.appPreferences);
  60. this.treeWidget.expandAll();
  61. if (!isNull(this.initialClassName)) {
  62. this.showPageFor(this.initialClassName);
  63. }
  64. if (this.dialog.exec() === QDialog.Accepted.valueOf()) {
  65. // apply calls save and apply:
  66. this.apply();
  67. }
  68. this.uninit();
  69. destr(this.dialog);
  70. EAction.activateMainWindow();
  71. this.terminate();
  72. };
  73. /**
  74. * Initializes the navigation tree based on available add-ons with preferences.
  75. */
  76. AbstractPreferences.fillTreeWidget = function(addOns, treeWidget, appPreferences) {
  77. for (var i = 0; i < addOns.length; ++i) {
  78. var addOn = addOns[i];
  79. var className = addOn.getClassName();
  80. var scriptFile = addOn.getFilePath();
  81. if (appPreferences===false) {
  82. if (!scriptFile.contains("DrawingPreferences")) {
  83. continue;
  84. }
  85. }
  86. try {
  87. // include normally not needed
  88. var doInclude = false;
  89. if (isNull(global[className])) {
  90. doInclude = true;
  91. }
  92. if(doInclude) {
  93. include(addOn.getFilePath());
  94. }
  95. } catch (e1) {
  96. qWarning("AppPreferences.js: Exception: ", e1);
  97. continue;
  98. }
  99. var cat;
  100. cat = undefined;
  101. try {
  102. if (!isNull(global[className]) && isFunction(global[className].getPreferencesCategory)) {
  103. cat = global[className].getPreferencesCategory(appPreferences);
  104. }
  105. if (isNull(cat)) {
  106. continue;
  107. }
  108. } catch (e2) {
  109. qWarning("AppPreferences.js: Add-on class not found: ", className, " exception: ", e2);
  110. continue;
  111. }
  112. if (!addOn.preferenceFileExists()) {
  113. continue;
  114. }
  115. // update tree
  116. var items = treeWidget.findItems(cat[0], Qt.MatchFixedString);
  117. var item;
  118. item = undefined;
  119. if (items.length === 0) {
  120. item = new QTreeWidgetItem(treeWidget, [ cat[0] ]);
  121. treeWidget.addTopLevelItem(item);
  122. if (cat.length === 1) {
  123. item.setData(0, Qt.UserRole, i);
  124. }
  125. } else {
  126. item = items[0];
  127. }
  128. var parent = item;
  129. for (var x = 1; x < cat.length; ++x) {
  130. var subitem;
  131. subitem = undefined;
  132. for (var c = 0; c < parent.childCount(); ++c) {
  133. var child = parent.child(c);
  134. if (child.text(0) === cat[x]) {
  135. subitem = child;
  136. break;
  137. }
  138. }
  139. if (isNull(subitem)) {
  140. subitem = new QTreeWidgetItem(parent, [ cat[x] ]);
  141. parent.addChild(subitem);
  142. if (x == cat.length - 1) {
  143. subitem.setData(0, Qt.UserRole, i);
  144. }
  145. }
  146. parent = subitem;
  147. }
  148. }
  149. treeWidget.sortItems(0, Qt.AscendingOrder);
  150. };
  151. /**
  152. * Applies the settings of all preference pages by calling
  153. * 'applyPreferences' for every add-on class.
  154. */
  155. AbstractPreferences.prototype.apply = function() {
  156. var mdiChild, document;
  157. // force all settings to be reloaded from RSettings when they are
  158. // used next time:
  159. RSettings.resetCache();
  160. this.save();
  161. for (var i=0; i<this.addOns.length; ++i) {
  162. var addOn = this.addOns[i];
  163. var className = addOn.getClassName();
  164. var widget = addOn.getPreferenceWidget();
  165. if (isNull(widget)) {
  166. continue;
  167. }
  168. var hasChanged = widget.property("hasChanged");
  169. if (hasChanged !== true) {
  170. continue;
  171. }
  172. try {
  173. // include normally not needed
  174. var doInclude = false;
  175. if (isNull(global[className])) {
  176. doInclude = true;
  177. }
  178. if (doInclude) {
  179. include(addOn.getFilePath());
  180. }
  181. // apply application settings globally:
  182. if (this.appPreferences) {
  183. if (!isNull(global[className]) && isFunction(global[className].applyPreferences)) {
  184. global[className].applyPreferences(undefined, undefined, widget);
  185. }
  186. var mdiArea = EAction.getMdiArea();
  187. var mdiChildren = mdiArea.subWindowList();
  188. for (var j = 0; j < mdiChildren.length; ++j) {
  189. mdiChild = mdiChildren[j];
  190. document = mdiChild.getDocument();
  191. if (!isNull(global[className]) && isFunction(global[className].applyPreferences)) {
  192. global[className].applyPreferences(document, mdiChild, widget);
  193. }
  194. }
  195. }
  196. // apply document specific settings to current document:
  197. else {
  198. mdiChild = EAction.getMdiChild();
  199. document = EAction.getDocument();
  200. if (!isNull(global[className]) && isFunction(global[className].applyPreferences)) {
  201. global[className].applyPreferences(document, mdiChild, widget);
  202. }
  203. }
  204. } catch (e) {
  205. qWarning("AbstractPreferences.js:",
  206. "apply(): Exception: %1; %2; %3"
  207. .arg(e.message).arg(e.fileName).arg(e.lineNumber));
  208. continue;
  209. }
  210. }
  211. var di = EAction.getDocumentInterface();
  212. if (!isNull(di)) {
  213. di.regenerateScenes();
  214. }
  215. var appWin = EAction.getMainWindow();
  216. if (this.appPreferences) {
  217. appWin.notifyPreferencesListeners(null);
  218. }
  219. else {
  220. appWin.notifyPreferencesListeners(di);
  221. }
  222. };
  223. /**
  224. * Cleans up settings of all preference pages by calling
  225. * 'uninitPreferences' for every add-on class.
  226. */
  227. AbstractPreferences.prototype.uninit = function() {
  228. var mdiChild, document;
  229. for (var i = 0; i < this.addOns.length; ++i) {
  230. var addOn = this.addOns[i];
  231. var className = addOn.getClassName();
  232. var widget = addOn.getPreferenceWidget();
  233. // break link from add on to preference widget (preference widget is deleted with dialog):
  234. addOn.setPreferenceWidget(undefined);
  235. if (isNull(widget)) {
  236. continue;
  237. }
  238. var hasChanged = widget.property("hasChanged");
  239. if (hasChanged !== true) {
  240. continue;
  241. }
  242. try {
  243. // include normally not needed
  244. var doInclude = false;
  245. if (isNull(global[className])) {
  246. doInclude = true;
  247. }
  248. if (doInclude) {
  249. include(addOn.getFilePath());
  250. }
  251. // uninit application settings globally:
  252. if (this.appPreferences) {
  253. if (!isNull(global[className]) && isFunction(global[className].uninitPreferences)) {
  254. global[className].uninitPreferences(undefined, undefined, widget);
  255. }
  256. }
  257. // uninit document specific settings to current document:
  258. else {
  259. mdiChild = EAction.getMdiChild();
  260. document = EAction.getDocument();
  261. if (!isNull(global[className]) && isFunction(global[className].uninitPreferences)) {
  262. global[className].uninitPreferences(document, mdiChild, widget);
  263. }
  264. }
  265. } catch (e) {
  266. qWarning("AbstractPreferences.js:",
  267. "uninit(): Exception: %1; %2; %3"
  268. .arg(e.message).arg(e.fileName).arg(e.lineNumber));
  269. continue;
  270. }
  271. }
  272. };
  273. /**
  274. * Loads the preference page of the given add-on.
  275. * Called by 'showPage'.
  276. */
  277. AbstractPreferences.prototype.load = function(addOn) {
  278. var widget = addOn.getPreferenceWidget();
  279. if (widget == undefined) {
  280. return;
  281. }
  282. // load preferences
  283. var className = addOn.getClassName();
  284. var document;
  285. document = undefined;
  286. if (!this.appPreferences) {
  287. document = EAction.getDocument();
  288. }
  289. if (!isNull(global[className]) && isFunction(global[className].initPreferences)) {
  290. global[className].initPreferences(widget, true, document, this);
  291. }
  292. // check if preferences apply to block:
  293. var store = document;
  294. if (!this.appPreferences) {
  295. var preferencesScope = "document";
  296. if (!isNull(global[className]) && isFunction(global[className].getPreferencesScope)) {
  297. if (document.getCurrentBlockId()!==document.getModelSpaceBlockId()) {
  298. preferencesScope = global[className].getPreferencesScope();
  299. }
  300. }
  301. if (!isNull(document) && preferencesScope==="block") {
  302. store = document.queryCurrentBlock();
  303. }
  304. }
  305. WidgetFactory.restoreState(widget, undefined, undefined, false, store);
  306. if (!isNull(global[className]) && isFunction(global[className].postInitPreferences)) {
  307. global[className].postInitPreferences(widget, true, document, this);
  308. }
  309. };
  310. /**
  311. * Saves all preferences to the current document (this.appPreferences==false)
  312. * or to a global settings file (this.appPreferences==true).
  313. */
  314. AbstractPreferences.prototype.save = function() {
  315. var document = undefined;
  316. var transaction = undefined;
  317. if (!this.appPreferences) {
  318. document = EAction.getDocument();
  319. transaction = new RTransaction(document.getStorage(), qsTr("Apply preferences"), true);
  320. }
  321. for (var i = 0; i < this.addOns.length; ++i) {
  322. var addOn = this.addOns[i];
  323. var widget = addOn.getPreferenceWidget();
  324. if (isNull(widget)) {
  325. continue;
  326. }
  327. var hasChanged = widget.property("hasChanged");
  328. if (hasChanged !== true) {
  329. continue;
  330. }
  331. var className = addOn.getClassName();
  332. if (!isNull(global[className]) && isFunction(global[className].savePreferences)) {
  333. global[className].savePreferences(widget, true, document, transaction);
  334. }
  335. // check if preferences apply to block:
  336. var store = document;
  337. var preferencesScope = "document";
  338. if (!this.appPreferences) {
  339. if (!isNull(global[className]) && isFunction(global[className].getPreferencesScope)) {
  340. if (document.getCurrentBlockId()!==document.getModelSpaceBlockId()) {
  341. preferencesScope = global[className].getPreferencesScope();
  342. }
  343. }
  344. if (!isNull(document) && preferencesScope==="block") {
  345. store = document.queryCurrentBlock();
  346. }
  347. }
  348. WidgetFactory.saveState(widget, undefined, store);
  349. if (!this.appPreferences) {
  350. if (preferencesScope==="block") {
  351. transaction.addObject(store);
  352. }
  353. }
  354. }
  355. if (!this.appPreferences) {
  356. transaction.end();
  357. }
  358. // check if preference changes require application restart and show
  359. // message if appropriate:
  360. if (WidgetFactory.requiresRestart===true && this.restartWarningShown === false) {
  361. var appWin = EAction.getMainWindow();
  362. QMessageBox.warning(appWin,
  363. qsTr("Restart required"),
  364. qsTr("Please restart QCAD for\nthe preference changes to take effect.")
  365. );
  366. this.restartWarningShown = true;
  367. }
  368. WidgetFactory.requiresRestart = false;
  369. };
  370. /**
  371. * Updates the navigation tree based on the given filter string.
  372. */
  373. AbstractPreferences.prototype.updateTreeWidget = function(filterText) {
  374. if (isNull(filterText)) {
  375. filterText = "";
  376. }
  377. var rexp;
  378. try{
  379. rexp = new RegExp(filterText, "i");
  380. } catch (e) {
  381. rexp = new RegExp(".*", "i");
  382. }
  383. for(var i=0; i<this.treeWidget.topLevelItemCount; ++i) {
  384. var item = this.treeWidget.topLevelItem(i);
  385. var match = item.text(0).match(rexp);
  386. var found = this.filterItems(item, rexp, match);
  387. if (found || match) {
  388. item.setHidden(false);
  389. item.setExpanded(true);
  390. } else if(!match) {
  391. item.setHidden(true);
  392. }
  393. }
  394. };
  395. /**
  396. * \internal
  397. */
  398. AbstractPreferences.prototype.filterItems = function(item, rexp, showAll) {
  399. var foundAny = false;
  400. for(var i=0; i<item.childCount(); ++i) {
  401. var subitem = item.child(i);
  402. var match = subitem.text(0).match(rexp);
  403. var found = this.filterItems(subitem, rexp, match);
  404. if (found || match) {
  405. subitem.setHidden(false);
  406. subitem.setExpanded(true);
  407. foundAny = true;
  408. } else if(!match) {
  409. if(!showAll) {
  410. subitem.setHidden(true);
  411. }
  412. }
  413. }
  414. return foundAny;
  415. };
  416. /**
  417. * Called when user enters a filter text for the navigation tree.
  418. */
  419. AbstractPreferences.prototype.filterTree = function(text) {
  420. this.updateTreeWidget(text);
  421. };
  422. /**
  423. * Loads and shows the appropriate preferences page for the
  424. * current selected item in the navigation tree. Called
  425. * when the user selects an item in the tree.
  426. */
  427. AbstractPreferences.prototype.showPage = function() {
  428. var items = this.treeWidget.selectedItems();
  429. if (items.length!==1) {
  430. return;
  431. }
  432. var item = items[0];
  433. if (isNull(item)) {
  434. return;
  435. }
  436. var parent = item.parent();
  437. var warningLabel = this.dialog.findChild("Warning");
  438. warningLabel.text = "";
  439. warningLabel.visible = false;
  440. if (!isNull(parent)) {
  441. if (parent.text(0).contains(qsTr("Defaults for"))) {
  442. var col = "red";
  443. if (RSettings.hasDarkGuiBackground()) {
  444. col = "#C00000";
  445. }
  446. warningLabel.text = "<font color='" + col + "'>" +
  447. qsTr("These are default preferences for new drawings.") + "<br/>" +
  448. qsTr("Changes will affect new drawings but NOT the current drawing.") + "<br/>" +
  449. qsTr("Preferences of the current drawing can be changed under<br/><i>Edit &gt; Drawing Preferences</i>.") +
  450. "</font>";
  451. warningLabel.visible = true;
  452. }
  453. }
  454. var pText = "";
  455. while (!isNull(parent)) {
  456. pText += parent.text(0) + ": " + pText;
  457. parent = parent.parent();
  458. }
  459. if (pText == "") {
  460. this.titleWidget.text = item.text(0);
  461. } else {
  462. this.titleWidget.text = pText + item.text(0);
  463. }
  464. var i = item.data(0, Qt.UserRole);
  465. var widget;
  466. if (!isNull(i)) {
  467. var addOn = this.addOns[i];
  468. widget = addOn.getPreferenceWidget();
  469. if (isNull(widget)) {
  470. var prefFile = addOn.getPreferenceFile();
  471. widget = this.createWidget(prefFile);
  472. this.pageWidget.addWidget(widget);
  473. addOn.setPreferenceWidget(widget);
  474. // loads (initializes) the page:
  475. this.load(addOn);
  476. var treeWidget = this.treeWidget;
  477. widget.settingChangedEvent = function() {
  478. // mark changed category in bold:
  479. var font = treeWidget.currentItem().font(0);
  480. font.setBold(true);
  481. treeWidget.currentItem().setFont(0, font);
  482. };
  483. var btReset = this.dialog.findChild("ResetToDefaults");
  484. try {
  485. btReset.clicked.disconnect(this, this.reset);
  486. } catch (e) {
  487. // ignored: signal might not be connected
  488. }
  489. btReset.clicked.connect(this, this.reset);
  490. }
  491. } else {
  492. widget = this.pageWidget.findChild("empty");
  493. }
  494. this.pageWidget.setCurrentWidget(widget);
  495. this.treeWidget.setCurrentItem(item);
  496. };
  497. AbstractPreferences.prototype.showPageFor = function(className) {
  498. var flags = makeQtMatchFlags(Qt.MatchWildcard, Qt.MatchContains, Qt.MatchRecursive);
  499. var items = this.treeWidget.findItems("*", flags, 0);
  500. for (var i = 0; i < items.length; ++i) {
  501. var item = items[i];
  502. var ix = item.data(0, Qt.UserRole);
  503. if (!isNull(ix)) {
  504. var addOn = this.addOns[ix];
  505. if (addOn.getClassName()===className) {
  506. this.treeWidget.setCurrentItem(item);
  507. }
  508. }
  509. }
  510. };
  511. /**
  512. * Called when user clicks reset button on preferences page.
  513. */
  514. AbstractPreferences.prototype.reset = function() {
  515. var widget = this.pageWidget.currentWidget();
  516. //qDebug("AbstractPreferences.js:", "reset(): widget.objectName:", widget.objectName);
  517. WidgetFactory.resetState(widget);
  518. var treeWidget = this.treeWidget;
  519. var font = treeWidget.currentItem().font(0);
  520. font.setBold(true);
  521. treeWidget.currentItem().setFont(0, font);
  522. widget.setProperty("hasChanged", true);
  523. };
  524. /**
  525. * Called when the global or drawing unit changes. Notifies all loaded
  526. * add-on pages.
  527. */
  528. AbstractPreferences.prototype.unitUpdated = function(unit) {
  529. for (var i=0; i<this.addOns.length; ++i) {
  530. var addOn = this.addOns[i];
  531. var className = addOn.getClassName();
  532. // pref page of this add-on is not opened yet, skip:
  533. var widget = addOn.getPreferenceWidget();
  534. if (isNull(widget)) {
  535. continue;
  536. }
  537. if (!isNull(global[className]) && isFunction(global[className].updateUnit)) {
  538. global[className].updateUnit(unit);
  539. }
  540. }
  541. };
  542. /**
  543. * Called when the global or drawing paper unit changes. Notifies all loaded
  544. * add-on pages.
  545. */
  546. AbstractPreferences.prototype.paperUnitUpdated = function(unit) {
  547. for (var i=0; i<this.addOns.length; ++i) {
  548. var addOn = this.addOns[i];
  549. var className = addOn.getClassName();
  550. // pref page of this add-on is not opened yet, skip:
  551. var widget = addOn.getPreferenceWidget();
  552. if (isNull(widget)) {
  553. continue;
  554. }
  555. if (!isNull(global[className]) && isFunction(global[className].updatePaperUnit)) {
  556. global[className].updatePaperUnit(unit);
  557. }
  558. }
  559. };
  560. AbstractPreferences.prototype.linearFormatUpdated = function(linearFormat) {
  561. for (var i=0; i<this.addOns.length; ++i) {
  562. var addOn = this.addOns[i];
  563. var className = addOn.getClassName();
  564. // pref page of this add-on is not opened yet, skip:
  565. var widget = addOn.getPreferenceWidget();
  566. if (isNull(widget)) {
  567. continue;
  568. }
  569. if (!isNull(global[className]) && isFunction(global[className].updateLinearFormat)) {
  570. global[className].updateLinearFormat(linearFormat);
  571. }
  572. }
  573. };