CommandLine.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  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/EAction.js");
  22. include("scripts/WidgetFactory.js");
  23. function CommandLine(guiAction) {
  24. EAction.call(this, guiAction);
  25. }
  26. CommandLine.getPreferencesCategory = function() {
  27. return [ qsTr("Widgets"), qsTr("Command Line") ];
  28. };
  29. CommandLine.prototype = new EAction();
  30. /**
  31. * Shows / hides the command line widget.
  32. */
  33. CommandLine.prototype.beginEvent = function() {
  34. EAction.prototype.beginEvent.call(this);
  35. var appWin = RMainWindowQt.getMainWindow();
  36. var dock = appWin.findChild("CommandLineDock");
  37. if (!RSettings.getOriginalArguments().contains("-no-show")) {
  38. dock.visible = !dock.visible;
  39. if (dock.visible) dock.raise();
  40. }
  41. };
  42. CommandLine.prototype.finishEvent = function() {
  43. EAction.prototype.finishEvent.call(this);
  44. var appWin = RMainWindowQt.getMainWindow();
  45. var dock = appWin.findChild("CommandLineDock");
  46. this.getGuiAction().setChecked(dock.visible);
  47. };
  48. //CommandLine.initStyle = function() {
  49. // var appWin = RMainWindowQt.getMainWindow();
  50. // var formWidget = appWin.findChild("CommandLine");
  51. // var frame = formWidget.findChild("Frame");
  52. // var p = frame.palette;
  53. // if (!RSettings.hasDarkGuiBackground()) {
  54. // // white background of command line label:
  55. // p.setColor(QPalette.Active, QPalette.Window, new QColor(Qt.white));
  56. // }
  57. // else {
  58. // p.setColor(QPalette.Active, QPalette.Window, new QColor("#1e1e1e"));
  59. // }
  60. // frame.palette = p;
  61. // frame.autoFillBackground = true;
  62. // frame.repaint();
  63. //}
  64. CommandLine.init = function(basePath) {
  65. var appWin = EAction.getMainWindow();
  66. var action = new RGuiAction(qsTr("&Command Line"), appWin);
  67. action.setRequiresDocument(false);
  68. action.setScriptFile(basePath + "/CommandLine.js");
  69. action.setIcon(basePath + "/CommandLine.svg");
  70. action.setDefaultShortcut(new QKeySequence("g,m"));
  71. action.setDefaultCommands(["gm"]);
  72. action.setGroupSortOrder(3800);
  73. action.setSortOrder(100);
  74. action.setWidgetNames(["ViewMenu", "WidgetsToolBar", "ViewToolsPanel", "WidgetsMatrixPanel"]);
  75. var e;
  76. var formWidget = WidgetFactory.createWidget(basePath, "CommandLine.ui");
  77. var frame = formWidget.findChild("Frame");
  78. //CommandLine.initStyle();
  79. var teHistory = formWidget.findChild("History");
  80. var leCommand = formWidget.findChild("CommandEdit");
  81. WidgetFactory.initLineEditInfoTools(leCommand);
  82. var lCommand = formWidget.findChild("CommandLabel");
  83. var bToggleTitleBar = formWidget.findChild("ToggleTitleBar");
  84. var dock = new RDockWidget(qsTr("Command Line"), appWin);
  85. dock.objectName = "CommandLineDock";
  86. dock.setWidget(formWidget);
  87. //dock.setSizePolicy(QSizePolicy.Ignored, QSizePolicy.MinimumExpanding);
  88. appWin.addDockWidget(Qt.BottomDockWidgetArea, dock);
  89. dock.shown.connect(function() { action.setChecked(true); });
  90. dock.hidden.connect(function() { action.setChecked(false); });
  91. var blue = "#0000cc";
  92. var infoBlue = "#0066cc";
  93. var red = "#cc0000";
  94. if (RSettings.hasDarkGuiBackground()) {
  95. blue = "#2E9AFF";
  96. infoBlue = "#91c1ff";
  97. red = "#FF6060";
  98. }
  99. // open fragment links of format "#<entity ID>,<entity ID>" by selecting
  100. // the entities with the given IDs:
  101. teHistory.openLinks = false;
  102. teHistory.anchorClicked.connect(function(url) {
  103. var di = appWin.getDocumentInterface();
  104. if (isNull(di)) {
  105. return;
  106. }
  107. if (!url.hasFragment()) {
  108. return;
  109. }
  110. var frag = url.fragment();
  111. var entityIds = frag.split(",");
  112. for (var i=0; i<entityIds.length; i++) {
  113. entityIds[i] = parseInt(entityIds[i], 10);
  114. }
  115. di.selectEntities(entityIds);
  116. });
  117. function appendAndScroll(msg) {
  118. teHistory.append(msg);
  119. var c = teHistory.textCursor();
  120. c.movePosition(QTextCursor.End);
  121. teHistory.setTextCursor(c);
  122. teHistory.ensureCursorVisible();
  123. var appWin = EAction.getMainWindow();
  124. var dlg = appWin.findChild("ProgressDialog");
  125. if (!appWin.enabled || (!isNull(dlg) && dlg.visible)) {
  126. QCoreApplication.processEvents();
  127. }
  128. }
  129. var showTitleBar = RSettings.getBoolValue("Appearance/CommandLineTitleBar", false);
  130. if (!showTitleBar) {
  131. dock.setTitleBarWidget(new QWidget(dock));
  132. bToggleTitleBar.checked = false;
  133. }
  134. else {
  135. bToggleTitleBar.checked = true;
  136. }
  137. // hide title bar of command line dock widget:
  138. bToggleTitleBar.toggled.connect(
  139. function(show) {
  140. if (show) {
  141. dock.setTitleBarWidget(null);
  142. RSettings.setValue("Appearance/CommandLineTitleBar", true);
  143. }
  144. else {
  145. dock.setTitleBarWidget(new QWidget(dock));
  146. RSettings.setValue("Appearance/CommandLineTitleBar", false);
  147. }
  148. });
  149. // user edited command line. if input is a valid coordinate: preview
  150. leCommand.textChanged.connect(function(command) {
  151. var di = appWin.getDocumentInterface();
  152. if (isNull(di)) {
  153. return;
  154. }
  155. var doc = di.getDocument();
  156. // substitute variables:
  157. var cmd = doc.substituteAutoVariables(command);
  158. // handle action specific commands for preview:
  159. e = new RCommandEvent(cmd);
  160. di.commandEventPreview(e);
  161. if (e.isAccepted()) {
  162. di.repaintViews();
  163. return;
  164. }
  165. // handle math expressions preview,
  166. if (cmd.startsWith("=")) {
  167. return;
  168. }
  169. if (di.getClickMode() !== RAction.PickCoordinate) {
  170. return;
  171. }
  172. var view = di.getLastKnownViewWithFocus();
  173. // handle direct distance entry for preview:
  174. var pos = stringToDirectDistanceEntry(di.getRelativeZero(), di.getCursorPosition(), cmd);
  175. // handle coordinate entry for preview:
  176. if (isNull(pos) || !pos.isValid()) {
  177. pos = stringToCoordinate(di.getRelativeZero(), cmd);
  178. }
  179. var sp;
  180. if (isNull(pos) || !pos.isValid()) {
  181. sp = di.getCursorPosition();
  182. di.clearPreview();
  183. di.repaintViews();
  184. di.setCursorPosition(sp);
  185. return;
  186. }
  187. sp = di.getCursorPosition();
  188. di.setCursorPosition(pos);
  189. appWin.notifyCoordinateListeners(di);
  190. e = new RCoordinateEvent(pos, view.getScene(), getRGraphicsView(view));
  191. di.clearPreview();
  192. di.coordinateEventPreview(e);
  193. di.repaintViews();
  194. di.setCursorPosition(sp);
  195. });
  196. // user pressed enter, enter command or coordinate:
  197. leCommand.commandConfirmed.connect(function(command) {
  198. // enter with empty input: repeat last command:
  199. if (command === "") {
  200. var last = leCommand.getLastCommand();
  201. if (last !== "") {
  202. arguments.callee(last);
  203. return;
  204. }
  205. }
  206. var di = appWin.getDocumentInterface();
  207. if (!isNull(di)) {
  208. var doc = di.getDocument();
  209. // substitute variables:
  210. var cmd = doc.substituteAutoVariables(command);
  211. // handle action specific commands:
  212. var e = new RCommandEvent(cmd);
  213. di.commandEvent(e);
  214. if (e.isAccepted()) {
  215. //appWin.handleUserCommand(command);
  216. leCommand.clear();
  217. return;
  218. }
  219. // handle math expressions and leave result in command line,
  220. // or, if error, print error and leave command line unchanged
  221. if (cmd.startsWith("=")) {
  222. // remove leading '='
  223. cmd = cmd.slice(1);
  224. var prefix = "=";
  225. var relprefix = RSettings.getStringValue(
  226. "Input/RelativeCoordinatePrefix",
  227. String.fromCharCode(64) // @ (doxygen can't cope with an @ here)
  228. );
  229. if (cmd.endsWith(" ")) {
  230. prefix = "";
  231. } else if (cmd.endsWith(relprefix)) {
  232. cmd = cmd.replace(relprefix, "");
  233. prefix = relprefix;
  234. }
  235. var val = RMath.eval(cmd);
  236. if (isNumber(val)) {
  237. appWin.handleUserCommand("=" + cmd);
  238. appWin.handleUserInfo(val.toString());
  239. leCommand.text = prefix + val;
  240. } else {
  241. var er = RMath.getError();
  242. EAction.handleUserWarning(qsTr("Invalid value:") + " '=%1' - %2".arg(cmd).arg(er));
  243. }
  244. return;
  245. }
  246. // handle direct distance entry:
  247. var pos = stringToDirectDistanceEntry(di.getRelativeZero(), di.getCursorPosition(), cmd);
  248. // handle coordinate entry:
  249. if (isNull(pos) || !pos.isValid()) {
  250. pos = stringToCoordinate(di.getRelativeZero(), cmd);
  251. }
  252. if (!pos.isNaN()) {
  253. if (!pos.isValid()) {
  254. appWin.handleUserWarning(
  255. qsTr("Invalid coordinate or distance \"%1\".")
  256. .arg(RS.escape(cmd))
  257. );
  258. return;
  259. }
  260. var view = di.getLastKnownViewWithFocus();
  261. e = new RCoordinateEvent(pos, view.getScene(), getRGraphicsView(view));
  262. di.coordinateEvent(e);
  263. appWin.handleUserCommand(command);
  264. leCommand.clear();
  265. return;
  266. }
  267. }
  268. // handle commands that trigger new actions:
  269. var commandLower = command.toLowerCase();
  270. if (!RGuiAction.triggerByCommand(commandLower)) {
  271. EAction.handleUserWarning(
  272. qsTr("Unknown command or invalid coordinate or value: \"%1\"").arg(commandLower)
  273. );
  274. // guess: if there's a number somewhere, user might be trying to enter
  275. // a number value or coordinate. Nuge him in the right direction:
  276. if (command.search(/[0-9]./)!==-1) {
  277. EAction.handleUserWarning(
  278. qsTr("Numbers may be entered as: \"%1\"")
  279. .arg(numberToString(Math.PI, 3))
  280. );
  281. var sample = new RVector(1.234,1.234);
  282. var samplePolar = RVector.createPolar(10,30);
  283. EAction.handleUserWarning(
  284. qsTr("Coordinates may be entered as: \"%1\" (absolute) " +
  285. "or \"%2\" (relative) or \"%3\" (polar) or \"%4\" (relative polar)")
  286. .arg(coordinateToString(sample, 3, false, false))
  287. .arg(coordinateToString(sample, 3, true, false))
  288. .arg(coordinateToString(samplePolar, 3, false, true))
  289. .arg(coordinateToString(samplePolar, 3, true, true))
  290. );
  291. EAction.handleUserWarning(
  292. qsTr("You may change the number / coordinate " +
  293. "format in the application preferences.")
  294. );
  295. }
  296. leCommand.clear();
  297. return;
  298. }
  299. leCommand.clear();
  300. var historySize = RSettings.getIntValue("Console/HistorySize", 1000);
  301. var buf = teHistory.plainText.split("\n");
  302. if (buf.length > historySize) {
  303. teHistory.setPlainText(buf.slice(-historySize).join("\n"));
  304. }
  305. leCommand.setFocus();
  306. });
  307. // user pressed tab:
  308. leCommand.completeCommand.connect(function(command) {
  309. var choices = RGuiAction.getAvailableCommands(command, true);
  310. if (choices.length === 0) {
  311. // no commands match. beep?
  312. } else if (choices.length == 1) {
  313. // only one command matches. complete it:
  314. leCommand.text = choices[0];
  315. } else {
  316. // suggest multiple commands that match so far:
  317. appendAndScroll(choices.join(", "));
  318. // complete as much as possible:
  319. var done = false;
  320. var i = command.length;
  321. do {
  322. for (var k=0; k<choices.length; ++k) {
  323. if (choices[k].length <= i) {
  324. done = true;
  325. break;
  326. }
  327. if (choices[k][i] != choices[0][i]) {
  328. done = true;
  329. break;
  330. }
  331. }
  332. if (!done) {
  333. command = command.concat(choices[0][i++]);
  334. }
  335. } while(!done);
  336. leCommand.text = command;
  337. }
  338. });
  339. leCommand.clearHistory.connect(function() { teHistory.clear(); });
  340. // show message to user:
  341. appWin.userMessage.connect(function(message, escape) {
  342. if (escape) {
  343. message = RS.escape(message);
  344. }
  345. appendAndScroll("<span>" + message + "</span>");
  346. });
  347. // show warning to user:
  348. appWin.userWarning.connect(function(message, messageBox, escape) {
  349. // TODO: refactor:
  350. if (message==="#transaction_failed") {
  351. message = qsTr("Transaction failed. Please check for block recursions and locked or invisible layers or blocks.");
  352. }
  353. if (escape) {
  354. message = RS.escape(message);
  355. // workaround for Qt showing &quot; in label:
  356. message = message.replace(/&quot;/g, "\"");
  357. }
  358. appendAndScroll("<span style='color:" + red + ";'>" + message + "</span>");
  359. if (RSettings.getBoolValue("CommandLine/WarningsAsDialog", false) || messageBox) {
  360. QMessageBox.warning(appWin, qsTr("Warning"), message);
  361. }
  362. });
  363. // show info to user:
  364. appWin.userInfo.connect(function(message, escape) {
  365. if (escape) {
  366. message = RS.escape(message);
  367. }
  368. appendAndScroll("<span style='color:" + infoBlue + ";'>" + message + "</span>");
  369. if (RSettings.getBoolValue("CommandLine/InfoAsDialog", false)) {
  370. QMessageBox.information(appWin, qsTr("Info"), message);
  371. }
  372. });
  373. // show previously entered command:
  374. appWin.userCommand.connect(function(message, escape) {
  375. // prevent some very frequent commands from being displayed every time:
  376. if (message==="snapauto" || message==="restrictoff" || message==="escape") {
  377. return;
  378. }
  379. var msgEsc = message;
  380. if (escape) {
  381. msgEsc = RS.escape(message);
  382. }
  383. var cartCoordSep = RSettings.getStringValue("Input/CartesianCoordinateSeparator", ',');
  384. var polCoordSep = RSettings.getStringValue("Input/PolarCoordinateSeparator", '<');
  385. var what;
  386. if (message.startsWith("=")) {
  387. what = qsTr("Expression");
  388. }
  389. else if (message.contains(cartCoordSep) || message.contains(polCoordSep)) {
  390. what = qsTr("Coordinate");
  391. }
  392. else {
  393. what = qsTr("Command");
  394. }
  395. leCommand.appendCommand(message);
  396. appendAndScroll(
  397. "<span style='color:" + blue + ";'>"
  398. + "<i>" + what + ": </i>"
  399. + msgEsc + "</span>");
  400. });
  401. // change command prompt label:
  402. appWin.commandPrompt.connect(function(text) {
  403. if (text.length===0) {
  404. lCommand.text = qsTr("Command:") + " ";
  405. }
  406. else {
  407. lCommand.text = text + qsTr(": ");
  408. }
  409. });
  410. var system;
  411. switch (RS.getSystemId()) {
  412. case "win":
  413. system = "Windows";
  414. break;
  415. case "osx":
  416. system = "macOS";
  417. break;
  418. case "linux":
  419. system = "Linux";
  420. break;
  421. case "freebsd":
  422. system = "FreeBSD";
  423. break;
  424. case "netbsd":
  425. system = "NetBSD";
  426. break;
  427. case "openbsd":
  428. system = "OpenBSD";
  429. break;
  430. case "solaris":
  431. system = "Solaris";
  432. break;
  433. }
  434. EAction.handleUserMessage(
  435. "%1 %2 / Qt %3 / %4 %5"
  436. .arg(qApp.applicationName)
  437. .arg(RSettings.getVersionString())
  438. .arg(RSettings.getQtVersionString())
  439. .arg(system)
  440. .arg(RS.getBuildCpuArchitecture())
  441. );
  442. // var pl = new RPaletteListenerAdapter();
  443. // appWin.addPaletteListener(pl);
  444. // pl.paletteChanged.connect(CommandLine.initStyle);
  445. };