EcmaScriptShell.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /**
  2. * Copyright (c) 2011-2018 by Andrew Mustun. All rights reserved.
  3. *
  4. * DirectDistanceEntry handling added 2013 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("scripts/WidgetFactory.js");
  22. include("scripts/simple.js");
  23. /**
  24. * \class EcmaScriptShell
  25. * \brief Dockable script shell with history.
  26. * \ingroup ecma_misc_development
  27. */
  28. function EcmaScriptShell(guiAction) {
  29. EAction.call(this, guiAction);
  30. }
  31. /*
  32. EcmaScriptShell.getPreferencesCategory = function() {
  33. return [ qsTr("Widgets"), qsTr("ECMA Script Shell") ];
  34. };
  35. */
  36. EcmaScriptShell.prototype = new EAction();
  37. EcmaScriptShell.includeBasePath = includeBasePath;
  38. /**
  39. * Shows / hides the script shell widget.
  40. */
  41. EcmaScriptShell.prototype.beginEvent = function() {
  42. EAction.prototype.beginEvent.call(this);
  43. var appWin = RMainWindowQt.getMainWindow();
  44. var dock = appWin.findChild("EcmaScriptShellDock");
  45. if (!RSettings.getOriginalArguments().contains("-no-show")) {
  46. if (dock.visible===false) {
  47. // show warning:
  48. if (RSettings.getBoolValue("EcmaScriptShell/DontShowDialog", false)!==true) {
  49. var dialog = WidgetFactory.createDialog(EcmaScriptShell.includeBasePath, "EcmaScriptShellDialog.ui", appWin);
  50. var bb = dialog.findChild("ButtonBox");
  51. var b = bb.addButton(qsTr("Show ECMAScript Shell"), QDialogButtonBox.AcceptRole);
  52. b["default"] = false;
  53. bb.button(QDialogButtonBox.Cancel)["default"] = true;
  54. var l = dialog.findChild("Message");
  55. l.openExternalLinks = true;
  56. var ret = dialog.exec();
  57. if (ret!==QDialog.Accepted.valueOf()) {
  58. destr(dialog);
  59. EAction.activateMainWindow();
  60. return;
  61. }
  62. WidgetFactory.saveState(dialog);
  63. destr(dialog);
  64. EAction.activateMainWindow();
  65. }
  66. }
  67. dock.visible = !dock.visible;
  68. if (dock.visible) dock.raise();
  69. }
  70. };
  71. EcmaScriptShell.prototype.finishEvent = function() {
  72. EAction.prototype.finishEvent.call(this);
  73. var appWin = RMainWindowQt.getMainWindow();
  74. var dock = appWin.findChild("EcmaScriptShellDock");
  75. this.getGuiAction().setChecked(dock.visible);
  76. };
  77. //EcmaScriptShell.prototype.openUrl = function(url) {
  78. // QDesktopServices.openUrl(url);
  79. //};
  80. EcmaScriptShell.col = "#000";
  81. EcmaScriptShell.colWarning = "#cc0000";
  82. EcmaScriptShell.colPrompt = "#0000cc";
  83. EcmaScriptShell.initStyle = function() {
  84. if (RSettings.hasDarkGuiBackground()) {
  85. EcmaScriptShell.col = "#fff";
  86. EcmaScriptShell.colWarning = "#cc0000";
  87. EcmaScriptShell.colPrompt = "#2E9AFE";
  88. }
  89. else {
  90. EcmaScriptShell.col = "#000";
  91. EcmaScriptShell.colWarning = "#cc0000";
  92. EcmaScriptShell.colPrompt = "#0000cc";
  93. }
  94. };
  95. EcmaScriptShell.init = function(basePath) {
  96. var appWin = EAction.getMainWindow();
  97. var action = new RGuiAction(qsTr("&Script Shell"), appWin);
  98. action.setRequiresDocument(false);
  99. action.setScriptFile(basePath + "/EcmaScriptShell.js");
  100. action.setIcon(basePath + "/EcmaScriptShell.svg");
  101. action.setDefaultShortcut(new QKeySequence("g,e"));
  102. action.setDefaultCommands(["ge"]);
  103. action.setGroupSortOrder(3800);
  104. action.setSortOrder(100);
  105. action.setWidgetNames(["MiscDevelopmentMenu", "MiscDevelopmentToolBar", "MiscDevelopmentToolsPanel"]);
  106. EcmaScriptShell.initStyle();
  107. var pl = new RPaletteListenerAdapter();
  108. appWin.addPaletteListener(pl);
  109. pl.paletteChanged.connect(EcmaScriptShell.initStyle);
  110. var formWidget = WidgetFactory.createWidget(basePath, "EcmaScriptShell.ui");
  111. var frame = formWidget.findChild("Frame");
  112. // var p = frame.palette;
  113. // if (!RSettings.hasDarkGuiBackground()) {
  114. // // white background of command line label:
  115. // p.setColor(QPalette.Active, QPalette.Window, new QColor(Qt.white));
  116. // }
  117. // else {
  118. // p.setColor(QPalette.Active, QPalette.Window, new QColor("#1e1e1e"));
  119. // }
  120. // frame.palette = p;
  121. // frame.autoFillBackground = true;
  122. var teHistory = formWidget.findChild("History");
  123. var leCommand = formWidget.findChild("CommandEdit");
  124. leCommand.setHistory(RSettings.getStringListValue("EcmaScriptShell/History", []));
  125. var lCommand = formWidget.findChild("CommandLabel");
  126. var bToggleTitleBar = formWidget.findChild("ToggleTitleBar");
  127. var dock = new RDockWidget(qsTr("Script Shell"), appWin);
  128. dock.objectName = "EcmaScriptShellDock";
  129. dock.setWidget(formWidget);
  130. appWin.addDockWidget(Qt.RightDockWidgetArea, dock);
  131. dock.shown.connect(function() { action.setChecked(true); });
  132. dock.hidden.connect(function() { action.setChecked(false); });
  133. dock.visible = false;
  134. if (!isNull(warning)) {
  135. // initialize simple API warning handler:
  136. warning.handler = function(msg) {
  137. appendAndScroll("<span style='color:"+EcmaScriptShell.colWarning+"'>WARNING: " + RS.escape(msg) + "</span>");
  138. };
  139. }
  140. function appendAndScroll(msg) {
  141. teHistory.append(msg);
  142. var c = teHistory.textCursor();
  143. c.movePosition(QTextCursor.End);
  144. teHistory.setTextCursor(c);
  145. teHistory.ensureCursorVisible();
  146. //qDebug(teHistory.html);
  147. var appWin = EAction.getMainWindow();
  148. var dlg = appWin.findChild("ProgressDialog");
  149. if (!appWin.enabled || (!isNull(dlg) && dlg.visible)) {
  150. QCoreApplication.processEvents();
  151. }
  152. }
  153. var showTitleBar = RSettings.getBoolValue("EcmaScriptShell/ShowTitleBar", false);
  154. if (!showTitleBar) {
  155. dock.setTitleBarWidget(new QWidget(dock));
  156. bToggleTitleBar.checked = false;
  157. }
  158. else {
  159. bToggleTitleBar.checked = true;
  160. }
  161. // hide title bar of command line dock widget:
  162. bToggleTitleBar.toggled.connect(
  163. function(show) {
  164. if (show) {
  165. dock.setTitleBarWidget(null);
  166. RSettings.setValue("EcmaScriptShell/ShowTitleBar", true);
  167. }
  168. else {
  169. dock.setTitleBarWidget(new QWidget(dock));
  170. RSettings.setValue("EcmaScriptShell/ShowTitleBar", false);
  171. }
  172. }
  173. );
  174. var expression = "";
  175. var bracketsCountR = 0;
  176. var bracketsCountC = 0;
  177. var bracketsCountS = 0;
  178. lCommand.text = ">";
  179. // user pressed enter, eval command:
  180. function countBraces(str) {
  181. for (var i=0, len=str.length; i<len; ++i) {
  182. switch(str[i]) {
  183. case '(': ++bracketsCountR; break;
  184. case ')': --bracketsCountR; break;
  185. case '{': ++bracketsCountC; break;
  186. case '}': --bracketsCountC; break;
  187. case '[': ++bracketsCountS; break;
  188. case ']': --bracketsCountS; break;
  189. }
  190. if (bracketsCountR < 0 || bracketsCountC < 0 || bracketsCountS < 0) {
  191. // error:
  192. return false;
  193. }
  194. }
  195. return true;
  196. }
  197. leCommand.escape.connect(function() {
  198. leCommand.setProperty("OpenBrackets", bracketsCountR+bracketsCountC+bracketsCountS);
  199. bracketsCountR = 0;
  200. bracketsCountC = 0;
  201. bracketsCountS = 0;
  202. lCommand.text = ">";
  203. expression = "";
  204. });
  205. leCommand.commandConfirmed.connect(function(command) {
  206. // save history:
  207. var h = leCommand.getHistory();
  208. if (h.length>100) {
  209. h = h.slice(-100);
  210. }
  211. RSettings.setValue("EcmaScriptShell/History", h);
  212. leCommand.clear();
  213. var historySize = RSettings.getIntValue("EcmaScriptShell/HistorySize", 1000);
  214. var buf = teHistory.plainText.split("\n");
  215. if (buf.length > historySize) {
  216. teHistory.setPlainText(buf.slice(-historySize).join("\n"));
  217. }
  218. appendAndScroll("<span style='font-style:italic;color:"+EcmaScriptShell.colPrompt+";'>" + RS.escape("ecma> ") + "</span>"
  219. + "<span style='color:"+EcmaScriptShell.col+"'>" + RS.escape(command) + "</span>");
  220. leCommand.appendCommand(command);
  221. // if we have open brackets: continue entering:
  222. countBraces(command);
  223. expression+=command + "\n";
  224. if (bracketsCountR>0 || bracketsCountC>0 || bracketsCountS>0) {
  225. lCommand.text = "...";
  226. }
  227. else {
  228. var res;
  229. try {
  230. //startTransaction();
  231. //res = RMainWindow.getMainWindow().eval("js", expression);
  232. res = EcmaScriptShell.eval(expression);
  233. //endTransaction();
  234. // make sure input is enabled again:
  235. if (!isInputEnabled()) {
  236. enableInput();
  237. }
  238. if (res==undefined) {
  239. res = "undefined";
  240. }
  241. appendAndScroll("<span style='color:"+EcmaScriptShell.col+";'>" + RS.escape(res.toString()) + "</span>");
  242. }
  243. catch (e) {
  244. appendAndScroll("<span style='color:"+EcmaScriptShell.colWarning+";'>" + RS.escape(e.toString()) + "</span>");
  245. //qDebug("error: ", e);
  246. //qDebug("error: res:", res);
  247. }
  248. expression = "";
  249. bracketsCountR = 0;
  250. bracketsCountC = 0;
  251. bracketsCountS = 0;
  252. lCommand.text = ">";
  253. }
  254. leCommand.setFocus();
  255. });
  256. // user pressed tab:
  257. leCommand.completeCommand.connect(function(command) {
  258. // look from cursor pos backwards:
  259. //var toComplete = command.substring(0, leCommand.cursorPosition);
  260. var i2 = leCommand.cursorPosition;
  261. var i1 = command.regexLastIndexOf(/[^a-zA-Z0-9_]/, i2) + 1;
  262. var i0 = 0;
  263. if (i1>2) {
  264. i0 = command.regexLastIndexOf(/[^a-zA-Z0-9_]/, i1-2) + 1;
  265. }
  266. var objName = command.substring(i0, i1-1);
  267. var toComplete = command.substring(i1, i2);
  268. var obj = global;
  269. if (objName.length>0) {
  270. obj = eval(objName);
  271. }
  272. var choices = [];
  273. //choices = Object.keys(obj);
  274. for (var k in obj) {
  275. choices.push(k);
  276. }
  277. choices = choices.filter(function(e) { return e.startsWith(toComplete); });
  278. if (choices.length === 0) {
  279. // nothing to match
  280. } else if (choices.length === 1) {
  281. // only one match. complete it:
  282. //qDebug("inserting:",choices[0].substring(i2-i1));
  283. leCommand.insert(choices[0].substring(i2-i1));
  284. } else {
  285. // suggest multiple choices that match so far:
  286. appendAndScroll(choices.join(", "));
  287. // complete as much as possible:
  288. var done = false;
  289. var i = toComplete.length;
  290. do {
  291. for (var k=0; k<choices.length; ++k) {
  292. if (choices[k].length <= i) {
  293. done = true;
  294. break;
  295. }
  296. if (choices[k][i] != choices[0][i]) {
  297. done = true;
  298. break;
  299. }
  300. }
  301. if (!done) {
  302. leCommand.insert(choices[0][i++]);
  303. //command = command.concat(choices[0][i++]);
  304. }
  305. } while(!done);
  306. //leCommand.text = command;
  307. }
  308. });
  309. leCommand.clearHistory.connect(teHistory.clear);
  310. };
  311. EcmaScriptShell.eval = function(expression) {
  312. return RMainWindowQt.getMainWindow().eval("js", expression);
  313. }