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 &parameterLayout: 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