1 /*
2 Actiona
3 Copyright (C) 2005 Jonathan Mercier-Ganady
4
5 Actiona is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 Actiona is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 Contact : jmgr@jmgr.info
19 */
20
21 #include "actiondialog.h"
22 #include "ui_actiondialog.h"
23 #include "actioninstance.h"
24 #include "script.h"
25 #include "actiondefinition.h"
26 #include "elementdefinition.h"
27 #include "groupdefinition.h"
28 #include "parameterdefinition.h"
29 #include "abstractcodeeditor.h"
30 #include "codelineedit.h"
31 #include "linecombobox.h"
32 #include "helpbutton.h"
33
34 #ifdef ACT_PROFILE
35 #include "highresolutiontimer.h"
36 #endif
37
38 #include <QGroupBox>
39 #include <QLabel>
40 #include <QTimer>
41 #include <QDesktopServices>
42 #include <QUrl>
43 #include <QMessageBox>
44 #include <QFormLayout>
45 #include <QGridLayout>
46 #include <QComboBox>
47 #include <QSpinBox>
48 #include <QMenu>
49
50 #include <limits>
51 #include <algorithm>
52
ActionDialog(QAbstractItemModel * completionModel,ActionTools::Script * script,const ActionTools::ActionDefinition * actionDefinition,const QString & localeName,QWidget * parent)53 ActionDialog::ActionDialog(QAbstractItemModel *completionModel, ActionTools::Script *script, const ActionTools::ActionDefinition *actionDefinition, const QString &localeName, QWidget *parent)
54 : QDialog(parent),
55 ParameterContainer(script),
56 ui(new Ui::ActionDialog),
57 mActionInstance(nullptr),
58 mScript(script),
59 mCurrentLine(-1),
60 mCurrentColumn(-1),
61 mCurrentException(-1),
62 mCompletionModel(completionModel),
63 mExceptionsLayout(new QGridLayout),
64 mTabWidget(new QTabWidget(this)),
65 mExceptionsTabWidget(new QWidget(mTabWidget)),
66 mCommonTabWidget(new QWidget(mTabWidget)),
67 mPauseBeforeSpinBox(new QSpinBox(this)),
68 mPauseAfterSpinBox(new QSpinBox(this)),
69 mTimeoutSpinBox(new QSpinBox(this))
70 {
71 #ifdef ACT_PROFILE
72 Tools::HighResolutionTimer timer("ActionDialog creation " + actionDefinition->id());
73 #endif
74 ui->setupUi(this);
75
76 ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true);
77
78 //Init of texts & images
79 ui->actionIcon->setPixmap(actionDefinition->cachedIcon());
80 ui->actionName->setText(QStringLiteral("<h2>") + actionDefinition->name() + QStringLiteral("</h2>"));
81 ui->actionDescription->setText(QStringLiteral("<i>") + actionDefinition->description() + QStringLiteral("</i>"));
82 ui->helpPushButton->setTopic(QStringLiteral("%1:actions:%2").arg(localeName.left(2)).arg(actionDefinition->id().toLower()));
83
84 bool worksUnderThisOS = actionDefinition->worksUnderThisOS();
85 ui->actionOSAvailability->setVisible(!worksUnderThisOS);
86 ui->actionOSAvailabilityIcon->setVisible(!worksUnderThisOS);
87
88 //Init of tabs & group boxes
89 QStringList tabs = actionDefinition->tabs();
90 if(tabs.count() == 0)
91 tabs << QStringLiteral(QT_TRANSLATE_NOOP("ActionTabs", "Parameters"));
92
93 int tabCount = tabs.count();
94
95 QVector<QGroupBox *> groupBoxes[2];
96
97 for(auto ¶meterLayout: mParameterLayouts)
98 {
99 for(int i = 0; i < tabCount; ++i)
100 parameterLayout.append(new QFormLayout);
101 }
102
103 int tabIndex = 0;
104 for(const QString &tab: tabs)
105 {
106 QWidget *widget = new QWidget;
107 auto layout = new QVBoxLayout(widget);
108
109 QGroupBox *inputParametersGroupBox = new QGroupBox(tr("Input parameters"), widget);
110 inputParametersGroupBox->setStyleSheet(QStringLiteral("QGroupBox { background-color: #DDDDFF; }"));
111 inputParametersGroupBox->setLayout(mParameterLayouts[InputParameters][tabIndex]);
112 groupBoxes[InputParameters].append(inputParametersGroupBox);
113 QGroupBox *outputParametersGroupBox = new QGroupBox(tr("Output parameters"), widget);
114 outputParametersGroupBox->setStyleSheet(QStringLiteral("QGroupBox { background-color: #ffedce; }"));
115 outputParametersGroupBox->setLayout(mParameterLayouts[OutputParameters][tabIndex]);
116 groupBoxes[OutputParameters].append(outputParametersGroupBox);
117
118 layout->addWidget(inputParametersGroupBox);
119 layout->addWidget(outputParametersGroupBox);
120
121 widget->setLayout(layout);
122
123 mParameterTabWidgets.append(widget);
124 mTabWidget->addTab(widget, QApplication::translate("ActionTabs", tab.toUtf8().constData()));
125
126 ++tabIndex;
127 }
128
129 ui->parametersLayout->addWidget(mTabWidget);
130
131 //Init of common parameters
132 auto commonParametersLayout = new QVBoxLayout;
133 QGroupBox *inputCommonParametersGroupBox = new QGroupBox(tr("Input parameters"), mCommonTabWidget);
134 inputCommonParametersGroupBox->setStyleSheet(QStringLiteral("QGroupBox { background-color: #DDDDFF; }"));
135 auto inputCommonParametersLayout = new QFormLayout;
136
137 mPauseBeforeSpinBox->setToolTip(tr("Pause before executing the action"));
138 mPauseAfterSpinBox->setToolTip(tr("Pause after executing the action"));
139 mTimeoutSpinBox->setToolTip(tr("Action maximal execution time"));
140
141 mPauseBeforeSpinBox->setSingleStep(100);
142 mPauseAfterSpinBox->setSingleStep(100);
143 mTimeoutSpinBox->setSingleStep(100);
144
145 mPauseBeforeSpinBox->setMaximum(std::numeric_limits<int>::max());
146 mPauseAfterSpinBox->setMaximum(std::numeric_limits<int>::max());
147 mTimeoutSpinBox->setMaximum(std::numeric_limits<int>::max());
148
149 mPauseBeforeSpinBox->setSuffix(tr(" ms", "milliseconds"));
150 mPauseAfterSpinBox->setSuffix(tr(" ms", "milliseconds"));
151 mTimeoutSpinBox->setSuffix(tr(" ms", "milliseconds"));
152
153 mPauseBeforeSpinBox->setSpecialValueText(tr("No pause before"));
154 mPauseAfterSpinBox->setSpecialValueText(tr("No pause after"));
155 mTimeoutSpinBox->setSpecialValueText(tr("No timeout"));
156
157 inputCommonParametersLayout->addRow(tr("Pause before:"), mPauseBeforeSpinBox);
158 inputCommonParametersLayout->addRow(tr("Pause after:"), mPauseAfterSpinBox);
159 inputCommonParametersLayout->addRow(tr("Timeout:"), mTimeoutSpinBox);
160
161 inputCommonParametersGroupBox->setLayout(inputCommonParametersLayout);
162 commonParametersLayout->addWidget(inputCommonParametersGroupBox);
163 mCommonTabWidget->setLayout(commonParametersLayout);
164 mTabWidget->addTab(mCommonTabWidget, tr("Common"));
165
166 //Init of exceptions
167 const QList<ActionTools::ElementDefinition *> elements(actionDefinition->elements());
168
169 if(!elements.empty())
170 {
171 QStringList exceptionActionsNames;
172 for(const auto &exceptionActionName: ActionTools::ActionException::ExceptionActionName)
173 exceptionActionsNames << QApplication::translate("ActionException::ExceptionActionName", exceptionActionName.toLatin1().constData());
174
175 QList<ActionTools::ActionException *> actionExceptions = actionDefinition->exceptions();
176
177 for(int i = 0, exceptionIndex = 0; i < ActionTools::ActionException::ExceptionCount + actionExceptions.count(); ++i)
178 {
179 QString exceptionName;
180 int exceptionId;
181
182 if(i < ActionTools::ActionException::ExceptionCount)
183 {
184 exceptionName = QApplication::translate("ActionException::ExceptionName", ActionTools::ActionException::ExceptionName[i].toLatin1().constData());
185 exceptionId = i;
186 }
187 else
188 {
189 ActionTools::ActionException *actionException = actionExceptions.at(exceptionIndex);
190 exceptionName = actionException->name();
191 exceptionId = actionException->id();
192 ++exceptionIndex;
193 }
194
195 QLabel *exceptionNameLabel = new QLabel(exceptionName + tr(":"), this);
196 exceptionNameLabel->setProperty("id", exceptionId);
197 mExceptionsLayout->addWidget(exceptionNameLabel, i, 0, Qt::AlignLeft);
198
199 auto actionComboBox = new QComboBox(this);
200 actionComboBox->addItems(exceptionActionsNames);
201 actionComboBox->setProperty("row", i);
202
203 connect(actionComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, &ActionDialog::currentExceptionActionChanged);
204
205 mExceptionsLayout->addWidget(actionComboBox, i, 1, Qt::AlignCenter);
206
207 ActionTools::LineComboBox *actionLineComboBox = new ActionTools::LineComboBox(*mScript, this);
208 actionLineComboBox->codeLineEdit()->setAllowTextCodeChange(false);
209 actionLineComboBox->codeLineEdit()->setShowEditorButton(false);
210
211 mExceptionsLayout->addWidget(actionLineComboBox, i, 2, Qt::AlignCenter);
212 }
213
214 auto layout = new QVBoxLayout;
215 layout->addLayout(mExceptionsLayout);
216 layout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding));
217 mExceptionsTabWidget->setLayout(layout);
218
219 mTabWidget->addTab(mExceptionsTabWidget, tr("Exceptions"));
220 }
221 else
222 delete mExceptionsLayout;
223
224 //Init of action infos
225 QString informations;
226 QString author = actionDefinition->author();
227 QString email = actionDefinition->email();
228 QString website = actionDefinition->website();
229 Tools::Version version = actionDefinition->version();
230 ActionTools::ActionStatus status = actionDefinition->status();
231
232 if(!author.isEmpty())
233 {
234 informations = tr("By ");
235 if(!email.isEmpty())
236 informations += QStringLiteral("<a href=\"mailto:%1\">%2</a>").arg(email).arg(author);
237 else
238 informations += author;
239 }
240
241 if(!website.isEmpty())
242 {
243 if(!author.isEmpty())
244 informations += QStringLiteral(" - ");
245 informations += QStringLiteral("<a href=\"http://%1\">%1</a>").arg(website);
246 }
247
248 if(!informations.isEmpty())
249 informations += QStringLiteral("<br/>");
250
251 informations += tr("Version %1").arg(version.toString());
252
253 QString statusString;
254
255 switch(status)
256 {
257 case ActionTools::Alpha: statusString = tr("Alpha"); break;
258 case ActionTools::Beta: statusString = tr("Beta"); break;
259 case ActionTools::Testing: statusString = tr("Testing"); break;
260 case ActionTools::Stable: statusString = tr("Stable"); break;
261 }
262
263 informations += QStringLiteral(" (%1)").arg(statusString);
264
265 ui->actionInfo->setText(informations);
266
267 //Init of parameters
268 for(ActionTools::ElementDefinition *element: elements)
269 {
270 if(auto currentParameter = qobject_cast<ActionTools::ParameterDefinition *>(element))
271 addParameter(currentParameter, currentParameter->tab());
272 else if(auto currentGroup = qobject_cast<ActionTools::GroupDefinition *>(element))
273 {
274 for(ActionTools::ParameterDefinition *parameter: currentGroup->members())
275 addParameter(parameter, currentGroup->tab());
276 }
277 }
278
279 for(ActionTools::ElementDefinition *element: elements)
280 {
281 if(auto currentGroup = qobject_cast<ActionTools::GroupDefinition *>(element))
282 currentGroup->init();
283 }
284
285 for(int i = 0; i < tabCount; ++i)
286 {
287 int parameterCount = 0;
288
289 for(int parameterType = 0; parameterType < 2; ++parameterType)
290 {
291 if(mParameterLayouts[parameterType].at(i)->count() == 0)
292 groupBoxes[parameterType][i]->hide();
293 else
294 ++parameterCount;
295 }
296
297 if(parameterCount == 0)
298 mParameterTabWidgets[i]->layout()->addWidget(new QLabel(tr("<i>No parameters</i>"), mParameterTabWidgets[i]));
299 }
300
301 adjustSize();
302 }
303
~ActionDialog()304 ActionDialog::~ActionDialog()
305 {
306 delete ui;
307 }
308
createVariablesMenu(QWidget * parent) const309 QMenu *ActionDialog::createVariablesMenu(QWidget *parent) const
310 {
311 QSet<QString> thisActionsVariables;
312 for(ActionTools::ParameterDefinition *parameter: mParameters)
313 {
314 for(QWidget *editor: parameter->editors())
315 {
316 if(auto codeEditor = dynamic_cast<ActionTools::AbstractCodeEditor *>(editor))
317 thisActionsVariables.unite(codeEditor->findVariables());
318 }
319 }
320
321 QStringList variableList = thisActionsVariables.unite(mOtherActionsVariables).toList();
322 std::sort(variableList.begin(), variableList.end());
323
324 if(variableList.isEmpty())
325 return nullptr;
326
327 auto back = new QMenu(parent);
328
329 for(const QString &variable: variableList)
330 back->addAction(variable);
331
332 return back;
333 }
334
accept()335 void ActionDialog::accept()
336 {
337 #ifdef ACT_PROFILE
338 Tools::HighResolutionTimer timer("ActionDialog accept");
339 #endif
340
341 for(ActionTools::ParameterDefinition *parameter: mParameters)
342 parameter->save(mActionInstance);
343
344 if(!mParameters.empty())
345 {
346 for(int i = 0; i < mExceptionsLayout->rowCount(); ++i)
347 {
348 auto exceptionNameLabel = qobject_cast<QLabel *>(mExceptionsLayout->itemAtPosition(i, 0)->widget());
349 auto exceptionActionComboBox = qobject_cast<QComboBox *>(mExceptionsLayout->itemAtPosition(i, 1)->widget());
350 auto lineComboBox = qobject_cast<ActionTools::LineComboBox *>(mExceptionsLayout->itemAtPosition(i, 2)->widget());
351 ActionTools::ActionException::Exception exception = static_cast<ActionTools::ActionException::Exception>(exceptionNameLabel->property("id").toInt());
352 auto exceptionAction = static_cast<ActionTools::ActionException::ExceptionAction>(exceptionActionComboBox->currentIndex());
353
354 mActionInstance->setExceptionActionInstance(exception,
355 ActionTools::ActionException::ExceptionActionInstance(exceptionAction,
356 lineComboBox->currentText()));
357 }
358 }
359
360 mActionInstance->setPauseBefore(mPauseBeforeSpinBox->value());
361 mActionInstance->setPauseAfter(mPauseAfterSpinBox->value());
362 mActionInstance->setTimeout(mTimeoutSpinBox->value());
363
364 QDialog::accept();
365 }
366
exec(ActionTools::ActionInstance * actionInstance,const QString & field,const QString & subField,int currentLine,int currentColumn)367 int ActionDialog::exec(ActionTools::ActionInstance *actionInstance, const QString &field, const QString &subField, int currentLine, int currentColumn)
368 {
369 QTimer::singleShot(1, this, SLOT(postInit()));
370
371 mActionInstance = actionInstance;
372 mCurrentField = field;
373 mCurrentSubField = subField;
374 mCurrentLine = currentLine;
375 mCurrentColumn = currentColumn;
376
377 return QDialog::exec();
378 }
379
exec(ActionTools::ActionInstance * actionInstance,int exception)380 int ActionDialog::exec(ActionTools::ActionInstance *actionInstance, int exception)
381 {
382 QTimer::singleShot(1, this, SLOT(postInit()));
383
384 mActionInstance = actionInstance;
385 mCurrentException = exception;
386
387 return QDialog::exec();
388 }
389
postInit()390 void ActionDialog::postInit()
391 {
392 #ifdef ACT_PROFILE
393 Tools::HighResolutionTimer timer("ActionDialog postInit");
394 #endif
395 mOtherActionsVariables = mScript->findVariables(nullptr, mActionInstance);//Find in all actions except this one
396
397 for(ActionTools::ParameterDefinition *parameter: mParameters)
398 {
399 parameter->actionUpdate(mScript);
400 parameter->load(mActionInstance);
401 }
402
403 mScript->updateLineModel();
404
405 if(!mParameters.empty())
406 {
407 const ActionTools::ExceptionActionInstancesHash exceptionActionInstances = mActionInstance->exceptionActionInstances();
408 QList<ActionTools::ActionException *> actionExceptions = mActionInstance->definition()->exceptions();
409
410 for(int i = 0, exceptionIndex = 0; i < ActionTools::ActionException::ExceptionCount + actionExceptions.count(); ++i)
411 {
412 int exceptionId;
413
414 if(i < ActionTools::ActionException::ExceptionCount)
415 exceptionId = i;
416 else
417 {
418 ActionTools::ActionException *actionException = actionExceptions.at(exceptionIndex);
419 exceptionId = actionException->id();
420 ++exceptionIndex;
421 }
422
423 ActionTools::ActionException::ExceptionActionInstance exceptionActionInstance = exceptionActionInstances.value(static_cast<ActionTools::ActionException::Exception>(exceptionId));
424
425 auto exceptionActionComboBox = qobject_cast<QComboBox *>(mExceptionsLayout->itemAtPosition(i, 1)->widget());
426 auto lineComboBox = qobject_cast<ActionTools::LineComboBox *>(mExceptionsLayout->itemAtPosition(i, 2)->widget());
427
428 lineComboBox->setCompletionModel(mCompletionModel);
429 lineComboBox->setParameterContainer(this);
430
431 exceptionActionComboBox->setCurrentIndex(exceptionActionInstance.action());
432 lineComboBox->setValue(lineComboBox->isCode(), exceptionActionInstance.line());
433 lineComboBox->setEnabled(exceptionActionInstance.action() == ActionTools::ActionException::GotoLineExceptionAction);
434 }
435 }
436
437 mPauseBeforeSpinBox->setValue(mActionInstance->pauseBefore());
438 mPauseAfterSpinBox->setValue(mActionInstance->pauseAfter());
439 mTimeoutSpinBox->setValue(mActionInstance->timeout());
440
441 if(!mCurrentField.isEmpty())
442 {
443 for(ActionTools::ParameterDefinition *parameterDefinition: mParameters)
444 {
445 if(parameterDefinition->name().original() == mCurrentField && parameterDefinition->editors().count() > 0)
446 {
447 QWidget *editorWidget = parameterDefinition->editors().at(0);
448 if(!editorWidget)
449 continue;
450
451 mTabWidget->setCurrentWidget(mParameterTabWidgets.at(parameterDefinition->tab()));
452 editorWidget->setFocus();
453
454 if(!mCurrentSubField.isEmpty())
455 {
456 QString value = mActionInstance->subParameter(mCurrentField, mCurrentSubField).value();
457 if(value.contains(QLatin1Char('\n')))//Multiline : open the editor
458 {
459 if(auto codeEditor = dynamic_cast<ActionTools::AbstractCodeEditor *>(editorWidget))
460 codeEditor->openEditor(mCurrentLine, mCurrentColumn);
461 }
462 }
463
464 break;
465 }
466 }
467 }
468
469 if(!mParameters.empty())
470 {
471 for(int i = 0; i < mExceptionsLayout->rowCount(); ++i)
472 {
473 auto exceptionNameLabel = qobject_cast<QLabel *>(mExceptionsLayout->itemAtPosition(i, 0)->widget());
474 auto lineComboBox = qobject_cast<ActionTools::LineComboBox *>(mExceptionsLayout->itemAtPosition(i, 2)->widget());
475 ActionTools::ActionException::Exception exception = static_cast<ActionTools::ActionException::Exception>(exceptionNameLabel->property("id").toInt());
476
477 if(exception == mCurrentException)
478 {
479 lineComboBox->setFocus();
480 mTabWidget->setCurrentWidget(mExceptionsTabWidget);
481 break;
482 }
483 }
484 }
485 }
486
currentExceptionActionChanged(int index)487 void ActionDialog::currentExceptionActionChanged(int index)
488 {
489 auto comboBox = qobject_cast<QComboBox *>(sender());
490 if(!comboBox)
491 return;
492
493 auto exceptionAction = static_cast<ActionTools::ActionException::ExceptionAction>(index);
494 int row = comboBox->property("row").toInt();
495 QLayoutItem *item = mExceptionsLayout->itemAtPosition(row, 2);
496 if(!item)
497 return;
498
499 QWidget *widget = item->widget();
500 if(!widget)
501 return;
502
503 widget->setEnabled(exceptionAction == ActionTools::ActionException::GotoLineExceptionAction);
504 }
505
addParameter(ActionTools::ParameterDefinition * parameter,int tab)506 void ActionDialog::addParameter(ActionTools::ParameterDefinition *parameter, int tab)
507 {
508 #ifdef Q_OS_LINUX
509 if(!(parameter->operatingSystems() & ActionTools::WorksOnGnuLinux))
510 return;
511 #endif
512 #ifdef Q_OS_WIN
513 if(!(parameter->operatingSystems() & ActionTools::WorksOnWindows))
514 return;
515 #endif
516 #ifdef Q_OS_MAC
517 if(!(parameter->operatingSystems() & ActionTools::WorksOnMac))
518 return;
519 #endif
520
521 auto layout = new QBoxLayout(parameter->editorsOrientation() == Qt::Horizontal ? QBoxLayout::LeftToRight : QBoxLayout::TopToBottom );
522 layout->setMargin(0);
523 layout->setSpacing(1);
524
525 QWidget *parentWidget = new QWidget(this);
526 parentWidget->setLayout(layout);
527
528 parameter->buildEditors(mScript, parentWidget);
529 for(QWidget *editor: parameter->editors())
530 {
531 if(auto codeEditor = dynamic_cast<ActionTools::AbstractCodeEditor *>(editor))
532 {
533 codeEditor->setCompletionModel(mCompletionModel);
534 codeEditor->setParameterContainer(this);
535 }
536
537 layout->addWidget(editor);
538 }
539
540 mParameters.append(parameter);
541
542 int parameterType = (parameter->category() == ActionTools::ParameterDefinition::INPUT ? InputParameters : OutputParameters);
543
544 QFormLayout *parameterLayout = mParameterLayouts[parameterType][tab];
545 parameterLayout->addRow(parameter->name().translated() + tr(":"), parentWidget);
546 }
547
548