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 "executer.h"
22 #include "script.h"
23 #include "actionfactory.h"
24 #include "executionwindow.h"
25 #include "codeinitializer.h"
26 #include "actiondefinition.h"
27 #include "actionexception.h"
28 #include "scriptagent.h"
29 #include "actioninstance.h"
30 #include "code/codetools.h"
31 #include "code/image.h"
32 #include "code/rawdata.h"
33 #include "code/prettyprinting.h"
34 #include "codeactiona.h"
35 
36 #ifdef ACT_PROFILE
37 #include "highresolutiontimer.h"
38 #endif
39 
40 #include <QDesktopWidget>
41 #include <QAction>
42 #include <QMainWindow>
43 #include <QApplication>
44 #include <QLocale>
45 #include <QProgressDialog>
46 #include <QScriptEngine>
47 #include <QScriptValueIterator>
48 
49 namespace LibExecuter
50 {
51 	Executer::ExecutionStatus Executer::mExecutionStatus = Executer::Stopped;
52 
Executer(QObject * parent)53 	Executer::Executer(QObject *parent)
54 		: QObject(parent),
55 		mExecutionWindow(new ExecutionWindow()),
56 		mConsoleWidget(new ActionTools::ConsoleWidget())
57 
58 	{
59         connect(mExecutionWindow, &ExecutionWindow::canceled, this, &Executer::stopExecution);
60         connect(mExecutionWindow, &ExecutionWindow::paused, this, &Executer::pauseExecution);
61         connect(mExecutionWindow, &ExecutionWindow::debug, this, &Executer::debugExecution);
62         connect(&mExecutionTimer, &QTimer::timeout, this, &Executer::updateTimerProgress);
63         connect(&mScriptEngineDebugger, &QScriptEngineDebugger::evaluationSuspended, mExecutionWindow, &ExecutionWindow::onEvaluationPaused);
64         connect(&mScriptEngineDebugger, &QScriptEngineDebugger::evaluationResumed, mExecutionWindow, &ExecutionWindow::onEvaluationResumed);
65         connect(&mScriptEngineDebugger, &QScriptEngineDebugger::evaluationSuspended, this, &Executer::executionPaused);
66         connect(&mScriptEngineDebugger, &QScriptEngineDebugger::evaluationResumed, this, &Executer::executionResumed);
67 
68 		mScriptEngineDebugger.setAutoShowStandardWindow(false);
69 
70 		mConsoleWidget->setWindowFlags(Qt::Tool |
71 					   Qt::WindowStaysOnTopHint |
72 					   Qt::CustomizeWindowHint |
73 					   Qt::WindowTitleHint);
74 	}
75 
~Executer()76 	Executer::~Executer()
77 	{
78 		delete mExecutionWindow;
79 		delete mConsoleWidget;
80 	}
81 
setup(ActionTools::Script * script,ActionTools::ActionFactory * actionFactory,bool showExecutionWindow,int executionWindowPosition,int executionWindowScreen,bool showConsoleWindow,int consoleWindowPosition,int consoleWindowScreen,int pauseBefore,int pauseAfter,Tools::Version actionaVersion,Tools::Version scriptVersion,bool isActExec,QStandardItemModel * consoleModel)82 	void Executer::setup(ActionTools::Script *script,
83 			   ActionTools::ActionFactory *actionFactory,
84 			   bool showExecutionWindow,
85 			   int executionWindowPosition,
86 			   int executionWindowScreen,
87 			   bool showConsoleWindow,
88 			   int consoleWindowPosition,
89 			   int consoleWindowScreen,
90 			   int pauseBefore,
91 			   int pauseAfter,
92                Tools::Version actionaVersion,
93 			   Tools::Version scriptVersion,
94 			   bool isActExec,
95 			   QStandardItemModel *consoleModel)
96 	{
97 		mScript = script;
98 		mScriptEngine = new QScriptEngine(this);
99 
100 		for(QString extension: mScriptEngine->availableExtensions())
101 			mScriptEngine->importExtension(extension);
102 
103 		mActionFactory = actionFactory;
104 		mShowExecutionWindow = showExecutionWindow;
105 		mExecutionWindowPosition = executionWindowPosition;
106 		mExecutionWindowScreen = executionWindowScreen;
107 		mShowConsoleWindow = showConsoleWindow;
108 		mConsoleWindowPosition = consoleWindowPosition;
109 		mConsoleWindowScreen = consoleWindowScreen;
110 		mPauseBefore = pauseBefore;
111 		mPauseAfter = pauseAfter;
112 		mCurrentActionIndex = 0;
113 		mExecutionStarted = false;
114 		mExecutionEnded = false;
115 		mExecuteOnlySelection = false;
116 		mProgressDialog = nullptr;
117 		mActiveActionsCount = 0;
118 		mExecutionPaused = false;
119         mActionaVersion = actionaVersion;
120 		mScriptVersion = scriptVersion;
121 		mIsActExec = isActExec;
122 
123         Code::setupPrettyPrinting(*mScriptEngine);
124 
125 		mScriptEngineDebugger.attachTo(mScriptEngine);
126 		mDebuggerWindow = mScriptEngineDebugger.standardWindow();
127 
128 		mScriptAgent = new ScriptAgent(mScriptEngine);
129 
130         connect(mScriptAgent, &ScriptAgent::executionStopped, this, &Executer::stopExecution);
131         connect(mScriptAgent, &ScriptAgent::evaluationStarted, mExecutionWindow, &ExecutionWindow::enableDebug);
132         connect(mScriptAgent, &ScriptAgent::evaluationStopped, mExecutionWindow, &ExecutionWindow::disableDebug);
133 
134 		QScriptEngineAgent *debuggerAgent = mScriptEngine->agent();
135 		mScriptEngine->setAgent(mScriptAgent);
136 		mScriptAgent->setDebuggerAgent(debuggerAgent);
137 
138 		mConsoleWidget->setup(consoleModel);
139 
140 		mExecutionTimer.setSingleShot(false);
141 		mExecutionTimer.setInterval(5);
142 		mConsoleWidget->updateClearButton();
143 	}
144 
currentActionInstance() const145     ActionTools::ActionInstance *Executer::currentActionInstance() const
146     {
147         if(mCurrentActionIndex < 0 || mCurrentActionIndex >= mScript->actionCount())
148             return nullptr;
149 
150         return mScript->actionAt(mCurrentActionIndex);
151     }
152 
printCall(QScriptContext * context,ActionTools::ConsoleWidget::Type type)153 	void printCall(QScriptContext *context, ActionTools::ConsoleWidget::Type type)
154 	{
155 		QApplication::processEvents();//Call this to prevent UI freeze when calling print often
156 
157 		QScriptValue calleeData = context->callee().data();
158 		auto executer = qobject_cast<Executer *>(calleeData.toQObject());
159 		QString message;
160 		ScriptAgent *agent = executer->scriptAgent();
161 
162 		if(!agent)
163 			return;
164 
165 		for(int argumentIndex = 0; argumentIndex < context->argumentCount(); ++argumentIndex)
166 			message += context->argument(argumentIndex).toString();
167 
168 		switch(executer->scriptAgent()->context())
169 		{
170 		case ScriptAgent::Parameters:
171 			executer->consoleWidget()->addScriptParameterLine(message,
172 															  agent->currentParameter(),
173 															  agent->currentLine(),
174 															  agent->currentColumn(),
175 															  type);
176 			break;
177 		case ScriptAgent::Actions:
178 			{
179 				ActionTools::ActionInstance *currentAction = executer->script()->actionAt(executer->currentActionIndex());
180 				qint64 currentActionRuntimeId = -1;
181 				if(currentAction)
182 					currentActionRuntimeId = currentAction->runtimeId();
183 
184 				executer->consoleWidget()->addUserLine(message,
185 													   currentActionRuntimeId,
186 													   context->engine()->globalObject().property(QStringLiteral("currentParameter")).toString(),
187 													   context->engine()->globalObject().property(QStringLiteral("currentSubParameter")).toString(),
188 													   agent->currentLine(),
189 													   agent->currentColumn(),
190 													   context->backtrace(),
191 													   type);
192 			}
193 			break;
194 		default:
195 			return;
196 		}
197 	}
198 
printFunction(QScriptContext * context,QScriptEngine * engine)199 	QScriptValue printFunction(QScriptContext *context, QScriptEngine *engine)
200 	{
201 		if(!Executer::isExecuterRunning())
202 			return QScriptValue();
203 
204 		if(context->argumentCount() < 1)
205 			return engine->undefinedValue();
206 
207 		printCall(context, ActionTools::ConsoleWidget::Information);
208 
209 		return engine->undefinedValue();
210 	}
211 
printWarningFunction(QScriptContext * context,QScriptEngine * engine)212 	QScriptValue printWarningFunction(QScriptContext *context, QScriptEngine *engine)
213 	{
214 		if(!Executer::isExecuterRunning())
215 			return QScriptValue();
216 
217 		if(context->argumentCount() < 1)
218 			return engine->undefinedValue();
219 
220 		printCall(context, ActionTools::ConsoleWidget::Warning);
221 
222 		return engine->undefinedValue();
223 	}
224 
printErrorFunction(QScriptContext * context,QScriptEngine * engine)225 	QScriptValue printErrorFunction(QScriptContext *context, QScriptEngine *engine)
226 	{
227 		if(!Executer::isExecuterRunning())
228 			return QScriptValue();
229 
230 		if(context->argumentCount() < 1)
231 			return engine->undefinedValue();
232 
233 		printCall(context, ActionTools::ConsoleWidget::Error);
234 
235 		return engine->undefinedValue();
236 	}
237 
clearConsoleFunction(QScriptContext * context,QScriptEngine * engine)238     QScriptValue clearConsoleFunction(QScriptContext *context, QScriptEngine *engine)
239     {
240         if(!Executer::isExecuterRunning())
241             return QScriptValue();
242 
243         QApplication::processEvents();//Call this to prevent UI freeze when calling clear often
244 
245         QScriptValue calleeData = context->callee().data();
246         auto executer = qobject_cast<Executer *>(calleeData.toQObject());
247 
248         executer->consoleWidget()->clearExceptSeparators();
249 
250         return engine->undefinedValue();
251     }
252 
callProcedureFunction(QScriptContext * context,QScriptEngine * engine)253     QScriptValue callProcedureFunction(QScriptContext *context, QScriptEngine *engine)
254     {
255         if(!Executer::isExecuterRunning())
256             return QScriptValue();
257 
258         if(context->argumentCount() < 1)
259             return engine->undefinedValue();
260 
261         QScriptValue calleeData = context->callee().data();
262         auto executer = qobject_cast<Executer *>(calleeData.toQObject());
263         ActionTools::ActionInstance *currentActionInstance = executer->currentActionInstance();
264 
265         if(currentActionInstance)
266             currentActionInstance->callProcedure(context->argument(0).toString());
267 
268         return engine->undefinedValue();
269     }
270 
startExecution(bool onlySelection,const QString & filename)271     bool Executer::startExecution(bool onlySelection, const QString &filename)
272 	{
273 		Q_ASSERT(mScriptAgent);
274 		Q_ASSERT(mScriptEngine);
275 
276 	#ifdef ACT_PROFILE
277 		Tools::HighResolutionTimer timer("Executer::startExecution");
278 	#endif
279 
280 		Code::CodeTools::addClassToScriptEngine<CodeActiona>(QStringLiteral("Actiona"), mScriptEngine);
281         CodeActiona::setActExec(mIsActExec);
282         CodeActiona::setActionaVersion(mActionaVersion);
283         CodeActiona::setScriptVersion(mScriptVersion);
284 		Code::CodeTools::addClassGlobalFunctionToScriptEngine(QStringLiteral("Actiona"), &CodeActiona::version, QStringLiteral("version"), mScriptEngine);
285 		Code::CodeTools::addClassGlobalFunctionToScriptEngine(QStringLiteral("Actiona"), &CodeActiona::scriptVersion, QStringLiteral("scriptVersion"), mScriptEngine);
286 		Code::CodeTools::addClassGlobalFunctionToScriptEngine(QStringLiteral("Actiona"), &CodeActiona::isActExec, QStringLiteral("isActExec"), mScriptEngine);
287 		Code::CodeTools::addClassGlobalFunctionToScriptEngine(QStringLiteral("Actiona"), &CodeActiona::isActiona, QStringLiteral("isActiona"), mScriptEngine);
288 
289 		mScriptAgent->setContext(ScriptAgent::ActionInit);
290         CodeInitializer::initialize(mScriptEngine, mScriptAgent, mActionFactory, filename);
291 		mScriptAgent->setContext(ScriptAgent::Parameters);
292 
293         QScriptValue script = mScriptEngine->newObject();
294 		mScriptEngine->globalObject().setProperty(QStringLiteral("Script"), script, QScriptValue::ReadOnly);
295 		script.setProperty(QStringLiteral("nextLine"), 1);
296 		script.setProperty(QStringLiteral("doNotResetPreviousActions"), false);
297 		script.setProperty(QStringLiteral("line"), 1, QScriptValue::ReadOnly);
298         QScriptValue callProcedureFun = mScriptEngine->newFunction(callProcedureFunction);
299         callProcedureFun.setData(mScriptEngine->newQObject(this));
300 		script.setProperty(QStringLiteral("callProcedure"), callProcedureFun);
301 
302 		QScriptValue console = mScriptEngine->newObject();
303 		mScriptEngine->globalObject().setProperty(QStringLiteral("Console"), console, QScriptValue::ReadOnly);
304 
305         QScriptValue function = mScriptEngine->newFunction(printFunction);
306         function.setData(mScriptEngine->newQObject(this));
307 		console.setProperty(QStringLiteral("print"), function);
308 
309         function = mScriptEngine->newFunction(printWarningFunction);
310         function.setData(mScriptEngine->newQObject(this));
311 		console.setProperty(QStringLiteral("printWarning"), function);
312 
313         function = mScriptEngine->newFunction(printErrorFunction);
314         function.setData(mScriptEngine->newQObject(this));
315 		console.setProperty(QStringLiteral("printError"), function);
316 
317         function = mScriptEngine->newFunction(clearConsoleFunction);
318         function.setData(mScriptEngine->newQObject(this));
319 		console.setProperty(QStringLiteral("clear"), function);
320 
321 		mExecuteOnlySelection = onlySelection;
322 		mCurrentActionIndex = 0;
323 		mActiveActionsCount = 0;
324 		mExecutionPaused = false;
325 
326 		bool initSucceeded = true;
327 		int lastBeginProcedure = -1;
328 
329 		mScript->clearProcedures();
330 		mScript->clearCallStack();
331 
332 		const QMap<QString, ActionTools::Resource> &resources = mScript->resources();
333         for(const QString &key: resources.keys())
334         {
335             const ActionTools::Resource &resource = resources.value(key);
336             QScriptValue value;
337 
338             switch(resource.type())
339             {
340             case ActionTools::Resource::BinaryType:
341             case ActionTools::Resource::TypeCount:
342                 value = Code::RawData::constructor(resource.data(), mScriptEngine);
343                 break;
344             case ActionTools::Resource::TextType:
345 				value = QString::fromUtf8(resource.data());
346                 break;
347             case ActionTools::Resource::ImageType:
348                 {
349                     QImage image;
350 
351                     if(!image.loadFromData(resource.data()))
352                     {
353                         mConsoleWidget->addResourceLine(tr("Invalid image resource"), key, ActionTools::ConsoleWidget::Error);
354 
355                         return false;
356                     }
357 
358                     value = Code::Image::constructor(image, mScriptEngine);
359                 }
360                 break;
361             }
362 
363             mScriptEngine->globalObject().setProperty(key, value, QScriptValue::ReadOnly | QScriptValue::Undeletable);
364         }
365 
366 		for(int actionIndex = 0; actionIndex < mScript->actionCount(); ++actionIndex)
367 		{
368 			ActionTools::ActionInstance *actionInstance = mScript->actionAt(actionIndex);
369 			actionInstance->reset();
370 			actionInstance->clearRuntimeParameters();
371 			actionInstance->setupExecution(mScriptEngine, mScript, actionIndex);
372 			mActionEnabled.append(true);
373 
374 			qint64 currentActionRuntimeId = -1;
375 			if(actionInstance)
376 				currentActionRuntimeId = actionInstance->runtimeId();
377 
378 			if(canExecuteAction(actionIndex) == CanExecute)
379 			{
380 				++mActiveActionsCount;
381 
382 				if(actionInstance->definition()->id() == QLatin1String("ActionBeginProcedure"))
383 				{
384 					if(lastBeginProcedure != -1)
385 					{
386 						mConsoleWidget->addActionLine(tr("Invalid Begin procedure action, you have to end the previous procedure before starting another one"), currentActionRuntimeId, QString(), QString(), -1, -1, ActionTools::ConsoleWidget::Error);
387 
388 						return false;
389 					}
390 
391 					lastBeginProcedure = actionIndex;
392 
393 					const ActionTools::SubParameter &nameParameter = actionInstance->subParameter(QStringLiteral("name"), QStringLiteral("value"));
394                     const QString &procedureName = nameParameter.value();
395 
396 					if(procedureName.isEmpty())
397 					{
398 						mConsoleWidget->addActionLine(tr("A procedure name cannot be empty"), currentActionRuntimeId, QString(), QString(), -1, -1, ActionTools::ConsoleWidget::Error);
399 
400 						return false;
401 					}
402 
403 					if(mScript->findProcedure(procedureName) != -1)
404 					{
405 						mConsoleWidget->addActionLine(tr("A procedure with the name \"%1\" has already been declared").arg(procedureName), currentActionRuntimeId, QString(), QString(), -1, -1, ActionTools::ConsoleWidget::Error);
406 
407 						return false;
408 					}
409 
410 					mScript->addProcedure(procedureName, actionIndex);
411 				}
412 				else if(actionInstance->definition()->id() == QLatin1String("ActionEndProcedure"))
413 				{
414 					if(lastBeginProcedure == -1)
415 					{
416 						mConsoleWidget->addActionLine(tr("Invalid End procedure"), currentActionRuntimeId, QString(), QString(), -1, -1, ActionTools::ConsoleWidget::Error);
417 
418 						return false;
419 					}
420 
421 					ActionTools::ActionInstance *beginProcedureActionInstance = mScript->actionAt(lastBeginProcedure);
422 
423 					actionInstance->setRuntimeParameter(QStringLiteral("procedureBeginLine"), lastBeginProcedure);
424 					beginProcedureActionInstance->setRuntimeParameter(QStringLiteral("procedureEndLine"), actionIndex);
425 
426 					lastBeginProcedure = -1;
427 				}
428 			}
429 		}
430 
431 		if(lastBeginProcedure != -1)
432 		{
433 			ActionTools::ActionInstance *actionInstance = mScript->actionAt(lastBeginProcedure);
434 			qint64 actionRuntimeId = -1;
435 			if(actionInstance)
436 				actionRuntimeId = actionInstance->runtimeId();
437 
438 			mConsoleWidget->addActionLine(tr("Begin procedure action without end procedure"), actionRuntimeId, QString(), QString(), -1, -1, ActionTools::ConsoleWidget::Error);
439 
440 			return false;
441 		}
442 
443 		for(int parameterIndex = 0; parameterIndex < mScript->parameterCount(); ++parameterIndex)
444 		{
445 			mScriptAgent->setCurrentParameter(parameterIndex);
446 
447 			const ActionTools::ScriptParameter &scriptParameter = mScript->parameter(parameterIndex);
448 			QRegExp nameRegExp(QStringLiteral("[a-z_][a-z0-9_]*"), Qt::CaseInsensitive);
449 
450 			if(!nameRegExp.exactMatch(scriptParameter.name()))
451 			{
452 				mConsoleWidget->addScriptParameterLine(tr("Incorrect parameter name: \"%1\"").arg(scriptParameter.name()),
453 													   parameterIndex,
454 													   -1,
455 													   -1,
456 													   ActionTools::ConsoleWidget::Error);
457 				initSucceeded = false;
458 				continue;
459 			}
460 
461 			QString value;
462 			if(scriptParameter.isCode())
463 			{
464 				QScriptValue result = mScriptEngine->evaluate(scriptParameter.value());
465 				if(result.isError())
466 				{
467 					mConsoleWidget->addScriptParameterLine(tr(R"(Error while evaluating parameter "%1", error message: "%2")")
468 														   .arg(scriptParameter.name())
469 														   .arg(result.toString()),
470 														   parameterIndex,
471 														   -1,
472 														   -1,
473 														   ActionTools::ConsoleWidget::Error);
474 					initSucceeded = false;
475 					continue;
476 				}
477 				else
478 					value = result.toString();
479 			}
480 			else
481 				value = scriptParameter.value();
482 
483 			mScriptEngine->globalObject().setProperty(scriptParameter.name(), value, QScriptValue::ReadOnly | QScriptValue::Undeletable);
484 		}
485 
486 		if(!initSucceeded || mScript->actionCount() == 0)
487 			return false;
488 
489 		if(mShowExecutionWindow)
490 		{
491 			QRect screenRect = QApplication::desktop()->availableGeometry(mExecutionWindowScreen);
492 			QPoint position;
493 
494 			if(mExecutionWindowPosition >= 0 && mExecutionWindowPosition <= 2)//Left
495 				position.setX(screenRect.left());
496 			else if(mExecutionWindowPosition >= 3 && mExecutionWindowPosition <= 5)//HCenter
497 				position.setX(screenRect.left() + screenRect.width() / 2 - mExecutionWindow->width() / 2);
498 			else if(mExecutionWindowPosition >= 6 && mExecutionWindowPosition <= 8)//Right
499 				position.setX(screenRect.left() + screenRect.width() - mExecutionWindow->width());
500 
501 			if(mExecutionWindowPosition == 0 || mExecutionWindowPosition == 3 || mExecutionWindowPosition == 6)//Up
502 				position.setY(screenRect.top());
503 			else if(mExecutionWindowPosition == 1 || mExecutionWindowPosition == 4 || mExecutionWindowPosition == 7)//VCenter
504 				position.setY(screenRect.top() + screenRect.height() / 2 - mExecutionWindow->height() / 2);
505 			else if(mExecutionWindowPosition == 2 || mExecutionWindowPosition == 5 || mExecutionWindowPosition == 8)//Down
506 				position.setY(screenRect.top() + screenRect.height() - mExecutionWindow->height());
507 
508 			mExecutionWindow->setPauseStatus(false);
509 			mExecutionWindow->move(position);
510 			mExecutionWindow->show();
511 		}
512 
513 		if(mShowConsoleWindow)
514 		{
515 			QRect screenRect = QApplication::desktop()->availableGeometry(mConsoleWindowScreen);
516 			QPoint position;
517 
518 			if(mConsoleWindowPosition >= 0 && mConsoleWindowPosition <= 2)//Left
519 				position.setX(screenRect.left());
520 			else if(mConsoleWindowPosition >= 3 && mConsoleWindowPosition <= 5)//HCenter
521 				position.setX(screenRect.left() + screenRect.width() / 2 - mConsoleWidget->width() / 2);
522 			else if(mConsoleWindowPosition >= 6 && mConsoleWindowPosition <= 8)//Right
523 				position.setX(screenRect.left() + screenRect.width() - mConsoleWidget->width());
524 
525 			if(mConsoleWindowPosition == 0 || mConsoleWindowPosition == 3 || mConsoleWindowPosition == 6)//Up
526 				position.setY(screenRect.top());
527 			else if(mConsoleWindowPosition == 1 || mConsoleWindowPosition == 4 || mConsoleWindowPosition == 7)//VCenter
528 				position.setY(screenRect.top() + screenRect.height() / 2 - mConsoleWidget->height() / 2);
529 			else if(mConsoleWindowPosition == 2 || mConsoleWindowPosition == 5 || mConsoleWindowPosition == 8)//Down
530 				position.setY(screenRect.top() + screenRect.height() - mConsoleWidget->height());
531 
532 			mConsoleWidget->move(position);
533 			mConsoleWidget->show();
534 		}
535 
536 		mExecutionStarted = true;
537 
538 		mScriptAgent->setContext(ScriptAgent::Actions);
539 
540 		mHasExecuted = true;
541 
542 		executeCurrentAction();
543 
544 		return true;
545 	}
546 
stopExecution()547 	void Executer::stopExecution()
548 	{
549         if(!mExecutionStarted)
550 			return;
551 
552 		mScriptAgent->pause(false);
553 		mScriptAgent->stopExecution(false);
554 		mScriptEngineDebugger.action(QScriptEngineDebugger::ContinueAction)->trigger();
555 
556 		mExecutionStarted = false;
557 		mExecutionStatus = Stopped;
558 
559         if(mScriptEngine)
560             mScriptEngine->abortEvaluation();
561 
562 		mExecutionTimer.stop();
563 
564 		if(mCurrentActionIndex >= 0 && mCurrentActionIndex < mScript->actionCount())
565 		{
566 			currentActionInstance()->disconnect();
567 			if(!mExecutionEnded)
568                 currentActionInstance()->doStopExecution();
569 		}
570 
571         mScript->executionStopped();
572 
573 		mScriptEngineDebugger.detach();
574 
575         if(mScriptAgent)
576         {
577             mScriptAgent->deleteLater();
578             mScriptAgent = nullptr;
579         }
580         if(mScriptEngine)
581         {
582             mScriptEngine->deleteLater();
583             mScriptEngine = nullptr;
584         }
585 
586 		delete mProgressDialog;
587 		mProgressDialog = nullptr;
588 		mDebuggerWindow->hide();
589 		mExecutionWindow->hide();
590 		mConsoleWidget->hide();
591 
592 		emit executionStopped();
593 	}
594 
pauseExecution()595 	void Executer::pauseExecution()
596 	{
597 		pauseOrDebug(false);
598 	}
599 
debugExecution()600 	void Executer::debugExecution()
601 	{
602 		pauseOrDebug(true);
603 	}
604 
executionException(int exception,const QString & message)605 	void Executer::executionException(int exception,
606 									  const QString &message)
607 	{
608 		ActionTools::ActionInstance *actionInstance = currentActionInstance();
609 		bool standardException = (exception >= 0 && exception < ActionTools::ActionException::ExceptionCount);
610 		bool customException = false;
611 
612 		for(ActionTools::ActionException *actionException: actionInstance->definition()->exceptions())
613 		{
614 			if(actionException->id() == exception)
615 			{
616 				customException = true;
617 				break;
618 			}
619 		}
620 
621 		if(!standardException && !customException)
622 		{
623 			mConsoleWidget->addDesignErrorLine(tr("Action design error: Invalid exception emitted (%1, line %2)")
624 											   .arg(actionInstance->definition()->name())
625 											   .arg(mCurrentActionIndex+1), ActionTools::ConsoleWidget::Error);
626 			stopExecution();
627 			return;
628 		}
629 
630 		ActionTools::ActionException::ExceptionActionInstance exceptionActionInstance = actionInstance->exceptionActionInstance(static_cast<ActionTools::ActionException::Exception>(exception));
631 		ActionTools::ConsoleWidget::Type exceptionType;
632 		bool shouldStopExecution;
633 		switch(exceptionActionInstance.action())
634 		{
635 		case ActionTools::ActionException::SkipExceptionAction:
636 			exceptionType = ActionTools::ConsoleWidget::Information;
637 			actionExecutionEnded();
638 
639 			shouldStopExecution = false;
640 			break;
641 		case ActionTools::ActionException::GotoLineExceptionAction:
642 			{
643 				exceptionType = ActionTools::ConsoleWidget::Information;
644 
645 				if(canExecuteAction(exceptionActionInstance.line()) != CanExecute)
646 				{
647 					ActionTools::ActionInstance *currentAction = mScript->actionAt(mCurrentActionIndex);
648 					qint64 currentActionRuntimeId = -1;
649 					if(currentAction)
650 						currentActionRuntimeId = currentAction->runtimeId();
651 
652 					mConsoleWidget->addExceptionLine(tr("Invalid exception line: %1").arg(exceptionActionInstance.line()),
653 													 currentActionRuntimeId,
654 													 exception,
655 													 ActionTools::ConsoleWidget::Error);
656 					shouldStopExecution = true;
657 				}
658 				else
659 				{
660 					QScriptValue script = mScriptEngine->globalObject().property(QStringLiteral("Script"));
661 					script.setProperty(QStringLiteral("nextLine"), mScriptEngine->newVariant(QVariant(exceptionActionInstance.line())));
662 					actionExecutionEnded();
663 					shouldStopExecution = false;
664 				}
665 			}
666 			break;
667 		default:
668 			exceptionType = ActionTools::ConsoleWidget::Error;
669 
670 			shouldStopExecution = true;
671 		}
672 
673 		if(shouldStopExecution)
674 		{
675             QString finalMessage = tr("Script line %1: ").arg(mCurrentActionIndex+1);
676 
677 			ActionTools::ActionInstance *currentAction = mScript->actionAt(mCurrentActionIndex);
678 			qint64 currentActionRuntimeId = -1;
679 			if(currentAction)
680 				currentActionRuntimeId = currentAction->runtimeId();
681 
682 			mConsoleWidget->addActionLine(finalMessage + message,
683 										currentActionRuntimeId,
684 										mScriptEngine->globalObject().property(QStringLiteral("currentParameter")).toString(),
685 										mScriptEngine->globalObject().property(QStringLiteral("currentSubParameter")).toString(),
686 										mScriptAgent->currentLine(),
687 										mScriptAgent->currentColumn(),
688 										exceptionType);
689 
690 			stopExecution();
691 		}
692 	}
693 
actionExecutionEnded()694 	void Executer::actionExecutionEnded()
695 	{
696 		mExecutionTimer.stop();
697 		currentActionInstance()->disconnect();
698 
699 		emit actionEnded(mCurrentActionIndex, mActiveActionsCount);
700 
701 		mExecutionStatus = PostPause;
702 
703 		mExecutionTimer.start();
704 		mExecutionTime.start();
705 		if(currentActionInstance()->pauseAfter() + mPauseAfter > 0)
706 		{
707 			mExecutionWindow->setProgressEnabled(true);
708 			mExecutionWindow->setProgressMinimum(0);
709 			mExecutionWindow->setProgressMaximum(currentActionInstance()->pauseAfter() + mPauseAfter);
710 			mExecutionWindow->setProgressValue(0);
711 		}
712 		else
713 			mExecutionWindow->setProgressEnabled(false);
714 
715 		mExecutionEnded = true;
716 	}
717 
disableAction(bool disable)718 	void Executer::disableAction(bool disable)
719 	{
720 		mActionEnabled[mCurrentActionIndex] = !disable;
721 	}
722 
startNextAction()723 	void Executer::startNextAction()
724 	{
725 		mExecutionEnded = false;
726 
727 		QScriptValue script = mScriptEngine->globalObject().property(QStringLiteral("Script"));
728 		QString nextLineString = script.property(QStringLiteral("nextLine")).toString();
729 		int previousLine = mCurrentActionIndex;
730 
731 		bool ok;
732 		int nextLine = nextLineString.toInt(&ok);
733 
734 		if(!ok)
735 		{
736 			nextLine = mScript->labelLine(nextLineString);
737 
738 			if(nextLine == -1)
739 			{
740 				executionException(ActionTools::ActionException::CodeErrorException, tr("Unable to find the label named \"%1\"").arg(nextLineString));
741 				return;
742 			}
743 		}
744 		else
745 			--nextLine;//Make the nextLine value 0-based instead of 1-based
746 
747 		if(nextLine < 0 || nextLine == mScript->actionCount())//End of the script
748 			mCurrentActionIndex = nextLine;
749 		else
750 		{
751 			switch(canExecuteAction(nextLine))
752 			{
753 			case IncorrectLine:
754 				executionException(ActionTools::ActionException::CodeErrorException, tr("Incorrect Script.nextLine value: %1").arg(nextLineString));
755 				return;
756 			case InvalidAction:
757 				executionException(ActionTools::ActionException::CodeErrorException, tr("The action at line %1 is invalid").arg(nextLineString));
758 				return;
759 			case DisabledAction:
760 			case UnselectedAction:
761 			case CanExecute:
762 				mCurrentActionIndex = nextLine;
763 				break;
764 			}
765 		}
766 
767 		bool doNotResetPreviousActions = script.property(QStringLiteral("doNotResetPreviousActions")).toBool();
768 
769         if(doNotResetPreviousActions)
770         {
771 			script.setProperty(QStringLiteral("doNotResetPreviousActions"), false);
772         }
773         else if(mCurrentActionIndex >= 0)
774         {
775 			for(int actionIndex = mCurrentActionIndex; actionIndex < previousLine; ++actionIndex)
776 				mScript->actionAt(actionIndex)->reset();
777         }
778 
779 		executeCurrentAction();
780 	}
781 
startActionExecution()782 	void Executer::startActionExecution()
783 	{
784 		mExecutionStatus = Executing;
785 
786 		mExecutionEnded = false;
787 
788 		int actionTimeout = currentActionInstance()->timeout();
789 		if(actionTimeout > 0)
790 		{
791 			mExecutionTimer.start();
792 			mExecutionTime.start();
793 			mExecutionWindow->setProgressEnabled(true);
794 			mExecutionWindow->setProgressMinimum(0);
795 			mExecutionWindow->setProgressMaximum(actionTimeout);
796 			mExecutionWindow->setProgressValue(0);
797 		}
798 		else
799 			mExecutionWindow->setProgressEnabled(false);
800 
801 		emit actionStarted(mCurrentActionIndex, mActiveActionsCount);
802 
803         currentActionInstance()->doStartExecution();
804 	}
805 
updateTimerProgress()806 	void Executer::updateTimerProgress()
807 	{
808 		if(mExecutionPaused)
809 			return;
810 
811 		ActionTools::ActionInstance *actionInstance = currentActionInstance();
812 		switch(mExecutionStatus)
813 		{
814 		case PrePause:
815 			if(mExecutionTime.elapsed() >= actionInstance->pauseBefore() + mPauseBefore)
816 			{
817 				mExecutionTimer.stop();
818 				startActionExecution();
819 			}
820 			mExecutionWindow->setProgressValue(mExecutionTime.elapsed());
821 			break;
822 		case Executing://Timeout
823 			if(mExecutionTime.elapsed() >= actionInstance->timeout())
824 			{
825 				mExecutionTimer.stop();
826 				actionInstance->disconnect();
827                 actionInstance->doStopExecution();
828 
829 				executionException(ActionTools::ActionException::TimeoutException, QString());
830 			}
831 			mExecutionWindow->setProgressValue(mExecutionTime.elapsed());
832 			break;
833 		case PostPause:
834 			if(mExecutionTime.elapsed() >= actionInstance->pauseAfter() + mPauseAfter)
835 			{
836 				mExecutionTimer.stop();
837 				startNextAction();
838 			}
839 			mExecutionWindow->setProgressValue(mExecutionTime.elapsed());
840 			break;
841 		default:
842 			Q_ASSERT(false && "updateTimerProgress() called, but execution is stopped");
843 			break;
844 		}
845 	}
846 
showProgressDialog(const QString & title,int maximum)847 	void Executer::showProgressDialog(const QString &title, int maximum)
848 	{
849 		if(!mProgressDialog)
850 			mProgressDialog = new QProgressDialog(nullptr, Qt::WindowStaysOnTopHint);
851 
852         connect(mProgressDialog, &QProgressDialog::canceled, this, &Executer::stopExecution);
853 
854 		mProgressDialog->setWindowTitle(title);
855 		mProgressDialog->setMaximum(maximum);
856 		mProgressDialog->setValue(0);
857 
858 		mProgressDialog->show();
859 	}
860 
updateProgressDialog(const QString & caption)861 	void Executer::updateProgressDialog(const QString &caption)
862 	{
863 		mProgressDialog->setLabelText(caption);
864 	}
865 
updateProgressDialog(int value)866 	void Executer::updateProgressDialog(int value)
867 	{
868 		mProgressDialog->setValue(value);
869 	}
870 
hideProgressDialog()871 	void Executer::hideProgressDialog()
872 	{
873 		delete mProgressDialog;
874 		mProgressDialog = nullptr;
875 	}
876 
executionPaused()877 	void Executer::executionPaused()
878 	{
879 		mExecutionPaused = true;
880 
881         if(!mPauseInterrupt)
882         {
883             if(mShowDebuggerOnCodeError)
884                 mDebuggerWindow->show();
885             else
886                 mScriptEngineDebugger.action(QScriptEngineDebugger::ContinueAction)->trigger();
887         }
888 		else
889 			mPauseInterrupt = false;
890 	}
891 
executionResumed()892 	void Executer::executionResumed()
893 	{
894 		mExecutionPaused = false;
895 	}
896 
consolePrint(const QString & text)897 	void Executer::consolePrint(const QString &text)
898 	{
899 		consolePrint(text, ActionTools::ConsoleWidget::Information);
900 	}
901 
consolePrintWarning(const QString & text)902 	void Executer::consolePrintWarning(const QString &text)
903 	{
904 		consolePrint(text, ActionTools::ConsoleWidget::Warning);
905 	}
906 
consolePrintError(const QString & text)907 	void Executer::consolePrintError(const QString &text)
908 	{
909 		consolePrint(text, ActionTools::ConsoleWidget::Error);
910 	}
911 
canExecuteAction(const QString & line) const912 	Executer::ExecuteActionResult Executer::canExecuteAction(const QString &line) const
913 	{
914 		bool ok;
915 		int nextLine = line.toInt(&ok);
916 
917 		if(!ok)
918 			nextLine = mScript->labelLine(line);
919 		else
920 			--nextLine;
921 
922 		return canExecuteAction(nextLine);
923 	}
924 
consolePrint(const QString & text,ActionTools::ConsoleWidget::Type type)925 	void Executer::consolePrint(const QString &text, ActionTools::ConsoleWidget::Type type)
926 	{
927 		ActionTools::ActionInstance *currentAction = mScript->actionAt(currentActionIndex());
928 		qint64 currentActionRuntimeId = -1;
929 		if(currentAction)
930 			currentActionRuntimeId = currentAction->runtimeId();
931 
932 		consoleWidget()->addUserLine(text,
933 									   currentActionRuntimeId,
934 									   mScriptEngine->globalObject().property(QStringLiteral("currentParameter")).toString(),
935 									   mScriptEngine->globalObject().property(QStringLiteral("currentSubParameter")).toString(),
936 									   mScriptAgent->currentLine(),
937 									   mScriptAgent->currentColumn(),
938 									   mScriptEngine->currentContext()->backtrace(),
939 									   type);
940 	}
941 
pauseOrDebug(bool debug)942 	void Executer::pauseOrDebug(bool debug)
943 	{
944 		if(mExecutionStatus == Stopped)
945 			return;
946 
947 		mExecutionPaused = !mExecutionPaused;
948 
949 		mPauseInterrupt = !debug;
950 
951 		if(mScriptEngine->isEvaluating())
952 		{
953 			if(mExecutionPaused)
954 			{
955 				mScriptEngineDebugger.action(QScriptEngineDebugger::InterruptAction)->trigger();
956 
957 				if(debug)
958 					mDebuggerWindow->show();
959 			}
960 			else
961 			{
962 				mScriptEngineDebugger.action(QScriptEngineDebugger::ContinueAction)->trigger();
963 
964 				if(debug)
965 					mDebuggerWindow->hide();
966 			}
967 
968 			mScriptAgent->pause(mExecutionPaused);
969 		}
970 		else
971 		{
972 			ActionTools::ActionInstance *currentAction = currentActionInstance();
973 			if(currentAction)
974 			{
975 				if(mExecutionPaused)
976                     currentAction->doPauseExecution();
977 				else
978                     currentAction->doResumeExecution();
979 			}
980 		}
981 
982 		mExecutionWindow->setPauseStatus(mExecutionPaused);
983 	}
984 
canExecuteAction(int index) const985 	Executer::ExecuteActionResult Executer::canExecuteAction(int index) const
986 	{
987 		if(index < 0 || index >= mScript->actionCount())
988 			return IncorrectLine;
989 
990 		ActionTools::ActionInstance *actionInstance = mScript->actionAt(index);
991 		if(!actionInstance)
992 			return InvalidAction;
993 
994 		if(!mActionEnabled[index] || !actionInstance->isEnabled())
995 			return DisabledAction;
996 
997 		if(mExecuteOnlySelection && !actionInstance->isSelected())
998 			return UnselectedAction;
999 
1000 		return CanExecute;
1001 	}
1002 
executeCurrentAction()1003 	void Executer::executeCurrentAction()
1004 	{
1005 		//Skip disabled actions
1006 		if(mCurrentActionIndex >= 0)
1007 		{
1008 			while(mCurrentActionIndex < mScript->actionCount() && canExecuteAction(mCurrentActionIndex) != CanExecute)
1009 				++mCurrentActionIndex;
1010 		}
1011 
1012 		if(mCurrentActionIndex < 0 || mCurrentActionIndex >= mScript->actionCount())
1013 		{
1014 			stopExecution();
1015 			return;
1016 		}
1017 
1018         int nextLine = mCurrentActionIndex + 2;
1019 		if(nextLine > mScript->actionCount())
1020 			nextLine = -1;
1021 
1022 		QScriptValue script = mScriptEngine->globalObject().property(QStringLiteral("Script"));
1023 		script.setProperty(QStringLiteral("nextLine"), mScriptEngine->newVariant(QVariant(nextLine)));
1024 		script.setProperty(QStringLiteral("line"), mCurrentActionIndex + 1, QScriptValue::ReadOnly);
1025 
1026 		ActionTools::ActionInstance *actionInstance = currentActionInstance();
1027 
1028         const ActionTools::ExceptionActionInstancesHash &exceptionActionInstancesHash = actionInstance->exceptionActionInstances();
1029         const ActionTools::ActionException::ExceptionActionInstance &exceptionAction = exceptionActionInstancesHash.value(ActionTools::ActionException::CodeErrorException);
1030         mShowDebuggerOnCodeError = (exceptionAction.action() == ActionTools::ActionException::StopExecutionExceptionAction);
1031 
1032 		mExecutionWindow->setCurrentActionName(actionInstance->definition()->name());
1033 		mExecutionWindow->setCurrentActionColor(actionInstance->color());
1034 
1035         connect(actionInstance, &ActionTools::ActionInstance::executionEndedSignal, this, &Executer::actionExecutionEnded);
1036 		connect(actionInstance, &ActionTools::ActionInstance::executionException, this, &Executer::executionException);
1037 		connect(actionInstance, &ActionTools::ActionInstance::disableAction, this, &Executer::disableAction);
1038 		connect(actionInstance, &ActionTools::ActionInstance::showProgressDialog, this, &Executer::showProgressDialog);
1039         connect(actionInstance, static_cast<void (ActionTools::ActionInstance::*)(int)>(&ActionTools::ActionInstance::updateProgressDialog),
1040                 this, static_cast<void (Executer::*)(int)>(&Executer::updateProgressDialog));
1041         connect(actionInstance, static_cast<void (ActionTools::ActionInstance::*)(const QString &)>(&ActionTools::ActionInstance::updateProgressDialog),
1042                 this, static_cast<void (Executer::*)(const QString &)>(&Executer::updateProgressDialog));
1043 		connect(actionInstance, &ActionTools::ActionInstance::hideProgressDialog, this, &Executer::hideProgressDialog);
1044         connect(actionInstance, &ActionTools::ActionInstance::consolePrint, this, static_cast<void (Executer::*)(const QString &)>(&Executer::consolePrint));
1045 		connect(actionInstance, &ActionTools::ActionInstance::consolePrintWarning, this, &Executer::consolePrintWarning);
1046 		connect(actionInstance, &ActionTools::ActionInstance::consolePrintError, this, &Executer::consolePrintError);
1047 
1048 		mExecutionStatus = PrePause;
1049 
1050 		mExecutionTimer.start();
1051 		mExecutionTime.start();
1052 		if(currentActionInstance()->pauseBefore() + mPauseBefore > 0)
1053 		{
1054 			mExecutionWindow->setProgressEnabled(true);
1055 			mExecutionWindow->setProgressMinimum(0);
1056 			mExecutionWindow->setProgressMaximum(currentActionInstance()->pauseBefore() + mPauseBefore);
1057 			mExecutionWindow->setProgressValue(0);
1058 		}
1059 		else
1060 			mExecutionWindow->setProgressEnabled(false);
1061 
1062 		mExecutionEnded = true;
1063 	}
1064 }
1065