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