1 #pragma once
2 
3 #include <memory>
4 
5 #include <QHash>
6 #include <QString>
7 #include <QDomNode>
8 #include <QDomElement>
9 #include <QScriptEngine>
10 #include <QDir>
11 #include <QScriptEngineDebugger>
12 #include <QtDebug>
13 #include <QRegExp>
14 
15 #include "preferences/usersettings.h"
16 #include "skin/legacy/pixmapsource.h"
17 #include "util/color/color.h"
18 #include "widget/wsingletoncontainer.h"
19 #include "widget/wpixmapstore.h"
20 
21 #define SKIN_WARNING(node, context) (context).logWarning(__FILE__, __LINE__, (node))
22 
23 // A class for managing the current context/environment when processing a
24 // skin. Used hierarchically by LegacySkinParser to create new contexts and
25 // evaluate skin XML nodes while loading the skin.
26 class SkinContext {
27   public:
28     SkinContext(
29             UserSettingsPointer pConfig,
30             const QString& xmlPath);
31     SkinContext(
32             const SkinContext* parent);
33     virtual ~SkinContext();
34 
35     // Not copiable
36     SkinContext(const SkinContext&) = delete;
37     SkinContext& operator=(const SkinContext&) = delete;
38 
39     // Moveable
40     SkinContext(SkinContext&&) = default;
41     SkinContext& operator=(SkinContext&&) = default;
42 
43     // Gets a path relative to the skin path.
makeSkinPath(const QString & relativePath)44     QString makeSkinPath(const QString& relativePath) const {
45         if (relativePath.isEmpty() || relativePath.startsWith("/")
46                 || relativePath.contains(":")) {
47             // This is already an absolute path start with the root folder "/"
48             // a windows drive letter e.g. "C:" or a qt search path prefix
49             return relativePath;
50         }
51         return QString("skin:").append(relativePath);
52     }
53 
54     // Sets the base path used by getSkinPath.
setSkinBasePath(const QString & skinBasePath)55     void setSkinBasePath(const QString& skinBasePath) {
56         QStringList skinPaths(skinBasePath);
57         QDir::setSearchPaths("skin", skinPaths);
58         m_skinBasePath = skinBasePath;
59     }
60 
61     // Sets the base path used by getSkinPath.
setSkinTemplatePath(const QString & skinTemplatePath)62     void setSkinTemplatePath(const QString& skinTemplatePath) {
63         QStringList skinPaths(m_skinBasePath);
64         skinPaths.append(skinTemplatePath);
65         QDir::setSearchPaths("skin", skinPaths);
66     }
67 
68     // Variable lookup and modification methods.
69     QString variable(const QString& name) const;
variables()70     const QHash<QString, QString>& variables() const {
71         return m_variables;
72     }
73     void setVariable(const QString& name, const QString& value);
74     void setXmlPath(const QString& xmlPath);
75 
76     // Returns whether the node has a <SetVariable> node.
77     bool hasVariableUpdates(const QDomNode& node) const;
78     // Updates the SkinContext with all the <SetVariable> children of node.
79     void updateVariables(const QDomNode& node);
80     // Updates the SkinContext with 'element', a <SetVariable> node.
81     void updateVariable(const QDomElement& element);
82 
selectNode(const QDomNode & node,const QString & nodeName)83     static inline QDomNode selectNode(const QDomNode& node, const QString& nodeName) {
84         QDomNode child = node.firstChild();
85         while (!child.isNull()) {
86             if (child.nodeName() == nodeName) {
87                 return child;
88             }
89             child = child.nextSibling();
90         }
91         return QDomNode();
92     }
93 
selectElement(const QDomNode & node,const QString & nodeName)94     static inline QDomElement selectElement(const QDomNode& node, const QString& nodeName) {
95         QDomNode child = selectNode(node, nodeName);
96         return child.toElement();
97     }
98 
selectString(const QDomNode & node,const QString & nodeName)99     inline QString selectString(const QDomNode& node, const QString& nodeName) const {
100         QDomElement child = selectElement(node, nodeName);
101         return nodeToString(child);
102     }
103 
104     inline float selectFloat(const QDomNode& node, const QString& nodeName, float defaultValue = 0.0) const {
105         bool ok = false;
106         float conv = nodeToString(selectElement(node, nodeName)).toFloat(&ok);
107         return ok ? conv : defaultValue;
108     }
109 
110     inline double selectDouble(const QDomNode& node, const QString& nodeName, double defaultValue = 0.0) const {
111         bool ok = false;
112         double conv = nodeToString(selectElement(node, nodeName)).toDouble(&ok);
113         return ok ? conv : defaultValue;
114     }
115 
116     inline int selectInt(const QDomNode& node, const QString& nodeName,
117                          bool* pOk = nullptr) const {
118             bool ok = false;
119             int conv = nodeToString(selectElement(node, nodeName)).toInt(&ok);
120             if (pOk != nullptr) {
121                 *pOk = ok;
122             }
123             return ok ? conv : 0;
124     }
125 
selectColor(const QDomNode & node,const QString & nodeName)126     inline QColor selectColor(const QDomNode& node, const QString& nodeName) const {
127         QString sColorString = nodeToString(selectElement(node, nodeName));
128         return QColor(sColorString);
129     }
130 
selectBool(const QDomNode & node,const QString & nodeName,bool defaultValue)131     inline bool selectBool(const QDomNode& node, const QString& nodeName,
132                            bool defaultValue) const {
133         QDomNode child = selectNode(node, nodeName);
134         if (!child.isNull()) {
135             QString stringValue = nodeToString(child);
136             return stringValue.contains("true", Qt::CaseInsensitive);
137         }
138         return defaultValue;
139     }
140 
hasNodeSelectElement(const QDomNode & node,const QString & nodeName,QDomElement * value)141     inline bool hasNodeSelectElement(const QDomNode& node, const QString& nodeName,
142                                      QDomElement* value) const {
143         QDomElement child = selectElement(node, nodeName);
144         if (!child.isNull()) {
145             *value = child;
146             return true;
147         }
148         return false;
149     }
150 
hasNodeSelectString(const QDomNode & node,const QString & nodeName,QString * value)151     inline bool hasNodeSelectString(const QDomNode& node, const QString& nodeName,
152                                     QString *value) const {
153         QDomNode child = selectNode(node, nodeName);
154         if (!child.isNull()) {
155             *value = nodeToString(child);
156             return true;
157         }
158         return false;
159     }
160 
hasNodeSelectBool(const QDomNode & node,const QString & nodeName,bool * value)161     inline bool hasNodeSelectBool(const QDomNode& node, const QString& nodeName,
162                                   bool* value) const {
163         QDomNode child = selectNode(node, nodeName);
164         if (!child.isNull()) {
165             QString stringValue = nodeToString(child);
166             *value = stringValue.contains("true", Qt::CaseInsensitive);
167             return true;
168         }
169         return false;
170     }
171 
hasNodeSelectInt(const QDomNode & node,const QString & nodeName,int * value)172     inline bool hasNodeSelectInt(const QDomNode& node, const QString& nodeName,
173                                  int* value) const {
174         QDomNode child = selectNode(node, nodeName);
175         if (!child.isNull()) {
176             bool ok = false;
177             int result = nodeToString(child).toInt(&ok);
178             if (ok) {
179                 *value = result;
180                 return true;
181             }
182         }
183         return false;
184     }
185 
hasNodeSelectDouble(const QDomNode & node,const QString & nodeName,double * value)186     inline bool hasNodeSelectDouble(const QDomNode& node, const QString& nodeName,
187                                     double* value) const {
188         QDomNode child = selectNode(node, nodeName);
189         if (!child.isNull()) {
190             bool ok = false;
191             double result = nodeToString(child).toDouble(&ok);
192             if (ok) {
193                 *value = result;
194                 return true;
195             }
196         }
197         return false;
198     }
199 
selectAttributeBool(const QDomElement & element,const QString & attributeName,bool defaultValue)200     inline bool selectAttributeBool(const QDomElement& element,
201                                     const QString& attributeName,
202                                     bool defaultValue) const {
203         QString stringValue;
204         if (hasAttributeSelectString(element, attributeName, &stringValue)) {
205             return stringValue.contains("true", Qt::CaseInsensitive);
206         }
207         return defaultValue;
208     }
209 
hasAttributeSelectString(const QDomElement & element,const QString & attributeName,QString * result)210     inline bool hasAttributeSelectString(const QDomElement& element,
211                                          const QString& attributeName,
212                                          QString* result) const {
213         *result = element.attribute(attributeName);
214         return !result->isNull();
215     }
216 
217     QString nodeToString(const QDomNode& node) const;
218     PixmapSource getPixmapSource(const QDomNode& pixmapNode) const;
219     PixmapSource getPixmapSource(const QString& filename) const;
220 
selectScaleMode(const QDomElement & element,Paintable::DrawMode defaultDrawMode)221     inline Paintable::DrawMode selectScaleMode(
222             const QDomElement& element,
223             Paintable::DrawMode defaultDrawMode) const {
224         QString drawModeStr;
225         if (hasAttributeSelectString(element, "scalemode", &drawModeStr)) {
226             return Paintable::DrawModeFromString(drawModeStr);
227         }
228         return defaultDrawMode;
229     }
230 
231     QScriptValue evaluateScript(const QString& expression,
232                                 const QString& filename=QString(),
233                                 int lineNumber=1) const;
234     QScriptValue importScriptExtension(const QString& extensionName);
hasUncaughtScriptException()235     bool hasUncaughtScriptException() const {
236         return m_pSharedState->scriptEngine.hasUncaughtException();
237     }
238     void enableDebugger(bool state) const;
239 
240     QDebug logWarning(const char* file, const int line, const QDomNode& node) const;
241 
defineSingleton(const QString & objectName,QWidget * widget)242     void defineSingleton(const QString& objectName, QWidget* widget) {
243         m_pSharedState->singletons.insertSingleton(objectName, widget);
244     }
245 
getSingletonWidget(const QString & objectName)246     QWidget* getSingletonWidget(const QString& objectName) const {
247         return m_pSharedState->singletons.getSingletonWidget(objectName);
248     }
249 
getHookRegex()250     const QRegExp& getHookRegex() const {
251         return m_hookRx;
252     }
253 
254     int scaleToWidgetSize(QString& size) const;
255 
getScaleFactor()256     double getScaleFactor() const {
257         return m_scaleFactor;
258     }
259 
getConfig()260     UserSettingsPointer getConfig() const {
261         return m_pConfig;
262     }
263 
getSkinBasePath()264     QString getSkinBasePath() const {
265         return m_skinBasePath;
266     }
267 
268   private:
269     PixmapSource getPixmapSourceInner(const QString& filename) const;
270 
271     QDomElement loadSvg(const QString& filename) const;
272 
273     // If our parent global isValid() then we were constructed with a
274     // parent. Otherwise we are a root SkinContext.
isRoot()275     bool isRoot() const { return !m_parentGlobal.isValid(); }
276 
277     QString variableNodeToText(const QDomElement& element) const;
278 
279     UserSettingsPointer m_pConfig;
280 
281     QString m_xmlPath;
282     QString m_skinBasePath;
283 
284     struct SharedState final {
285         SharedState() = default;
286         SharedState(const SharedState&) = delete;
287         SharedState(SharedState&&) = delete;
288 
289         QScriptEngine scriptEngine;
290         QScriptEngineDebugger scriptDebugger;
291         QHash<QString, QDomElement> svgCache;
292         // The SingletonContainer map is passed to child SkinContexts, so that all
293         // templates in the tree can share a single map.
294         SingletonMap singletons;
295     };
296     // Use std::shared_ptr instead of QSharedPointer to guarantee
297     // correct move semantics!
298     std::shared_ptr<SharedState> m_pSharedState;
299 
300     QHash<QString, QString> m_variables;
301     QScriptValue m_parentGlobal;
302     QRegExp m_hookRx;
303 
304     double m_scaleFactor;
305 };
306