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