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 "actioninstance.h"
22 #include "actiondefinition.h"
23 #include "actionfactory.h"
24 #include "elementdefinition.h"
25 #include "parameterdefinition.h"
26 #include "groupdefinition.h"
27 #include "script.h"
28 #include "code/point.h"
29 #include "code/color.h"
30 #include "code/image.h"
31 
32 #include <QDateTime>
33 #include <QScriptValueIterator>
34 #include <QApplication>
35 #include <QDesktopWidget>
36 
37 namespace ActionTools
38 {
operator ==(const ActionInstanceData & other) const39 	bool ActionInstanceData::operator==(const ActionInstanceData &other) const
40 	{
41 		return (parametersData == other.parametersData &&
42 				definition == other.definition &&
43 				comment == other.comment &&
44 				label == other.label &&
45 				color == other.color &&
46 				enabled == other.enabled &&
47 				selected == other.selected &&
48 				exceptionActionInstances == other.exceptionActionInstances &&
49 				pauseBefore == other.pauseBefore &&
50 				pauseAfter == other.pauseAfter &&
51                 timeout == other.timeout);
52 	}
53 
54 
55 	const QRegExp ActionInstance::NumericalIndex(QStringLiteral("(\\d+)"));
56 	const QRegExp ActionInstance::NameRegExp(QStringLiteral("^[A-Za-z_][A-Za-z0-9_]*$"), Qt::CaseSensitive, QRegExp::RegExp2);
57 	const QRegExp ActionInstance::VariableRegExp(QStringLiteral("\\$([A-Za-z_][A-Za-z0-9_]*)"), Qt::CaseSensitive, QRegExp::RegExp2);
58 	qint64 ActionInstance::mCurrentRuntimeId = 0;
59 
ActionInstance(const ActionDefinition * definition,QObject * parent)60 	ActionInstance::ActionInstance(const ActionDefinition *definition, QObject *parent)
61 		: QObject(parent),
62 		  mRuntimeId(mCurrentRuntimeId),
63 		  d(new ActionInstanceData())
64     {
65 		d->definition = definition;
66 		++mCurrentRuntimeId;
67 
68 		//Set the default values
69 		if(definition)
70 		{
71             for(ElementDefinition *element: definition->elements())
72 				element->setDefaultValues(this);
73 
74 			//Set the default exception action
75 			for(int i = 0; i < ActionTools::ActionException::ExceptionCount; ++i)
76 			{
77 				setExceptionActionInstance(static_cast<ActionTools::ActionException::Exception>(i),
78 										   ActionTools::ActionException::ExceptionActionInstance(ActionTools::ActionException::ExceptionDefaultAction[i], QString()));
79 			}
80 		}
81 	}
82 
ActionInstance(const ActionInstance & other)83 	ActionInstance::ActionInstance(const ActionInstance &other)
84 		: QObject(),
85 		  mRuntimeId(mCurrentRuntimeId),
86 		  d(other.d)
87 	{
88 		++mCurrentRuntimeId;
89 	}
90 
operator ==(const ActionInstance & other) const91     bool ActionInstance::operator==(const ActionInstance &other) const
92     {
93         return ((*d) == (*other.d));
94     }
95 
operator !=(const ActionInstance & other) const96     bool ActionInstance::operator!=(const ActionInstance &other) const
97     {
98         return !((*d) == (*other.d));
99     }
100 
callProcedure(const QString & procedureName)101     bool ActionInstance::callProcedure(const QString &procedureName)
102     {
103         //Search for the corresponding ActionBeginProcedure action
104         int beginProcedureLine = script()->findProcedure(procedureName);
105         if(beginProcedureLine == -1)
106         {
107             emit executionException(ActionTools::ActionException::InvalidParameterException, tr("Unable to find any procedure named \"%1\"").arg(procedureName));
108 
109             return false;
110         }
111 
112         setNextLine(beginProcedureLine + 2);//Lines start at 1
113 
114         script()->addProcedureCall(scriptLine());
115 
116         return true;
117     }
118 
doStartExecution()119     void ActionInstance::doStartExecution()
120     {
121         ++d->executionCounter;
122 
123         d->executionTimer.start();
124 
125         startExecution();
126     }
127 
doStopExecution()128     void ActionInstance::doStopExecution()
129     {
130         stopExecution();
131     }
132 
doPauseExecution()133     void ActionInstance::doPauseExecution()
134     {
135         pauseExecution();
136 
137         d->executionDuration += d->executionTimer.elapsed();
138     }
139 
doResumeExecution()140     void ActionInstance::doResumeExecution()
141     {
142         d->executionTimer.start();
143 
144         resumeExecution();
145     }
146 
copyActionDataFrom(const ActionInstance & other)147     void ActionInstance::copyActionDataFrom(const ActionInstance &other)
148     {
149         setComment(other.comment());
150         setLabel(other.label());
151         setParametersData(other.parametersData());
152 		setColor(other.color());
153 		setEnabled(other.isEnabled());
154 		setSelected(other.isSelected());
155 		setExceptionActionInstances(other.exceptionActionInstances());
156 		setPauseBefore(other.pauseBefore());
157 		setPauseAfter(other.pauseAfter());
158         setTimeout(other.timeout());
159     }
160 
convertToVariableName(const QString & input)161     QString ActionInstance::convertToVariableName(const QString &input)
162     {
163         QString back = input;
164 
165         for(int i = 0; i < back.size(); ++i)
166         {
167 			if(back[i] >= QLatin1Char('a') && back[i] <= QLatin1Char('z'))
168                 continue;
169 			if(back[i] >= QLatin1Char('A') && back[i] <= QLatin1Char('Z'))
170                 continue;
171 			if(i > 0 && back[i] >= QLatin1Char('0') && back[i] <= QLatin1Char('9'))
172                 continue;
173 
174 			back[i] = QLatin1Char('_');
175         }
176 
177         return back;
178     }
179 
findVariables(const QString & input,bool code)180     QSet<QString> ActionInstance::findVariables(const QString &input, bool code)
181     {
182         QSet<QString> back;
183 
184         if(code)
185         {
186 			for(const QString &codeLine: input.split(QRegExp(QStringLiteral("[\n\r;]")), QString::SkipEmptyParts))
187             {
188                 int position = 0;
189 
190                 while((position = Script::CodeVariableDeclarationRegExp.indexIn(codeLine, position)) != -1)
191                 {
192                     QString foundVariableName = Script::CodeVariableDeclarationRegExp.cap(1);
193 
194                     position += Script::CodeVariableDeclarationRegExp.cap(1).length();
195 
196                     if(!foundVariableName.isEmpty())
197                         back << foundVariableName;
198                 }
199             }
200         }
201         else
202         {
203             int position = 0;
204 
205             while((position = ActionInstance::VariableRegExp.indexIn(input, position)) != -1)
206             {
207                 QString foundVariableName = ActionInstance::VariableRegExp.cap(1);
208 
209                 position += ActionInstance::VariableRegExp.cap(0).length();
210 
211                 if(!foundVariableName.isEmpty())
212                     back << foundVariableName;
213             }
214         }
215 
216         return back;
217     }
218 
evaluateValue(bool & ok,const QString & parameterName,const QString & subParameterName)219     QScriptValue ActionInstance::evaluateValue(bool &ok,
220                                                const QString &parameterName,
221                                                const QString &subParameterName)
222     {
223         if(!ok)
224             return QScriptValue();
225 
226         const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
227         QScriptValue result;
228 
229         if(subParameter.isCode())
230             result = evaluateCode(ok, subParameter);
231         else
232             result = evaluateText(ok, subParameter);
233 
234         if(!ok)
235             return QScriptValue();
236 
237         return result;
238     }
239 
evaluateString(bool & ok,const QString & parameterName,const QString & subParameterName)240 	QString ActionInstance::evaluateString(bool &ok,
241 										const QString &parameterName,
242 										const QString &subParameterName)
243 	{
244 		if(!ok)
245 			return QString();
246 
247 		const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
248 		QString result;
249 
250 		if(subParameter.isCode())
251             result = evaluateCode(ok, subParameter).toString();
252 		else
253 			result = evaluateText(ok, subParameter);
254 
255 		if(!ok)
256 			return QString();
257 
258         return result;
259     }
260 
evaluateImage(bool & ok,const QString & parameterName,const QString & subParameterName)261     QImage ActionInstance::evaluateImage(bool &ok, const QString &parameterName, const QString &subParameterName)
262     {
263         if(!ok)
264             return QImage();
265 
266         const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
267         QString filename;
268 
269         if(subParameter.isCode())
270         {
271             QScriptValue evaluationResult = evaluateCode(ok, subParameter);
272             if(auto codeImage = qobject_cast<Code::Image*>(evaluationResult.toQObject()))
273                 return codeImage->image();
274 
275             if(!evaluationResult.isString())
276             {
277                 ok = false;
278 
279                 emit executionException(ActionException::InvalidParameterException, tr("Invalid image."));
280 
281                 return QImage();
282             }
283 
284             filename = evaluationResult.toString();
285         }
286         else
287             filename = evaluateText(ok, subParameter);
288 
289         if(!ok || filename.isEmpty())
290             return QImage();
291 
292         QImage image(filename);
293 
294         if(!image.isNull())
295             return image;
296 
297         ok = false;
298 
299         emit executionException(ActionException::InvalidParameterException, tr("Unable to load image: %1").arg(filename));
300 
301         return QImage();
302     }
303 
evaluateVariable(bool & ok,const QString & parameterName,const QString & subParameterName)304 	QString ActionInstance::evaluateVariable(bool &ok,
305 										const QString &parameterName,
306 										const QString &subParameterName)
307 	{
308 		QString result = evaluateString(ok, parameterName, subParameterName);
309 
310 		if(!ok)
311 			return QString();
312 
313         if(!result.isEmpty() && !NameRegExp.exactMatch(result))
314 		{
315 			ok = false;
316 
317             emit executionException(ActionException::InvalidParameterException, tr("A variable name can only contain alphanumeric characters and cannot start with a digit."));
318 
319 			return QString();
320 		}
321 
322 		return result;
323 	}
324 
evaluateVariableArray(bool & ok,const QScriptValue & scriptValue)325 	QString ActionInstance::evaluateVariableArray(bool &ok, const QScriptValue &scriptValue)
326 	{
327 		QString result;
328 
329 		QScriptValueIterator it(scriptValue);
330 
331 		if(scriptValue.isArray())
332 		{
333 			int lastIndex = -1;
334 			result = QStringLiteral("[");
335 
336 			while (it.hasNext()) {
337 				it.next();
338 
339 				if (it.flags() & QScriptValue::SkipInEnumeration)
340 					continue;
341 
342 				QScriptValue nextScriptValue = it.value();
343 				//is it an array ?
344 				if(nextScriptValue.isArray())
345 					result += evaluateVariableArray(ok, nextScriptValue);
346 				else
347                     if(NumericalIndex.exactMatch(it.name())) //it.name : numerical only ?
348 					{
349 						int newIndex = it.name().toInt();
350 						if( newIndex > lastIndex+1)
351 						{
352 							//insert some commas
353 							for(lastIndex++ ; lastIndex < newIndex; lastIndex++ )
354 								result += QStringLiteral(",");
355 						}
356 						lastIndex = newIndex;
357 						result += it.value().toString();
358 					}
359 					else
360 						result += it.name().append(QStringLiteral("=")).append(it.value().toString());
361 
362 				result += QStringLiteral(",");
363 			}
364 
365 			if(result == QLatin1String("["))
366 				result += QStringLiteral("]");
367 			else
368 				result[result.lastIndexOf(QStringLiteral(","))] = QLatin1Char(']');
369 		}
370 		else
371 			result = it.value().toString();
372 
373 		return result;
374 	}
375 
evaluateInteger(bool & ok,const QString & parameterName,const QString & subParameterName)376 	int ActionInstance::evaluateInteger(bool &ok,
377 										  const QString &parameterName,
378 										  const QString &subParameterName)
379 	{
380 		QString result = evaluateString(ok, parameterName, subParameterName);
381 
382 		if(!ok || result.isEmpty())
383 			return 0;
384 
385 		int intResult = result.toInt(&ok);
386 
387 		if(!ok)
388 		{
389 			ok = false;
390 
391             emit executionException(ActionException::InvalidParameterException, tr("Integer value expected."));
392 
393 			return 0;
394 		}
395 
396 		return intResult;
397 	}
398 
evaluateBoolean(bool & ok,const QString & parameterName,const QString & subParameterName)399 	bool ActionInstance::evaluateBoolean(bool &ok,
400 										  const QString &parameterName,
401 										  const QString &subParameterName)
402 	{
403 		QVariant result = evaluateString(ok, parameterName, subParameterName);
404 
405 		if(!ok)
406 			return false;
407 
408 		return result.toBool();
409 	}
410 
evaluateDouble(bool & ok,const QString & parameterName,const QString & subParameterName)411 	double ActionInstance::evaluateDouble(bool &ok,
412 										const QString &parameterName,
413 										const QString &subParameterName)
414 	{
415 		QString result = evaluateString(ok, parameterName, subParameterName);
416 
417 		if(!ok || result.isEmpty())
418 			return 0.0;
419 
420 		double doubleResult = result.toDouble(&ok);
421 
422 		if(!ok)
423 		{
424 			ok = false;
425 
426             emit executionException(ActionException::InvalidParameterException, tr("Decimal value expected."));
427 
428 			return 0.0;
429 		}
430 
431 		return doubleResult;
432 	}
433 
evaluateIfAction(bool & ok,const QString & parameterName)434 	IfActionValue ActionInstance::evaluateIfAction(bool &ok,
435 										const QString &parameterName)
436 	{
437 		QString action = evaluateString(ok, parameterName, QStringLiteral("action"));
438 
439 		if(!ok)
440 			return IfActionValue();
441 
442 		return IfActionValue(action, subParameter(parameterName, QStringLiteral("line")));
443 	}
444 
evaluateSubParameter(bool & ok,const SubParameter & subParameter)445 	QString ActionInstance::evaluateSubParameter(bool &ok,
446 							  const SubParameter &subParameter)
447 	{
448         if(!ok || subParameter.value().isEmpty())
449 			return QString();
450 
451 		QString result;
452 
453 		if(subParameter.isCode())
454 			result = evaluateCode(ok, subParameter).toString();
455 		else
456 			result = evaluateText(ok, subParameter);
457 
458 		if(!ok)
459 			return QString();
460 
461 		return result;
462     }
463 
evaluateEditableListElement(bool & ok,const Tools::StringListPair & listElements,const QString & parameterName,const QString & subParameterName)464     QString ActionInstance::evaluateEditableListElement(bool &ok, const Tools::StringListPair &listElements, const QString &parameterName, const QString &subParameterName)
465     {
466         if(!ok)
467             return QString();
468 
469         const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
470         QString result;
471 
472         if(subParameter.isCode())
473             result = evaluateCode(ok, subParameter).toString();
474         else
475             result = evaluateText(ok, subParameter);
476 
477         if(!ok)
478             return QString();
479 
480         //Search in the translated items
481         for(int i=0;i<listElements.second.size();++i)
482         {
483             if(listElements.second.at(i) == result)
484                 return listElements.first.at(i);
485         }
486 
487         if(result.isEmpty())
488         {
489             ok = false;
490 
491             setCurrentParameter(parameterName, subParameterName);
492 
493             emit executionException(ActionException::InvalidParameterException, tr("Please choose a value for this field."));
494 
495             return QString();
496         }
497 
498         return result;
499     }
500 
computePercentPosition(QPointF & point,const SubParameter & unitSubParameter)501     void computePercentPosition(QPointF &point, const SubParameter &unitSubParameter)
502     {
503         if(unitSubParameter.value().toInt() == 1)//Percents
504         {
505             QRect screenGeometry = QApplication::desktop()->screenGeometry();
506 
507             point.setX((point.x() * screenGeometry.width()) / 100.0f);
508             point.setY((point.y() * screenGeometry.height()) / 100.0f);
509         }
510     }
511 
evaluatePoint(bool & ok,const QString & parameterName,const QString & subParameterName,bool * empty)512     QPoint ActionInstance::evaluatePoint(bool &ok,
513                                          const QString &parameterName,
514                                          const QString &subParameterName,
515                                          bool *empty)
516     {
517         if(empty)
518             *empty = false;
519 
520         if(!ok)
521             return QPoint();
522 
523         const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
524 		const SubParameter &unitSubParameter = retreiveSubParameter(parameterName, QStringLiteral("unit"));
525         QString result;
526 
527         if(subParameter.isCode())
528         {
529             QScriptValue evaluationResult = evaluateCode(ok, subParameter);
530             if(auto codePoint = qobject_cast<Code::Point*>(evaluationResult.toQObject()))
531             {
532                 QPointF point = QPointF(codePoint->point().x(), codePoint->point().y());
533 
534                 computePercentPosition(point, unitSubParameter);
535 
536                 return QPoint(point.x(), point.y());
537             }
538 
539             result = evaluationResult.toString();
540         }
541         else
542             result = evaluateText(ok, subParameter);
543 
544         if(!ok)
545             return QPoint();
546 
547 		if(result.isEmpty() || result == QLatin1String(":"))
548         {
549             if(empty)
550                 *empty = true;
551 
552             return QPoint();
553         }
554 
555 		QStringList positionStringList = result.split(QStringLiteral(":"));
556         if(positionStringList.count() != 2)
557         {
558             ok = false;
559 
560             emit executionException(ActionException::InvalidParameterException, tr("\"%1\" is not a valid position.").arg(result));
561 
562             return QPoint();
563         }
564 
565         QPointF point(positionStringList.at(0).toFloat(&ok), positionStringList.at(1).toFloat(&ok));
566         if(!ok)
567         {
568             emit executionException(ActionException::InvalidParameterException, tr("\"%1\" is not a valid position.").arg(result));
569 
570             return QPoint();
571 		}
572 
573         computePercentPosition(point, unitSubParameter);
574 
575         return {static_cast<int>(point.x()), static_cast<int>(point.y())};
576 	}
577 
evaluateItemList(bool & ok,const QString & parameterName,const QString & subParameterName)578 	QStringList ActionInstance::evaluateItemList(bool &ok, const QString &parameterName, const QString &subParameterName)
579 	{
580 		if(!ok)
581 			return QStringList();
582 
583 		const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
584 		QString result;
585 
586 		if(subParameter.isCode())
587 			result = evaluateCode(ok, subParameter).toString();
588 		else
589 			result = evaluateText(ok, subParameter);
590 
591 		if(!ok)
592 			return QStringList();
593 
594 		return result.split(QLatin1Char('\n'), QString::SkipEmptyParts);
595 	}
596 
evaluatePolygon(bool & ok,const QString & parameterName,const QString & subParameterName)597 	QPolygon ActionInstance::evaluatePolygon(bool &ok,
598 					   const QString &parameterName,
599 					   const QString &subParameterName)
600 	{
601 		if(!ok)
602 			return QPolygon();
603 
604 		const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
605 		QString result;
606 
607 		if(subParameter.isCode())
608 			result = evaluateCode(ok, subParameter).toString();
609 		else
610 			result = evaluateText(ok, subParameter);
611 
612 		if(!ok)
613 			return QPolygon();
614 
615 		if(result.isEmpty() || result == QLatin1String(";"))
616 			return QPolygon();
617 
618 		QStringList pointStrings = result.split(QLatin1Char(';'), QString::SkipEmptyParts);
619 		QPolygon polygon;
620 
621         for(const QString &pointString: pointStrings)
622 		{
623 			QStringList pointComponents = pointString.split(QLatin1Char(':'), QString::SkipEmptyParts);
624 			if(pointComponents.size() != 2)
625 				continue;
626 
627 			polygon << QPoint(pointComponents.at(0).toInt(), pointComponents.at(1).toInt());
628 		}
629 
630 		return polygon;
631 	}
632 
evaluateColor(bool & ok,const QString & parameterName,const QString & subParameterName)633 	QColor ActionInstance::evaluateColor(bool &ok,
634 					   const QString &parameterName,
635 					   const QString &subParameterName)
636 	{
637 		if(!ok)
638 			return {};
639 
640 		const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
641 		QString result;
642 
643 		if(subParameter.isCode())
644 		{
645 			QScriptValue evaluationResult = evaluateCode(ok, subParameter);
646 			if(auto codeColor = qobject_cast<Code::Color*>(evaluationResult.toQObject()))
647 				return codeColor->color();
648 
649 			result = evaluationResult.toString();
650 		}
651 		else
652 			result = evaluateText(ok, subParameter);
653 
654 		if(!ok)
655 			return QColor();
656 
657 		if(result.isEmpty() || result == QLatin1String("::"))
658 			return QColor();
659 
660 		QStringList colorStringList = result.split(QStringLiteral(":"));
661 		if(colorStringList.count() != 3)
662 		{
663 			ok = false;
664 
665             emit executionException(ActionException::InvalidParameterException, tr("\"%1\" is not a valid color.").arg(result));
666 
667 			return QColor();
668 		}
669 
670 		QColor color = QColor(colorStringList.at(0).toInt(&ok), colorStringList.at(1).toInt(&ok), colorStringList.at(2).toInt(&ok));
671 		if(!ok)
672 		{
673             emit executionException(ActionException::InvalidParameterException, tr("\"%1\" is not a valid color.").arg(result));
674 
675 			return QColor();
676 		}
677 
678         return color;
679     }
680 
evaluateDateTime(bool & ok,const QString & parameterName,const QString & subParameterName)681     QDateTime ActionInstance::evaluateDateTime(bool &ok, const QString &parameterName, const QString &subParameterName)
682     {
683         if(!ok)
684             return QDateTime();
685 
686         const SubParameter &subParameter = retreiveSubParameter(parameterName, subParameterName);
687         QString result;
688 
689         if(subParameter.isCode())
690         {
691             QScriptValue evaluationResult = evaluateCode(ok, subParameter);
692             if(evaluationResult.isDate())
693                 return evaluationResult.toDateTime();
694 
695             result = evaluationResult.toString();
696         }
697         else
698             result = evaluateText(ok, subParameter);
699 
700         if(!ok)
701             return QDateTime();
702 
703 		QDateTime dateTime = QDateTime::fromString(result, QStringLiteral("dd/MM/yyyy hh:mm:ss"));
704 
705         if(!dateTime.isValid())
706         {
707             ok = false;
708 
709             return QDateTime();
710         }
711 
712         return dateTime;
713     }
714 
validateParameterRange(bool & ok,int parameter,const QString & parameterName,const QString & parameterTranslatedName,int minimum,int maximum)715     void ActionInstance::validateParameterRange(bool &ok, int parameter, const QString &parameterName, const QString &parameterTranslatedName, int minimum, int maximum)
716     {
717         if(ok && (parameter < minimum || parameter > maximum))
718         {
719             ok = false;
720 
721             setCurrentParameter(parameterName);
722             emit executionException(ActionTools::ActionException::InvalidParameterException, tr("Invalid %1 value : %2").arg(parameterTranslatedName).arg(parameter));
723             return;
724         }
725     }
726 
nextLine() const727 	QString ActionInstance::nextLine() const
728 	{
729 		return d->scriptEngine->property("Script.nextLine").toString();
730 	}
731 
setNextLine(const QString & nextLine,bool doNotResetPreviousActions)732     void ActionInstance::setNextLine(const QString &nextLine, bool doNotResetPreviousActions)
733 	{
734 		QScriptValue scriptValue = d->scriptEngine->globalObject().property(QStringLiteral("Script"));
735 		scriptValue.setProperty(QStringLiteral("nextLine"), d->scriptEngine->newVariant(QVariant(nextLine)));
736 		scriptValue.setProperty(QStringLiteral("doNotResetPreviousActions"), doNotResetPreviousActions);
737 	}
738 
setNextLine(int nextLine,bool doNotResetPreviousActions)739     void ActionInstance::setNextLine(int nextLine, bool doNotResetPreviousActions)
740 	{
741         setNextLine(QString::number(nextLine), doNotResetPreviousActions);
742 	}
743 
setArray(const QString & name,const QStringList & stringList)744 	void ActionInstance::setArray(const QString &name, const QStringList &stringList)
745 	{
746 		if(stringList.isEmpty())
747 			return;
748 
749 		QScriptValue back = d->scriptEngine->newArray(stringList.count());
750 
751 		for(int index = 0; index < stringList.count(); ++index)
752 			back.setProperty(index, stringList.at(index));
753 
754         if(!name.isEmpty() && NameRegExp.exactMatch(name))
755 			d->scriptEngine->globalObject().setProperty(name, back);
756 	}
757 
setArrayKeyValue(const QString & name,const QHash<QString,QString> & hashKeyValue)758 	void ActionInstance::setArrayKeyValue(const QString &name, const QHash<QString, QString> &hashKeyValue)
759 	{
760 		if(hashKeyValue.isEmpty())
761 			return;
762 
763 		QScriptValue back = d->scriptEngine->newArray(hashKeyValue.count());
764 
765 		QHashIterator<QString, QString> it(hashKeyValue);
766 		while (it.hasNext())
767 		{
768 			it.next();
769 			back.setProperty(it.key(), it.value());
770 		}
771 
772 		setVariable(name, back);
773 	}
774 
setVariable(const QString & name,const QScriptValue & value)775 	void ActionInstance::setVariable(const QString &name, const QScriptValue &value)
776 	{
777         if(!name.isEmpty() && NameRegExp.exactMatch(name))
778 			d->scriptEngine->globalObject().setProperty(name, value);
779 	}
780 
variable(const QString & name)781     QScriptValue ActionInstance::variable(const QString &name)
782 	{
783         if(name.isEmpty() || !NameRegExp.exactMatch(name))
784             return QScriptValue();
785 
786         return d->scriptEngine->globalObject().property(name);
787 	}
788 
setCurrentParameter(const QString & parameterName,const QString & subParameterName)789 	void ActionInstance::setCurrentParameter(const QString &parameterName, const QString &subParameterName)
790 	{
791 		d->scriptEngine->globalObject().setProperty(QStringLiteral("currentParameter"), parameterName, QScriptValue::ReadOnly);
792         d->scriptEngine->globalObject().setProperty(QStringLiteral("currentSubParameter"), subParameterName, QScriptValue::ReadOnly);
793     }
794 
executionEnded()795     void ActionInstance::executionEnded()
796     {
797         emit executionEndedSignal();
798 
799         d->executionDuration += d->executionTimer.elapsed();
800     }
801 
retreiveSubParameter(const QString & parameterName,const QString & subParameterName)802 	SubParameter ActionInstance::retreiveSubParameter(const QString &parameterName, const QString &subParameterName)
803 	{
804 		setCurrentParameter(parameterName, subParameterName);
805 
806         SubParameter back = subParameter(parameterName, subParameterName);
807 
808         // Re-evaluate the field as code if it contains a single variable
809 		if(!back.isCode() && back.value().startsWith(QLatin1Char('$')))
810         {
811             QString stringValue = back.value();
812             QString variableName = stringValue.right(stringValue.size() - 1);
813             const QScriptValue &value = d->scriptEngine->globalObject().property(variableName);
814 
815             if(value.isValid())
816             {
817                 back.setCode(true);
818                 back.setValue(variableName);
819             }
820         }
821 
822         return back;
823 	}
824 
evaluateCode(bool & ok,const QString & toEvaluate)825     QScriptValue ActionInstance::evaluateCode(bool &ok, const QString &toEvaluate)
826 	{
827 		ok = true;
828 
829         QScriptValue result = d->scriptEngine->evaluate(toEvaluate);
830 		if(result.isError())
831 		{
832             ok = false;
833 
834             emit executionException(ActionException::CodeErrorException, result.toString());
835 			return QScriptValue();
836 		}
837 
838 		if(!result.isValid())
839 		{
840 			ok = false;
841 
842 			return QScriptValue();
843 		}
844 
845         return result;
846     }
847 
evaluateCode(bool & ok,const SubParameter & toEvaluate)848     QScriptValue ActionInstance::evaluateCode(bool &ok, const SubParameter &toEvaluate)
849     {
850         return evaluateCode(ok, toEvaluate.value());
851     }
852 
evaluateText(bool & ok,const QString & toEvaluate)853     QString ActionInstance::evaluateText(bool &ok, const QString &toEvaluate)
854 	{
855 		ok = true;
856 
857         int position = 0;
858 
859         return evaluateTextString(ok, toEvaluate, position);
860     }
861 
evaluateText(bool & ok,const SubParameter & toEvaluate)862     QString ActionInstance::evaluateText(bool &ok, const SubParameter &toEvaluate)
863     {
864         return evaluateText(ok, toEvaluate.value());
865     }
866 
evaluateTextString(bool & ok,const QString & toEvaluate,int & position)867     QString ActionInstance::evaluateTextString(bool &ok, const QString &toEvaluate, int &position)
868 	{
869 		ok = true;
870 
871 		int startIndex = position;
872 
873 		QString result;
874 
875 		while(position < toEvaluate.length())
876 		{
877 			if(toEvaluate[position] == QLatin1Char('$'))
878 			{
879 				//find a variable name
880 				if(VariableRegExp.indexIn(toEvaluate, position) != -1)
881 				{
882 					QString foundVariableName = VariableRegExp.cap(1);
883 					QScriptValue foundVariable = d->scriptEngine->globalObject().property(foundVariableName);
884 
885 					position += foundVariableName.length();
886 
887 					if(!foundVariable.isValid())
888 					{
889 						ok = false;
890 
891 						emit executionException(ActionException::InvalidParameterException, tr("Undefined variable \"%1\"").arg(foundVariableName));
892 						return QString();
893 					}
894 
895 					QString stringEvaluationResult;
896 
897 					if(foundVariable.isNull())
898 						stringEvaluationResult = QStringLiteral("[Null]");
899 					else if(foundVariable.isUndefined())
900 						stringEvaluationResult = QStringLiteral("[Undefined]");
901 					else if(foundVariable.isArray())
902 					{
903 						while((position + 1 < toEvaluate.length()) && toEvaluate[position + 1] == QLatin1Char('['))
904 						{
905 							position += 2;
906 							QString indexArray = evaluateTextString(ok, toEvaluate, position);
907 
908 							if((position < toEvaluate.length()) && toEvaluate[position] == QLatin1Char(']'))
909 							{
910 								QScriptString internalIndexArray = d->scriptEngine->toStringHandle(indexArray);
911 								bool flag = true;
912 								int numIndex = internalIndexArray.toArrayIndex(&flag);
913 
914 								if(flag) //numIndex is valid
915 									foundVariable = foundVariable.property(numIndex);
916 								else //use internalIndexArray
917 									foundVariable = foundVariable.property(internalIndexArray);
918 							}
919 							else
920 							{
921 								//syntax error
922 								ok = false;
923 
924 								emit executionException(ActionException::InvalidParameterException, tr("Invalid parameter. Unable to evaluate string"));
925 								return QString();
926 							}
927 
928 							//COMPATIBILITY: we break the while loop if foundVariable is no more of Array type
929 							if(!foundVariable.isArray())
930 								break;
931 						}
932 						//end of while, no more '['
933 						if(foundVariable.isArray())
934 							stringEvaluationResult = evaluateVariableArray(ok, foundVariable);
935 						else
936 							stringEvaluationResult = foundVariable.toString();
937 					}
938 					else if(foundVariable.isVariant())
939 					{
940 						QVariant variantEvaluationResult = foundVariable.toVariant();
941 						switch(variantEvaluationResult.type())
942 						{
943 						case QVariant::StringList:
944 							stringEvaluationResult = variantEvaluationResult.toStringList().join(QStringLiteral("\n"));
945 							break;
946 						case QVariant::ByteArray:
947 							stringEvaluationResult = QStringLiteral("[Raw data]");
948 							break;
949 						default:
950 							stringEvaluationResult = foundVariable.toString();
951 							break;
952 						}
953 					}
954 					else
955 						stringEvaluationResult = foundVariable.toString();
956 
957 					result.append(stringEvaluationResult);
958 				}
959 
960 			}
961 			else if (toEvaluate[position] == QLatin1Char(']'))
962 			{
963 				if(startIndex == 0)
964 					//in top level evaluation isolated character ']' is accepted (for compatibility reason), now prefer "\]"
965 					//i.e without matching '['
966 					result.append(toEvaluate[position]);
967 				else
968 					//on other levels, the parsing is stopped at this point
969 					return result;
970 			}
971 			else if(toEvaluate[position] == QLatin1Char('\\'))
972 			{
973 				if(startIndex == 0)
974 				{
975 					//for ascendant compatibility reason
976 					//in top level evaluation '\' is not only an escape character,
977 					//but can also be a standard character in some cases
978 					if((position + 1) < toEvaluate.length())
979 					{
980 						position++;
981 						if(toEvaluate[position] == QLatin1Char('$') || toEvaluate[position] == QLatin1Char('[') || toEvaluate[position] == QLatin1Char(']') || toEvaluate[position] == QLatin1Char('\\'))
982 							result.append(toEvaluate[position]);
983 						else
984 						{
985 							position--;
986 							result.append(toEvaluate[position]);
987 						}
988 					}
989 					else
990 						result.append(toEvaluate[position]);
991 				}
992 				else
993 				{
994 					position++;
995 					if( position < toEvaluate.length() )
996 						result.append(toEvaluate[position]);
997 				}
998 			}
999 			else
1000 				result.append(toEvaluate[position]);
1001 
1002 			position++;
1003 		}
1004 
1005 		return result;
1006 	}
1007 
operator <<(QDataStream & s,const ActionInstance & actionInstance)1008 	QDataStream &operator << (QDataStream &s, const ActionInstance &actionInstance)
1009 	{
1010 		s << actionInstance.label();
1011 		s << actionInstance.comment();
1012 		s << actionInstance.parametersData();
1013 		s << actionInstance.color();
1014 		s << actionInstance.isEnabled();
1015 		s << actionInstance.isSelected();
1016 		s << actionInstance.exceptionActionInstances();
1017 		s << actionInstance.pauseBefore();
1018 		s << actionInstance.pauseAfter();
1019 		s << actionInstance.timeout();
1020 
1021 		return s;
1022 	}
1023 
operator >>(QDataStream & s,ActionInstance & actionInstance)1024 	QDataStream &operator >> (QDataStream &s, ActionInstance &actionInstance)
1025 	{
1026 		QString label;
1027 		QString comment;
1028 		ParametersData parametersData;
1029 		QColor color;
1030 		bool enabled;
1031 		bool selected;
1032 		ExceptionActionInstancesHash exceptionActionInstances;
1033 		int pauseBefore;
1034 		int pauseAfter;
1035 		int timeout;
1036 
1037 		s >> label;
1038 		s >> comment;
1039 		s >> parametersData;
1040 		s >> color;
1041 		s >> enabled;
1042 		s >> selected;
1043 		s >> exceptionActionInstances;
1044 		s >> pauseBefore;
1045 		s >> pauseAfter;
1046 		s >> timeout;
1047 
1048 		actionInstance.setLabel(label);
1049 		actionInstance.setComment(comment);
1050 		actionInstance.setParametersData(parametersData);
1051 		actionInstance.setColor(color);
1052 		actionInstance.setEnabled(enabled);
1053 		actionInstance.setSelected(selected);
1054 		actionInstance.setExceptionActionInstances(exceptionActionInstances);
1055 		actionInstance.setPauseBefore(pauseBefore);
1056 		actionInstance.setPauseAfter(pauseAfter);
1057 		actionInstance.setTimeout(timeout);
1058 
1059 		return s;
1060 	}
1061 
operator <<(QDebug & dbg,const ActionInstance & actionInstance)1062 	QDebug &operator << (QDebug &dbg, const ActionInstance &actionInstance)
1063 	{
1064 		dbg.space() << "Id:" << actionInstance.definition()->id();
1065 		dbg.space() << "Label:" << actionInstance.label();
1066 		dbg.space() << "Comment:" << actionInstance.comment();
1067 		dbg.space() << "Color:" << actionInstance.color();
1068 		dbg.space() << "Enabled:" << actionInstance.isEnabled();
1069 		dbg.space() << "Selected:" << actionInstance.isSelected();
1070 		dbg.space() << "Exception action instances:" << actionInstance.exceptionActionInstances();
1071 		dbg.space() << "Data:" << actionInstance.parametersData();
1072 		dbg.space() << "Pause before:" << actionInstance.pauseBefore();
1073 		dbg.space() << "Pause after:" << actionInstance.pauseAfter();
1074 		dbg.space() << "Timeout:" << actionInstance.timeout();
1075 
1076 		return dbg.maybeSpace();
1077 	}
1078 
operator <<(QDebug & dbg,const ParametersData & parametersData)1079 	QDebug &operator << (QDebug &dbg, const ParametersData &parametersData)
1080 	{
1081         for(const QString &parameterName: parametersData.keys())
1082 		{
1083 			dbg.space() << parameterName << "=" << parametersData.value(parameterName);
1084 		}
1085 
1086 		return dbg.maybeSpace();
1087 	}
1088 
operator <<(QDebug & dbg,const ExceptionActionInstancesHash & exceptionActionInstancesHash)1089 	QDebug &operator << (QDebug &dbg, const ExceptionActionInstancesHash &exceptionActionInstancesHash)
1090 	{
1091         for(ActionException::Exception exception: exceptionActionInstancesHash.keys())
1092 		{
1093 			dbg.space() << exception << "=" << exceptionActionInstancesHash.value(exception);
1094 		}
1095 
1096         return dbg.maybeSpace();
1097     }
1098 }
1099