1 /*****************************************************************************
2  * Copyright (C) 2004-2007 Jonas Bähr <jonas.baehr@web.de>                   *
3  * Copyright (C) 2004-2019 Krusader Krew [https://krusader.org]              *
4  *                                                                           *
5  * This file is part of Krusader [https://krusader.org].                     *
6  *                                                                           *
7  * Krusader is free software: you can redistribute it and/or modify          *
8  * it under the terms of the GNU General Public License as published by      *
9  * the Free Software Foundation, either version 2 of the License, or         *
10  * (at your option) any later version.                                       *
11  *                                                                           *
12  * Krusader is distributed in the hope that it will be useful,               *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of            *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the             *
15  * GNU General Public License for more details.                              *
16  *                                                                           *
17  * You should have received a copy of the GNU General Public License         *
18  * along with Krusader.  If not, see [http://www.gnu.org/licenses/].         *
19  *****************************************************************************/
20 
21 #include "actionproperty.h"
22 #include "addplaceholderpopup.h"
23 
24 #include "../UserAction/useraction.h"
25 #include "../UserAction/kraction.h"
26 #include "../krusader.h"
27 #include "../krglobal.h"
28 #include "../icon.h"
29 
30 // QtWidgets
31 #include <QFileDialog>
32 #include <QInputDialog>
33 
34 #include <KI18n/KLocalizedString>
35 #include <KWidgetsAddons/KMessageBox>
36 #include <KXmlGui/KActionCollection>
37 
38 
ActionProperty(QWidget * parent,KrAction * action)39 ActionProperty::ActionProperty(QWidget *parent, KrAction *action)
40         : QWidget(parent), _modified(false)
41 {
42 
43     setupUi(this);
44 
45     if (action) {
46         _action = action;
47         updateGUI(_action);
48     }
49 
50     ButtonAddPlaceholder->setIcon(Icon("list-add"));
51     ButtonAddStartpath->setIcon(Icon("document-open"));
52 
53     // fill with all existing categories
54     cbCategory->addItems(krUserAction->allCategories());
55 
56     connect(ButtonAddPlaceholder, SIGNAL(clicked()), this, SLOT(addPlaceholder()));
57     connect(ButtonAddStartpath, SIGNAL(clicked()), this, SLOT(addStartpath()));
58     connect(ButtonNewProtocol, SIGNAL(clicked()), this, SLOT(newProtocol()));
59     connect(ButtonEditProtocol, SIGNAL(clicked()), this, SLOT(editProtocol()));
60     connect(ButtonRemoveProtocol, SIGNAL(clicked()), this, SLOT(removeProtocol()));
61     connect(ButtonAddPath, SIGNAL(clicked()), this, SLOT(addPath()));
62     connect(ButtonEditPath, SIGNAL(clicked()), this, SLOT(editPath()));
63     connect(ButtonRemovePath, SIGNAL(clicked()), this, SLOT(removePath()));
64     connect(ButtonAddMime, SIGNAL(clicked()), this, SLOT(addMime()));
65     connect(ButtonEditMime, SIGNAL(clicked()), this, SLOT(editMime()));
66     connect(ButtonRemoveMime, SIGNAL(clicked()), this, SLOT(removeMime()));
67     connect(ButtonNewFile, SIGNAL(clicked()), this, SLOT(newFile()));
68     connect(ButtonEditFile, SIGNAL(clicked()), this, SLOT(editFile()));
69     connect(ButtonRemoveFile, SIGNAL(clicked()), this, SLOT(removeFile()));
70     connect(KeyButtonShortcut, SIGNAL(keySequenceChanged(QKeySequence)), this, SLOT(changedShortcut(QKeySequence)));
71     // track modifications:
72     connect(leDistinctName, SIGNAL(textChanged(QString)), SLOT(setModified()));
73     connect(leTitle, SIGNAL(textChanged(QString)), SLOT(setModified()));
74     connect(ButtonIcon, SIGNAL(iconChanged(QString)), SLOT(setModified()));
75     connect(cbCategory, SIGNAL(currentTextChanged(QString)), SLOT(setModified()));
76     connect(leTooltip, SIGNAL(textChanged(QString)), SLOT(setModified()));
77     connect(textDescription, SIGNAL(textChanged()), SLOT(setModified()));
78     connect(leCommandline, SIGNAL(textChanged(QString)), SLOT(setModified()));
79     connect(leStartpath, SIGNAL(textChanged(QString)), SLOT(setModified()));
80     connect(chkSeparateStdError, SIGNAL(clicked()), SLOT(setModified()));
81     connect(radioCollectOutput, SIGNAL(clicked()), SLOT(setModified()));
82     connect(radioNormal, SIGNAL(clicked()), SLOT(setModified()));
83     connect(radioTE, SIGNAL(clicked()), SLOT(setModified()));
84     connect(radioTerminal, SIGNAL(clicked()), SLOT(setModified()));
85     connect(radioLocal, SIGNAL(clicked()), SLOT(setModified()));
86     connect(radioUrl, SIGNAL(clicked()), SLOT(setModified()));
87     connect(KeyButtonShortcut, SIGNAL(keySequenceChanged(QKeySequence)), SLOT(setModified()));
88     connect(chkEnabled, SIGNAL(clicked()), SLOT(setModified()));
89     connect(leDifferentUser, SIGNAL(textChanged(QString)), SLOT(setModified()));
90     connect(chkDifferentUser, SIGNAL(clicked()), SLOT(setModified()));
91     connect(chkConfirmExecution, SIGNAL(clicked()), SLOT(setModified()));
92     connect(chkSeparateStdError, SIGNAL(clicked()), SLOT(setModified()));
93     // The modified-state of the ShowOnly-lists is tracked in the access-functions below
94 }
95 
~ActionProperty()96 ActionProperty::~ActionProperty()
97 {
98 }
99 
changedShortcut(const QKeySequence & shortcut)100 void ActionProperty::changedShortcut(const QKeySequence& shortcut)
101 {
102     KeyButtonShortcut->setKeySequence(shortcut);
103 }
104 
105 
clear()106 void ActionProperty::clear()
107 {
108     _action = 0;
109 
110     // This prevents the changed-signal from being emitted during the GUI-update
111     _modified = true; // The real state is set at the end of this function.
112 
113     leDistinctName->clear();
114     cbCategory->clearEditText();
115     leTitle->clear();
116     leTooltip->clear();
117     textDescription->clear();
118     leCommandline->clear();
119     leStartpath->clear();
120     KeyButtonShortcut->clearKeySequence();
121 
122     lbShowonlyProtocol->clear();
123     lbShowonlyPath->clear();
124     lbShowonlyMime->clear();
125     lbShowonlyFile->clear();
126 
127     chkSeparateStdError->setChecked(false);
128     radioNormal->setChecked(true);
129 
130     radioLocal->setChecked(true);
131 
132     chkEnabled->setChecked(true);
133 
134     chkConfirmExecution->setChecked(false);
135 
136     ButtonIcon->resetIcon();
137 
138     leDifferentUser->clear();
139     chkDifferentUser->setChecked(false);
140 
141     setModified(false);
142 }
143 
updateGUI(KrAction * action)144 void ActionProperty::updateGUI(KrAction *action)
145 {
146     if (action)
147         _action = action;
148     if (! _action)
149         return;
150 
151     // This prevents the changed-signal from being emitted during the GUI-update.
152     _modified = true; // The real state is set at the end of this function.
153 
154     leDistinctName->setText(_action->objectName());
155     cbCategory->lineEdit()->setText(_action->category());
156     leTitle->setText(_action->text());
157     leTooltip->setText(_action->toolTip());
158     textDescription->setText(_action->whatsThis());
159     leCommandline->setText(_action->command());
160     leCommandline->home(false);
161     leStartpath->setText(_action->startpath());
162     KeyButtonShortcut->setKeySequence(_action->shortcut());
163 
164     lbShowonlyProtocol->clear();
165     lbShowonlyProtocol->addItems(_action->showonlyProtocol());
166     lbShowonlyPath->clear();
167     lbShowonlyPath->addItems(_action->showonlyPath());
168     lbShowonlyMime->clear();
169     lbShowonlyMime->addItems(_action->showonlyMime());
170     lbShowonlyFile->clear();
171     lbShowonlyFile->addItems(_action->showonlyFile());
172 
173     chkSeparateStdError->setChecked(false);
174     switch (_action->execType()) {
175     case KrAction::CollectOutputSeparateStderr:
176         chkSeparateStdError->setChecked(true);
177         radioCollectOutput->setChecked(true);
178         break;
179     case KrAction::CollectOutput:
180         radioCollectOutput->setChecked(true);
181         break;
182     case KrAction::Terminal:
183         radioTerminal->setChecked(true);
184         break;
185     case KrAction::RunInTE:
186         radioTE->setChecked(true);
187         break;
188     default: // case KrAction::Normal:
189         radioNormal->setChecked(true);
190         break;
191     }
192 
193     if (_action->acceptURLs())
194         radioUrl->setChecked(true);
195     else
196         radioLocal->setChecked(true);
197 
198     chkEnabled->setChecked(_action->isVisible());
199 
200     chkConfirmExecution->setChecked(_action->confirmExecution());
201 
202     if (! _action->icon().isNull())
203         ButtonIcon->setIcon(_action->icon());
204     else
205         ButtonIcon->resetIcon();
206 
207     leDifferentUser->setText(_action->user());
208     if (_action->user().isEmpty())
209         chkDifferentUser->setChecked(false);
210     else
211         chkDifferentUser->setChecked(true);
212 
213     setModified(false);
214 }
215 
updateAction(KrAction * action)216 void ActionProperty::updateAction(KrAction *action)
217 {
218     if (action)
219         _action = action;
220     if (! _action)
221         return;
222 
223     if (_action->category() != cbCategory->currentText()) {
224         _action->setCategory(cbCategory->currentText());
225         // Update the category-list
226         cbCategory->clear();
227         cbCategory->addItems(krUserAction->allCategories());
228         cbCategory->lineEdit()->setText(_action->category());
229     }
230 
231     _action->setObjectName(leDistinctName->text());
232     _action->setText(leTitle->text());
233     _action->setToolTip(leTooltip->text());
234     _action->setWhatsThis(textDescription->toPlainText());
235     _action->setCommand(leCommandline->text());
236     _action->setStartpath(leStartpath->text());
237     _action->setShortcut(KeyButtonShortcut->keySequence());
238 
239     QStringList list;
240 
241     for (int i1 = 0; i1 != lbShowonlyProtocol->count(); i1++) {
242         QListWidgetItem* lbi = lbShowonlyProtocol->item(i1);
243 
244         list << lbi->text();
245     }
246     _action->setShowonlyProtocol(list);
247 
248     list = QStringList();
249     for (int i1 = 0; i1 != lbShowonlyPath->count(); i1++) {
250         QListWidgetItem* lbi = lbShowonlyPath->item(i1);
251 
252         list << lbi->text();
253     }
254     _action->setShowonlyPath(list);
255 
256     list = QStringList();
257     for (int i1 = 0; i1 != lbShowonlyMime->count(); i1++) {
258         QListWidgetItem* lbi = lbShowonlyMime->item(i1);
259 
260         list << lbi->text();
261     }
262     _action->setShowonlyMime(list);
263 
264     list = QStringList();
265     for (int i1 = 0; i1 != lbShowonlyFile->count(); i1++) {
266         QListWidgetItem* lbi = lbShowonlyFile->item(i1);
267 
268         list << lbi->text();
269     }
270     _action->setShowonlyFile(list);
271 
272     if (radioCollectOutput->isChecked() && chkSeparateStdError->isChecked())
273         _action->setExecType(KrAction::CollectOutputSeparateStderr);
274     else if (radioCollectOutput->isChecked() && ! chkSeparateStdError->isChecked())
275         _action->setExecType(KrAction::CollectOutput);
276     else if (radioTerminal->isChecked())
277         _action->setExecType(KrAction::Terminal);
278     else if (radioTE->isChecked())
279         _action->setExecType(KrAction::RunInTE);
280     else
281         _action->setExecType(KrAction::Normal);
282 
283     if (radioUrl->isChecked())
284         _action->setAcceptURLs(true);
285     else
286         _action->setAcceptURLs(false);
287 
288     _action->setEnabled(chkEnabled->isChecked());
289     _action->setVisible(chkEnabled->isChecked());
290 
291     _action->setConfirmExecution(chkConfirmExecution->isChecked());
292 
293     _action->setIcon(Icon(ButtonIcon->icon()));
294     _action->setIconName(ButtonIcon->icon());
295 
296     _action->setUser(leDifferentUser->text());
297 
298     setModified(false);
299 }
300 
addPlaceholder()301 void ActionProperty::addPlaceholder()
302 {
303     AddPlaceholderPopup popup(this);
304     QString exp = popup.getPlaceholder(mapToGlobal(
305                                            QPoint(
306                                                ButtonAddPlaceholder->pos().x() + ButtonAddPlaceholder->width() + 6, // 6 is the default margin
307                                                ButtonAddPlaceholder->pos().y()
308                                            )
309                                        ));
310     leCommandline->insert(exp);
311 }
312 
313 
addStartpath()314 void ActionProperty::addStartpath()
315 {
316     QString folder = QFileDialog::getExistingDirectory(this);
317     if (!folder.isEmpty()) {
318         leStartpath->setText(folder);
319     }
320 }
321 
322 
newProtocol()323 void ActionProperty::newProtocol()
324 {
325     bool ok;
326 
327     QString currentText;
328     if (lbShowonlyProtocol->currentItem())
329         currentText = lbShowonlyProtocol->currentItem()->text();
330 
331     QString text = QInputDialog::getText(this, i18n("New protocol"), i18n("Set a protocol:"),
332                                          QLineEdit::Normal, currentText, &ok);
333     if (ok && !text.isEmpty()) {
334         lbShowonlyProtocol->addItems(text.split(';'));
335         setModified();
336     }
337 }
338 
editProtocol()339 void ActionProperty::editProtocol()
340 {
341     if (lbShowonlyProtocol->currentItem() == 0)
342         return;
343 
344     bool ok;
345 
346     QString currentText = lbShowonlyProtocol->currentItem()->text();
347 
348     QString text = QInputDialog::getText(this, i18n("Edit Protocol"), i18n("Set another protocol:"),
349                                          QLineEdit::Normal, currentText, &ok);
350     if (ok && !text.isEmpty()) {
351         lbShowonlyProtocol->currentItem()->setText(text);
352         setModified();
353     }
354 }
355 
removeProtocol()356 void ActionProperty::removeProtocol()
357 {
358     if (lbShowonlyProtocol->currentItem() != 0) {
359         delete lbShowonlyProtocol->currentItem();
360         setModified();
361     }
362 }
363 
addPath()364 void ActionProperty::addPath()
365 {
366     QString folder = QFileDialog::getExistingDirectory(this);
367     if (!folder.isEmpty()) {
368         lbShowonlyPath->addItem(folder);
369         setModified();
370     }
371 }
372 
editPath()373 void ActionProperty::editPath()
374 {
375     if (lbShowonlyPath->currentItem() == 0)
376         return;
377 
378     bool ok;
379 
380     QString currentText = lbShowonlyPath->currentItem()->text();
381 
382     QString text = QInputDialog::getText(this, i18n("Edit Path"), i18n("Set another path:"),
383                                          QLineEdit::Normal, currentText, &ok);
384     if (ok && !text.isEmpty()) {
385         lbShowonlyPath->currentItem()->setText(text);
386         setModified();
387     }
388 }
389 
removePath()390 void ActionProperty::removePath()
391 {
392     if (lbShowonlyPath->currentItem() != 0) {
393         delete lbShowonlyPath->currentItem();
394         setModified();
395     }
396 }
397 
addMime()398 void ActionProperty::addMime()
399 {
400     bool ok;
401 
402     QString currentText;
403     if (lbShowonlyMime->currentItem())
404         currentText = lbShowonlyMime->currentItem()->text();
405 
406     QString text = QInputDialog::getText(this, i18n("New MIME Type"), i18n("Set a MIME type:"),
407                                          QLineEdit::Normal, currentText, &ok);
408     if (ok && !text.isEmpty()) {
409         lbShowonlyMime->addItems(text.split(';'));
410         setModified();
411     }
412 }
413 
editMime()414 void ActionProperty::editMime()
415 {
416     if (lbShowonlyMime->currentItem() == 0)
417         return;
418 
419     bool ok;
420 
421     QString currentText = lbShowonlyMime->currentItem()->text();
422 
423     QString text = QInputDialog::getText(this, i18n("Edit MIME Type"), i18n("Set another MIME type:"),
424                                          QLineEdit::Normal, currentText, &ok);
425     if (ok && !text.isEmpty()) {
426         lbShowonlyMime->currentItem()->setText(text);
427         setModified();
428     }
429 }
430 
removeMime()431 void ActionProperty::removeMime()
432 {
433     if (lbShowonlyMime->currentItem() != 0) {
434         delete lbShowonlyMime->currentItem();
435         setModified();
436     }
437 }
438 
newFile()439 void ActionProperty::newFile()
440 {
441     bool ok;
442 
443     QString currentText;
444     if (lbShowonlyFile->currentItem())
445         currentText = lbShowonlyFile->currentItem()->text();
446 
447     QString text = QInputDialog::getText(this, i18n("New File Name"), i18n("Set a file name:"),
448                                          QLineEdit::Normal, currentText, &ok);
449     if (ok && !text.isEmpty()) {
450         lbShowonlyFile->addItems(text.split(';'));
451         setModified();
452     }
453 }
454 
editFile()455 void ActionProperty::editFile()
456 {
457     if (lbShowonlyFile->currentItem() == 0)
458         return;
459 
460     bool ok;
461 
462     QString currentText = lbShowonlyFile->currentItem()->text();
463 
464     QString text = QInputDialog::getText(this, i18n("Edit File Name"), i18n("Set another file name:"),
465                                          QLineEdit::Normal, currentText, &ok);
466     if (ok && !text.isEmpty()) {
467         lbShowonlyFile->currentItem()->setText(text);
468         setModified();
469     }
470 }
471 
removeFile()472 void ActionProperty::removeFile()
473 {
474     if (lbShowonlyFile->currentItem() != 0) {
475         delete lbShowonlyFile->currentItem();
476         setModified();
477     }
478 }
479 
480 
validProperties()481 bool ActionProperty::validProperties()
482 {
483     if (leDistinctName->text().simplified().isEmpty()) {
484         KMessageBox::error(this, i18n("Please set a unique name for the useraction"));
485         leDistinctName->setFocus();
486         return false;
487     }
488     if (leTitle->text().simplified().isEmpty()) {
489         KMessageBox::error(this, i18n("Please set a title for the menu entry"));
490         leTitle->setFocus();
491         return false;
492     }
493     if (leCommandline->text().simplified().isEmpty()) {
494         KMessageBox::error(this, i18n("Command line is empty"));
495         leCommandline->setFocus();
496         return false;
497     }
498     if (leDistinctName->isEnabled())
499         if (krApp->actionCollection()->action(leDistinctName->text())) {
500             KMessageBox::error(this,
501                                i18n("There already is an action with this name.\n"
502                                     "If you do not have such a useraction the name is used by Krusader for an internal action.")
503                               );
504             leDistinctName->setFocus();
505             return false;
506         }
507 
508     return true;
509 }
510 
setModified(bool m)511 void ActionProperty::setModified(bool m)
512 {
513     if (m && !_modified) {   // emit only when the state _changes_to_true_,
514         emit changed();
515     }
516     _modified = m;
517 }
518 
519