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 **
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 #include "qg_commandwidget.h"
27
28 #include <algorithm>
29
30 #include <QAction>
31 #include <QKeyEvent>
32 #include <QFileDialog>
33 #include <QSettings>
34
35 #include "qg_actionhandler.h"
36 #include "rs_commands.h"
37 #include "rs_commandevent.h"
38 #include "rs_system.h"
39 #include "rs_utility.h"
40
41 /*
42 * Constructs a QG_CommandWidget as a child of 'parent', with the
43 * name 'name' and widget flags set to 'f'.
44 */
QG_CommandWidget(QWidget * parent,const char * name,Qt::WindowFlags fl)45 QG_CommandWidget::QG_CommandWidget(QWidget* parent, const char* name, Qt::WindowFlags fl)
46 : QWidget(parent, fl)
47 , actionHandler(nullptr)
48 {
49 setObjectName(name);
50 setupUi(this);
51 connect(leCommand, SIGNAL(command(QString)), this, SLOT(handleCommand(QString)));
52 connect(leCommand, SIGNAL(escape()), this, SLOT(escape()));
53 connect(leCommand, SIGNAL(focusOut()), this, SLOT(setNormalMode()));
54 connect(leCommand, SIGNAL(focusIn()), this, SLOT(setCommandMode()));
55 connect(leCommand, SIGNAL(tabPressed()), this, SLOT(tabPressed()));
56 connect(leCommand, SIGNAL(clearCommandsHistory()), teHistory, SLOT(clear()));
57 connect(leCommand, SIGNAL(message(QString)), this, SLOT(appendHistory(QString)));
58 connect(leCommand, &QG_CommandEdit::keycode, this, &QG_CommandWidget::handleKeycode);
59
60 auto a1 = new QAction(QObject::tr("Keycode mode"), this);
61 a1->setObjectName("keycode_action");
62 a1->setCheckable(true);
63 connect(a1, &QAction::toggled, this, &QG_CommandWidget::setKeycodeMode);
64 options_button->addAction(a1);
65
66 QSettings settings;
67 if (settings.value("Widgets/KeycodeMode", false).toBool())
68 {
69 leCommand->keycode_mode = true;
70 a1->setChecked(true);
71 }
72
73 auto a2 = new QAction(QObject::tr("Load command file"), this);
74 connect(a2, &QAction::triggered, this, &QG_CommandWidget::chooseCommandFile);
75 options_button->addAction(a2);
76
77 auto a3 = new QAction(QObject::tr("Paste multiple commands"), this);
78 connect(a3, &QAction::triggered, leCommand, &QG_CommandEdit::modifiedPaste);
79 options_button->addAction(a3);
80
81 options_button->setStyleSheet("QToolButton::menu-indicator { image: none; }");
82 }
83
84 /*
85 * Destroys the object and frees any allocated resources
86 */
~QG_CommandWidget()87 QG_CommandWidget::~QG_CommandWidget()
88 {
89 QSettings settings;
90 auto action = findChild<QAction*>("keycode_action");
91 settings.setValue("Widgets/KeycodeMode", action->isChecked());
92 }
93
94 /*
95 * Sets the strings of the subwidgets using the current
96 * language.
97 */
languageChange()98 void QG_CommandWidget::languageChange()
99 {
100 retranslateUi(this);
101 }
102
eventFilter(QObject *,QEvent * event)103 bool QG_CommandWidget::eventFilter(QObject */*obj*/, QEvent *event)
104 {
105 if (event->type() == QEvent::KeyPress) {
106 QKeyEvent* e=static_cast<QKeyEvent*>(event);
107
108 int key {e->key()};
109 switch(key) {
110 case Qt::Key_Return:
111 case Qt::Key_Enter:
112 if(!leCommand->text().size())
113 return false;
114 else
115 break;
116 case Qt::Key_Escape:
117 return false;
118 default:
119 break;
120 }
121
122 //detect Ctl- Alt- modifier, but not Shift
123 //This should avoid filtering shortcuts, such as Ctl-C
124 Qt::KeyboardModifiers modifiers {e->modifiers()};
125 if ( !(Qt::GroupSwitchModifier == modifiers && Qt::Key_At == key) // let '@' key pass for relative coords
126 && modifiers != Qt::KeypadModifier
127 && modifiers & (Qt::KeyboardModifierMask ^ Qt::ShiftModifier)) {
128 return false;
129 }
130
131 event->accept();
132 this->setFocus();
133 QKeyEvent * newEvent = new QKeyEvent(*static_cast<QKeyEvent*>(event));
134 QApplication::postEvent(leCommand, newEvent);
135
136 return true;
137 }
138
139 return false;
140 }
141
setFocus()142 void QG_CommandWidget::setFocus()
143 {
144 if (!isActiveWindow())
145 activateWindow();
146
147 auto newEvent = new QFocusEvent(QEvent::FocusIn);
148 QApplication::postEvent(leCommand, newEvent);
149 leCommand->setFocus();
150 }
151
setCommand(const QString & cmd)152 void QG_CommandWidget::setCommand(const QString& cmd) {
153 if (cmd!="") {
154 lCommand->setText(cmd);
155 } else {
156 lCommand->setText(tr("Command:"));
157 }
158 leCommand->setText("");
159 }
160
appendHistory(const QString & msg)161 void QG_CommandWidget::appendHistory(const QString& msg) {
162 teHistory->append(msg);
163 }
164
handleCommand(QString cmd)165 void QG_CommandWidget::handleCommand(QString cmd)
166 {
167 cmd = cmd.simplified();
168 bool isAction=false;
169 if (!cmd.isEmpty()) {
170 appendHistory(cmd);
171 }
172
173 if (actionHandler) {
174 isAction=actionHandler->command(cmd);
175 }
176
177 if (!isAction && !(cmd.contains(',') || cmd.at(0)=='@')) {
178 appendHistory(tr("Unknown command: %1").arg(cmd));
179 }
180
181 leCommand->setText("");
182 }
183
tabPressed()184 void QG_CommandWidget::tabPressed() {
185 if (actionHandler) {
186 QStringList reducedChoice;
187 QString typed = leCommand->text();
188 QStringList choice;
189
190 // check current command:
191 choice = actionHandler->getAvailableCommands();
192 if (choice.count()==0) {
193 choice = RS_COMMANDS->complete(typed);
194 }
195
196 for (QStringList::Iterator it = choice.begin(); it != choice.end(); ++it) {
197 if (typed.isEmpty() || (*it).startsWith(typed)) {
198 reducedChoice << (*it);
199 }
200 }
201
202 // command found:
203 if (reducedChoice.count()==1) {
204 leCommand->setText(reducedChoice.first());
205 }
206 else if (reducedChoice.count()>0) {
207 QString const& proposal = this->getRootCommand(reducedChoice, typed);
208 appendHistory(reducedChoice.join(", "));
209 leCommand -> setText(proposal);
210 }
211 }
212 }
213
escape()214 void QG_CommandWidget::escape() {
215 //leCommand->clearFocus();
216 if (actionHandler) {
217 actionHandler->command(QString(tr("escape", "escape, go back from action steps")));
218 }
219 }
220
setActionHandler(QG_ActionHandler * ah)221 void QG_CommandWidget::setActionHandler(QG_ActionHandler* ah) {
222 actionHandler = ah;
223 }
224
setCommandMode()225 void QG_CommandWidget::setCommandMode() {
226 QPalette palette;
227 palette.setColor(lCommand->foregroundRole(), Qt::blue);
228 lCommand->setPalette(palette);
229 }
230
setNormalMode()231 void QG_CommandWidget::setNormalMode() {
232 QPalette palette;
233 palette.setColor(lCommand->foregroundRole(), Qt::black);
234 lCommand->setPalette(palette);
235 }
236
getRootCommand(const QStringList & cmdList,const QString & typed)237 QString QG_CommandWidget::getRootCommand( const QStringList & cmdList, const QString & typed ) {
238 //do we have to check for empty cmdList?
239 if(cmdList.empty()) return QString();
240
241 //find the shortest string in cmdList
242 auto const& shortestString = * std::min_element(cmdList.begin(), cmdList.end(),
243 [](QString const& a, QString const& b) -> bool
244 {
245 return a.size() < b.size();
246 }
247 );
248 int const lengthShortestString = shortestString.size();
249
250 // Now we parse the cmdList list, character of each item by character.
251 int low = typed.length();
252 int high = lengthShortestString + 1;
253
254 while(high > low + 1) {
255 int mid = (high + low)/2;
256 bool common = true;
257
258 QString const& proposal = shortestString.left(mid);
259 for(auto const& substring: cmdList) {
260 if(!substring.startsWith(proposal)) {
261 common = false;
262 break;
263 }
264 }
265 if(common) {
266 low = mid;
267 }
268 else {
269 high = mid;
270 }
271 }
272
273 // As we assign just before mid value to low (if strings are common), we can use it as parameter for left.
274 // If not common -> low value does not changes, even if escaping from the while. This avoids weird behaviors like continuing completion when pressing tab.
275 return shortestString.left(low);
276
277 }
278
chooseCommandFile()279 void QG_CommandWidget::chooseCommandFile()
280 {
281 QString path = QFileDialog::getOpenFileName(this);
282 if (!path.isEmpty())
283 {
284 leCommand->readCommandFile(path);
285 }
286 }
287
handleKeycode(QString code)288 void QG_CommandWidget::handleKeycode(QString code)
289 {
290 if (actionHandler->keycode(code))
291 {
292 leCommand->clear();
293 }
294 }
295
setKeycodeMode(bool state)296 void QG_CommandWidget::setKeycodeMode(bool state)
297 {
298 leCommand->keycode_mode = state;
299 }
300