1 /****************************************************************************
2 **
3 ** This file is part of the LibreCAD project, a 2D CAD program
4 **
5 ** Copyright (C) 2010 R. van Twisk (librecad@rvt.dds.nl)
6 ** Copyright (C) 2001-2003 RibbonSoft. All rights reserved.
7 ** Copyright (C) 2016 ravas (github.com/r-a-v-a-s)
8 **
9 ** This file may be distributed and/or modified under the terms of the
10 ** GNU General Public License version 2 as published by the Free Software
11 ** Foundation and appearing in the file gpl-2.0.txt included in the
12 ** packaging of this file.
13 **
14 ** This program is distributed in the hope that it will be useful,
15 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ** GNU General Public License for more details.
18 **
19 ** You should have received a copy of the GNU General Public License
20 ** along with this program; if not, write to the Free Software
21 ** Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
22 **
23 ** This copyright notice MUST APPEAR in all copies of the script!
24 **
25 **********************************************************************/
26
27 #include "qg_commandedit.h"
28
29 #include <QKeyEvent>
30 #include <QRegularExpression>
31 #include <QFile>
32 #include <QTextStream>
33 #include <QApplication>
34 #include <QClipboard>
35
36 #include <rs_math.h>
37 #include <rs_settings.h>
38
39
40 /**
41 * Default Constructor. You must call init manually if you choose
42 * to use this constructor.
43 */
QG_CommandEdit(QWidget * parent)44 QG_CommandEdit::QG_CommandEdit(QWidget* parent)
45 : QLineEdit(parent)
46 , keycode_mode(false)
47 , relative_ray("none")
48 , calculator_mode(false)
49
50 {
51 setStyleSheet("selection-color: white; selection-background-color: green;");
52 setFrame(false);
53 setFocusPolicy(Qt::StrongFocus);
54 }
55
56 /**
57 * Bypass for key press events from the tab key.
58 */
event(QEvent * e)59 bool QG_CommandEdit::event(QEvent* e) {
60 if (e->type()==QEvent::KeyPress) {
61 QKeyEvent* k = (QKeyEvent*)e;
62 if (k->key()==Qt::Key_Tab) {
63 emit tabPressed();
64 return true;
65 }
66 }
67
68 return QLineEdit::event(e);
69 }
70
71 /**
72 * History (arrow key up/down) support, tab.
73 */
keyPressEvent(QKeyEvent * e)74 void QG_CommandEdit::keyPressEvent(QKeyEvent* e)
75 {
76 if (e->modifiers() & Qt::ControlModifier)
77 {
78 auto value = text();
79
80 if (value.isEmpty())
81 value = relative_ray;
82
83 QString r_string;
84
85 switch (e->key())
86 {
87 case Qt::Key_Up:
88 r_string = "0," + value;
89 break;
90 case Qt::Key_Down:
91 r_string = "0,-" + value;
92 break;
93 case Qt::Key_Right:
94 r_string = value + ",0";
95 break;
96 case Qt::Key_Left:
97 r_string = "-" + value + ",0";
98 break;
99 default:
100 QLineEdit::keyPressEvent(e);
101 return;
102 }
103
104 // r_string is empty when Ctrl is pressed
105 if (!r_string.isEmpty())
106 {
107 if (value == "none")
108 {
109 emit message(
110 QObject::tr("You must input a distance first.")
111 );
112 }
113 else
114 {
115 relative_ray = value;
116 emit command("@"+r_string);
117 }
118 }
119 return;
120 }
121
122 switch (e->key())
123 {
124 case Qt::Key_Up:
125 if (!historyList.isEmpty() && it>historyList.begin())
126 {
127 it--;
128 setText(*it);
129 }
130 break;
131
132 case Qt::Key_Down:
133 if (!historyList.isEmpty() && it<historyList.end() )
134 {
135 it++;
136 if (it<historyList.end()) {
137 setText(*it);
138 }
139 else {
140 setText("");
141 }
142 }
143 break;
144
145 case Qt::Key_Enter:
146 case Qt::Key_Return:
147 processInput(text());
148 break;
149 case Qt::Key_Space:
150 if (RS_SETTINGS->readNumEntry("/Keyboard/EvaluateCommandOnSpace", true) ||
151 (text().isEmpty() && RS_SETTINGS->readNumEntry("/Keyboard/ToggleFreeSnapOnSpace", true)))
152 processInput(text());
153 else if (!text().isEmpty())
154 QLineEdit::keyPressEvent(e);
155 break;
156 case Qt::Key_Escape:
157 if (text().isEmpty()) {
158 emit escape();
159 }
160 else {
161 setText("");
162 }
163 break;
164
165 default:
166 QLineEdit::keyPressEvent(e);
167 break;
168 }
169
170 if (keycode_mode)
171 {
172 auto input = text();
173 if (input.size() == 2)
174 {
175 emit keycode(input);
176 }
177 }
178 }
179
evaluateExpression(QString input)180 void QG_CommandEdit::evaluateExpression(QString input)
181 {
182 QRegularExpression regex(R"~(([\d\.]+)deg|d)~");
183 input.replace(regex, R"~(\1*pi/180)~");
184 bool ok = true;
185 double result = RS_Math::eval(input, &ok);
186 if (ok)
187 emit message(input + " = " + QString::number(result, 'g', 12));
188 else
189 emit message(QObject::tr("Calculator error for input: ") + input);
190 }
191
focusInEvent(QFocusEvent * e)192 void QG_CommandEdit::focusInEvent(QFocusEvent *e) {
193 emit focusIn();
194 QLineEdit::focusInEvent(e);
195 }
196
focusOutEvent(QFocusEvent * e)197 void QG_CommandEdit::focusOutEvent(QFocusEvent *e) {
198 emit focusOut();
199 QLineEdit::focusOutEvent(e);
200 }
201
processInput(QString input)202 void QG_CommandEdit::processInput(QString input)
203 {
204 // author: ravas
205
206 // convert 10..0 to @10,0
207 QRegularExpression regex(R"~(([-\w\.\\]+)\.\.)~");
208 input.replace(regex, "@\\1,");
209
210 if (isForeignCommand(input))
211 {
212 if (input.contains(";"))
213 {
214 foreach (auto str, input.split(";"))
215 {
216 if (str.contains("\\"))
217 processVariable(str);
218 else
219 emit command(str);
220 }
221 }
222 else
223 {
224 if (input.contains("\\"))
225 processVariable(input);
226 else
227 emit command(input);
228 }
229
230 historyList.append(input);
231 it = historyList.end();
232 }
233 clear();
234 }
235
isForeignCommand(QString input)236 bool QG_CommandEdit::isForeignCommand(QString input)
237 {
238 // author: ravas
239
240 bool r_value = true;
241
242 if (input == tr("clear"))
243 {
244 emit clearCommandsHistory();
245 r_value = false;
246 }
247 else if (input == QObject::tr("cal"))
248 {
249 calculator_mode = !calculator_mode;
250 if(calculator_mode)
251 emit message(QObject::tr("Calculator mode: On"));
252 else
253 emit message(QObject::tr("Calculator mode: Off"));
254 r_value = false;
255 }
256 else if (calculator_mode)
257 {
258 evaluateExpression(input);
259 r_value = false;
260 }
261 else if (input.contains("="))
262 {
263 auto var_value = input.split("=");
264 variables[var_value[0]] = var_value[1];
265 r_value = false;
266 }
267 return r_value;
268 }
269
processVariable(QString input)270 void QG_CommandEdit::processVariable(QString input)
271 {
272 // author: ravas
273
274 if (input.contains(","))
275 {
276 QString rel = "";
277
278 if (input.contains("@"))
279 {
280 rel = "@";
281 input.remove("@");
282 }
283
284 auto x_y = input.split(",");
285 if (x_y[0].contains("\\"))
286 {
287 x_y[0].remove("\\");
288 if (variables.contains(x_y[0]))
289 x_y[0] = variables[x_y[0]];
290 }
291 if (x_y[1].contains("\\"))
292 {
293 x_y[1].remove("\\");
294 if (variables.contains(x_y[1]))
295 x_y[1] = variables[x_y[1]];
296 }
297 emit command(rel + x_y[0] + "," + x_y[1]);
298 return;
299 }
300
301 input.remove("\\");
302 if (variables.contains(input))
303 {
304 input = variables[input];
305 if (input.contains(";"))
306 {
307 foreach (auto str, input.split(";"))
308 {
309 if (str.contains("\\"))
310 processVariable(str);
311 else
312 emit command(str);
313 }
314 }
315 else emit command(input);
316 }
317 }
318
readCommandFile(const QString & path)319 void QG_CommandEdit::readCommandFile(const QString& path)
320 {
321 // author: ravas
322
323 QFile file(path);
324 if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
325 return;
326
327 QTextStream txt_stream(&file);
328 QString line;
329 while (!txt_stream.atEnd())
330 {
331 line = txt_stream.readLine();
332 line.remove(" ");
333 if (!line.startsWith("#"))
334 processInput(line);
335 }
336 }
337
modifiedPaste()338 void QG_CommandEdit::modifiedPaste()
339 {
340 auto txt = qApp->clipboard()->text();
341 txt.replace("\n", ";");
342 setText(txt);
343 }
344
345
346