1 /***************************************************************************
2 rkcomponent - description
3 -------------------
4 begin : Tue Dec 13 2005
5 copyright : (C) 2005, 2006, 2009, 2010, 2011, 2012, 2013, 2014 by Thomas Friedrichsmeier
6 email : thomas.friedrichsmeier@kdemail.net
7 ***************************************************************************/
8
9 /***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
18 #include "rkcomponent.h"
19
20 #include "rkstandardcomponent.h"
21 #include "../misc/rkcommonfunctions.h"
22
23 #include "../debug.h"
24
25 //############### RKComponentBase #####################
26
lookupComponent(const QString & identifier,QString * remainder)27 RKComponentBase* RKComponentBase::lookupComponent (const QString &identifier, QString *remainder) {
28 RK_TRACE (PLUGIN);
29 RK_ASSERT (remainder);
30
31 if (identifier.isEmpty ()) return this;
32 RK_DEBUG (PLUGIN, DL_DEBUG, "looking up '%s'", identifier.toLatin1 ().data ());
33
34 RKComponentBase *child = child_map.value (identifier.section ('.', 0, 0));
35 if (!child) { // if we do not have such a child, return this (and set remainder)
36 *remainder = identifier;
37 return this;
38 } else { // else do recursive lookup
39 return child->lookupComponent (identifier.section ('.', 1), remainder);
40 }
41 }
42
lookupProperty(const QString & identifier,QString * remainder,bool warn)43 RKComponentPropertyBase* RKComponentBase::lookupProperty (const QString &identifier, QString *remainder, bool warn) {
44 RK_TRACE (PLUGIN);
45 QString _remainder;
46 QString* p_remainder = remainder;
47 if (!remainder) p_remainder = &_remainder;
48
49 RKComponentBase* p = lookupComponent (identifier, p_remainder);
50 if (p && p->isProperty ()) {
51 if (!remainder && !p_remainder->isEmpty ()) {
52 if (warn) RK_DEBUG (PLUGIN, DL_ERROR, "Modifier is not allowed, here, while looking up property: %s. (Remainder was %s)", qPrintable (identifier), qPrintable (*p_remainder));
53 return 0;
54 }
55 return static_cast<RKComponentPropertyBase*> (p);
56 }
57 if (warn) RK_DEBUG (PLUGIN, DL_ERROR, "No such property: %s. Remainder was %s", qPrintable (identifier), qPrintable (*p_remainder));
58 return 0;
59 }
60
addChild(const QString & id,RKComponentBase * child)61 void RKComponentBase::addChild (const QString &id, RKComponentBase *child) {
62 RK_TRACE (PLUGIN);
63
64 child_map.insertMulti (id, child); // no overwriting even on duplicate ("#noid#") ids
65 }
66
fetchPropertyValuesRecursive(PropertyValueMap * list,bool include_top_level,const QString & prefix,bool include_inactive_elements) const67 void RKComponentBase::fetchPropertyValuesRecursive (PropertyValueMap *list, bool include_top_level, const QString &prefix, bool include_inactive_elements) const {
68 RK_TRACE (PLUGIN);
69
70 for (QHash<QString, RKComponentBase*>::const_iterator it = child_map.constBegin (); it != child_map.constEnd (); ++it) {
71 if (it.key () == "#noid#") continue;
72 if (it.value ()->isInternal ()) continue;
73
74 if (it.value ()->isProperty ()) {
75 if (include_top_level) {
76 list->insert (prefix + it.key (), fetchStringValue (it.value ()));
77 }
78 } else {
79 RK_ASSERT (it.value ()->isComponent ());
80 if (static_cast<RKComponent *> (it.value ())->isInactive () && (!include_inactive_elements)) continue;
81 it.value ()->fetchPropertyValuesRecursive (list, true, prefix + it.key () + '.');
82 }
83 }
84 }
85
setPropertyValues(const PropertyValueMap * list,bool warn)86 void RKComponentBase::setPropertyValues (const PropertyValueMap *list, bool warn) {
87 RK_TRACE (PLUGIN);
88 // TODO: visibility enabledness and requiredness should be excluded, as those are not directly user settable. Perhaps even mark up all properties as user settable or not.
89
90 for (PropertyValueMap::const_iterator it = list->constBegin (); it != list->constEnd (); ++it) {
91 QString mod;
92 RKComponentBase *prop = lookupComponent (it.key (), &mod);
93 if (mod.isEmpty () && prop->isProperty ()) { // found a property
94 RKComponentPropertyBase* p = static_cast<RKComponentPropertyBase*>(prop);
95 if (p->isInternal () && warn) {
96 RK_DEBUG (PLUGIN, DL_WARNING, "Setting value for property %s, which is marked internal.", qPrintable (it.key ()));
97 }
98 p->setValue (it.value ());
99 } else {
100 if (warn) RK_DEBUG (PLUGIN, DL_WARNING, "Property %s not found while setting values. Remainder was %s.", qPrintable (it.key ()), qPrintable (mod));
101 }
102 }
103 }
104
105 //static
valueMapToString(const PropertyValueMap & map)106 QString RKComponentBase::valueMapToString (const PropertyValueMap &map) {
107 RK_TRACE (PLUGIN);
108
109 QString out;
110 for (PropertyValueMap::const_iterator it = map.constBegin (); it != map.constEnd (); ++it) {
111 if (!out.isEmpty ()) out.append ("\n");
112 out.append (RKCommonFunctions::escape (it.key () + '=' + it.value ()));
113 }
114 return out;
115 }
116
117 //static
stringListToValueMap(const QStringList & strings,PropertyValueMap * map)118 bool RKComponentBase::stringListToValueMap (const QStringList &strings, PropertyValueMap *map) {
119 RK_TRACE (PLUGIN);
120
121 for (int i = 0; i < strings.size (); ++i) {
122 QString line = RKCommonFunctions::unescape (strings[i]);
123 int sep = line.indexOf ('=');
124 if (sep < 0) return false;
125 map->insert (line.left (sep), line.mid (sep+1));
126 }
127 return true;
128 }
129
matchAgainstState(const PropertyValueMap & state)130 QStringList RKComponentBase::matchAgainstState (const PropertyValueMap &state) {
131 RK_TRACE (PLUGIN);
132 QStringList probs;
133
134 PropertyValueMap current;
135 fetchPropertyValuesRecursive (¤t, true);
136
137 for (PropertyValueMap::const_iterator it = state.constBegin (); it != state.constEnd (); ++it) {
138 QString current_value = current.value (it.key ());
139 if (current_value != it.value ()) {
140 // this is not necessarily a problem. The value may simply not be in the serialization, or in slightly different format
141 QString dummy;
142 RKComponentBase *prop = lookupComponent (it.key (), &dummy);
143 if (dummy.isEmpty () && prop) {
144 if ((prop->type () == PropertyDouble) && static_cast<RKComponentPropertyDouble*> (prop)->doubleValue () == it.value ().toDouble ()) {
145 // COMPAT: In RKWard 0.5.1, the formatting of real numbers was different. Hence we compare the numeric values, instead
146 continue;
147 } else if ((prop->type () == PropertyBool) && (it.value () == prop->value ("labeled").toString ())) {
148 // COMPAT: In RKWard 0.6.0, bool properties returned the labelled string, by default. Hence we also compare on the labelled value
149 continue;
150 } else if (fetchStringValue (prop) == it.value ()) {
151 continue;
152 } else {
153 if (current_value.isEmpty ()) current_value = fetchStringValue (prop); // TODO: Hm, what did I have in mind, here?
154 probs.append (QString ("Tried to apply 'value %1' to property %2, but got '%3', instead").arg (it.value (), it.key (), current_value));
155 }
156 } else {
157 probs.append (QString ("No such property %1 (remainder was %2)").arg (it.key (), dummy));
158 }
159 }
160 }
161
162 return probs;
163 }
164
fetchStringValue(RKComponentBase * prop,const QString & modifier)165 QString RKComponentBase::fetchStringValue (RKComponentBase* prop, const QString &modifier) {
166 // not tracing this simple helper
167 // TODO: we need a bit of special casing, here. Probably, instead, we should add new virutal functions serialize() and unserialize(QString()), which properties can re-implement, if needed.
168
169 if (prop->type () == PropertyDouble) {
170 if (modifier.isEmpty ()) return (prop->value ("formatted").toString ());
171 } else if (prop->type () == PropertyStringList) {
172 if (modifier.isEmpty ()) return (prop->value ("joined").toString ());
173 } else if (prop->type () == PropertyRObjects) {
174 return (prop->value (modifier).toStringList ().join ("\n"));
175 }
176 QVariant value = prop->value (modifier);
177 if (value.type () == QVariant::StringList) {
178 return value.toStringList ().join ("\n");
179 }
180 return (value.toString ());
181 }
182
fetchStringValue(const QString & identifier)183 QString RKComponentBase::fetchStringValue (const QString &identifier) {
184 RK_TRACE (PLUGIN);
185
186 QString mod;
187 RKComponentBase *prop = lookupComponent (identifier, &mod);
188 return fetchStringValue (prop, mod);
189 }
190
fetchValue(const QString & id,const int hint)191 QVariant RKComponentBase::fetchValue (const QString &id, const int hint) {
192 if (hint == StringValue) {
193 return (fetchStringValue (id));
194 } else if (hint == TraditionalValue) {
195 QString val = fetchStringValue (id);
196 // return "0" as numeric constant. Many plugins rely on this form PHP times.
197 if (val == "0") return (QVariant (0.0));
198 else return (QVariant (val));
199 } else {
200 QString mod;
201 RKComponentBase *prop = lookupComponent (id, &mod);
202 if (hint == UiLabelPair) {
203 QStringList ret;
204 if (prop->isComponent ()) {
205 ret = static_cast<const RKComponent*> (prop)->getUiLabelPair ();
206 if (ret.isEmpty ()) {
207 RK_DEBUG (PLUGIN, DL_WARNING, "Component id %s does not support getting ui labels.", qPrintable (id));
208 }
209 RK_ASSERT (!(ret.size () % 2));
210 } else {
211 RK_DEBUG (PLUGIN, DL_WARNING, "Getting ui labels is not supported for properties, only for components. Failed id was: %s", qPrintable (id));
212 }
213 if (ret.isEmpty ()) {
214 ret << "-" << "-";
215 }
216 return QVariant (ret);
217 }
218 QVariant val = prop->value (mod);
219 if (hint == BooleanValue) {
220 bool ok;
221 val = RKComponentPropertyBool::variantToBool (val, &ok);
222 if (!ok) RK_DEBUG (PLUGIN, DL_WARNING, "Could not convert value of %s to boolean", qPrintable (id));
223 } else {
224 if (hint == StringlistValue) {
225 if (val.type () != QVariant::StringList) RK_DEBUG (PLUGIN, DL_WARNING, "Value of %s is not a string list", qPrintable (id));
226 } else if (hint == NumericValue) {
227 if (!val.canConvert (QVariant::Double)) RK_DEBUG (PLUGIN, DL_WARNING, "Value of %s is not numeric", qPrintable (id));
228 } else {
229 RK_ASSERT (false);
230 }
231 }
232 return (val);
233 }
234 }
235
value(const QString & modifier)236 QVariant RKComponentBase::value (const QString &modifier) {
237 RK_TRACE (PLUGIN);
238
239 RK_DEBUG (PLUGIN, DL_WARNING, "Component type %d does not have a value. Remaining modifier is: '%s'", type (), modifier.toLatin1 ().data ());
240 return QVariant ();
241 }
242
recursiveStatus()243 RKComponentBase::ComponentStatus RKComponentBase::recursiveStatus () {
244 RK_TRACE (PLUGIN);
245
246 bool processing = false;
247 bool children_satisfied = true;
248 // we always need to iterate over all children, since we need to make sure to find any which are dead or processing.
249 for (QHash<QString, RKComponentBase*>::const_iterator it = child_map.constBegin (); it != child_map.constEnd (); ++it) {
250 ComponentStatus s = it.value ()->recursiveStatus ();
251 if (s == Dead) return Dead;
252 if (s == Processing) processing = true;
253 else if (s != Satisfied) children_satisfied = false;
254 }
255 if (processing) return Processing;
256 bool req = required;
257 if (isComponent () && static_cast<RKComponent*>(this)->isInactive ()) req = false;
258 if (!req) return Satisfied;
259 if (children_satisfied && isValid ()) return Satisfied;
260 if (isComponent ()) RK_DEBUG (PLUGIN, DL_DEBUG, "component not satisfied: %s", qPrintable (static_cast<RKComponent*> (this)->getIdInParent ()));
261 return Unsatisfied;
262 }
263
264 //############### RKComponent ########################
265
RKComponent(RKComponent * parent_component,QWidget * parent_widget)266 RKComponent::RKComponent (RKComponent *parent_component, QWidget *parent_widget) : QWidget (parent_widget) {
267 RK_TRACE (PLUGIN);
268
269 createDefaultProperties ();
270
271 _parent = parent_component;
272 // even if this is component has (parent_widget == 0), the component should be added as a QObject child of the parent.
273 if (_parent && (!parent_widget)) setParent (_parent);
274 }
275
createDefaultProperties()276 void RKComponent::createDefaultProperties () {
277 RK_TRACE (PLUGIN);
278
279 addChild ("enabled", enabledness_property = new RKComponentPropertyBool (this, false));
280 enabledness_property->setBoolValue (true);
281 enabledness_property->setInternal (true);
282 connect (enabledness_property, &RKComponentPropertyBase::valueChanged, this, &RKComponent::propertyValueChanged);
283 addChild ("visible", visibility_property = new RKComponentPropertyBool (this, false));
284 visibility_property->setBoolValue (true);
285 visibility_property->setInternal (true);
286 connect (visibility_property, &RKComponentPropertyBase::valueChanged, this, &RKComponent::propertyValueChanged);
287 addChild ("required", requiredness_property = new RKComponentPropertyBool (this, false));
288 requiredness_property->setBoolValue (true);
289 requiredness_property->setInternal (true);
290 connect (requiredness_property, &RKComponentPropertyBase::valueChanged, this, &RKComponent::propertyValueChanged);
291 }
292
~RKComponent()293 RKComponent::~RKComponent () {
294 RK_TRACE (PLUGIN);
295
296 // properties are QObjects, and hence deleted automatically
297 }
298
propertyValueChanged(RKComponentPropertyBase * property)299 void RKComponent::propertyValueChanged (RKComponentPropertyBase *property) {
300 RK_TRACE (PLUGIN);
301
302 // slightly more elaborate than necessary on first thought, to prevent loops
303 if (property == visibility_property) {
304 if (visibility_property->boolValue ()) {
305 if (isHidden ()) show ();
306 } else {
307 if (!isHidden ()) hide ();
308 }
309 } else if (property == enabledness_property) {
310 updateEnablednessRecursive ((!parentComponent ()) || (parentComponent ()->isEnabled ()));
311 } else if (property == requiredness_property) {
312 required = requiredness_property->boolValue ();
313 changed ();
314 }
315 }
316
updateEnablednessRecursive(bool parent_enabled)317 void RKComponent::updateEnablednessRecursive (bool parent_enabled) {
318 RK_TRACE (PLUGIN);
319
320 bool enabled = (enabledness_property->boolValue () && parent_enabled);
321 bool changed = (enabled != isEnabled ());
322
323 setEnabled (enabled);
324 /* RKComponent hierarchy does not always correspond to QWidget hierarchy (although in _most_ cases, it does. For this reason,
325 * we need to update enabledness of all child components. */
326 if (changed) {
327 for (QHash<QString, RKComponentBase*>::const_iterator it = child_map.constBegin (); it != child_map.constEnd (); ++it) {
328 if (it.value ()->isComponent()) {
329 static_cast<RKComponent*> (it.value ())->updateEnablednessRecursive (enabled);
330 }
331 }
332 }
333 }
334
isInactive()335 bool RKComponent::isInactive () {
336 if (!isEnabled ()) return true;
337 if (parentWidget () && isHidden ()) return true; // Note: Components embedded as button may be "hidden" without being inaccessible
338 if (!visibility_property->boolValue ()) return true; // Note for those, this is the appropriate check
339 return false;
340 }
341
isWizardish()342 bool RKComponent::isWizardish () {
343 RK_TRACE (PLUGIN);
344
345 return false;
346 }
347
havePage(bool)348 bool RKComponent::havePage (bool) {
349 RK_TRACE (PLUGIN);
350 RK_ASSERT (false); // should not be called as isWizardish returns false
351
352 return false;
353 }
354
movePage(bool)355 void RKComponent::movePage (bool) {
356 RK_TRACE (PLUGIN);
357 RK_ASSERT (false); // should not be called as isWizardish returns false
358 }
359
addPage()360 RKComponent *RKComponent::addPage () {
361 RK_TRACE (PLUGIN);
362 RK_ASSERT (false); // should not be called as isWizardish returns false
363
364 return (new RKComponent (this, this));
365 }
366
addComponentToCurrentPage(RKComponent *)367 void RKComponent::addComponentToCurrentPage (RKComponent *) {
368 RK_TRACE (PLUGIN);
369 RK_ASSERT (false); // should not be called as isWizardish returns false
370 }
371
setRequired(bool required)372 void RKComponent::setRequired (bool required) {
373 RK_TRACE (PLUGIN);
374
375 requirednessProperty ()->setBoolValue (required);
376 }
377
changed()378 void RKComponent::changed () {
379 RK_TRACE (PLUGIN);
380
381 if (parentComponent ()) {
382 parentComponent ()->changed ();
383 }
384
385 emit (componentChanged (this));
386 }
387
standardComponent(QString * id_adjust) const388 RKStandardComponent *RKComponent::standardComponent (QString *id_adjust) const {
389 RK_TRACE (PLUGIN);
390
391 RKComponent *p = const_cast<RKComponent*> (this);
392 while (p) {
393 if (p->type () == RKComponent::ComponentStandard) return static_cast<RKStandardComponent*> (p);
394 if (id_adjust) id_adjust->prepend (p->getIdInParent () + '.');
395 p = p->parentComponent ();
396 }
397 RK_ASSERT (false);
398 return 0;
399 }
400
topmostStandardComponent()401 RKStandardComponent* RKComponent::topmostStandardComponent () {
402 RK_TRACE (PLUGIN);
403
404 RKComponent *p = this;
405 while (p->parentComponent ()) p = p->parentComponent ();
406 if (p->type () == RKComponent::ComponentStandard) return static_cast<RKStandardComponent*> (p);
407 // NOTE: currently, *only* standard components can be topmost
408 RK_ASSERT (false);
409 return 0;
410 }
411
xmlHelper() const412 XMLHelper* RKComponent::xmlHelper () const {
413 RK_TRACE (PLUGIN);
414
415 RKStandardComponent *sc = standardComponent ();
416 return sc->getXmlHelper ();
417 }
418
removeFromParent()419 void RKComponent::removeFromParent () {
420 RK_TRACE (PLUGIN);
421
422 if (!parentComponent ()) return;
423
424 // unfortunately, several items might hvae the same key, and there seems to be no way to selectively remove the current item only.
425 // however, this function should only ever be called in cases of emergency and to prevent crashes. So we make extra sure to remove the child,
426 // even if we remove a little more than necessary along the way.
427 QString key = getIdInParent ();
428 while (parentComponent ()->child_map.remove (key)) {;}
429 _parent = 0;
430 }
431
getIdInParent() const432 QString RKComponent::getIdInParent () const {
433 RK_TRACE (PLUGIN);
434
435 if (!parentComponent ()) return QString ();
436 return (parentComponent ()->child_map.key (const_cast<RKComponent*> (this)));
437 }
438
439 // static
stripAccelerators(const QString & in)440 QString RKComponent::stripAccelerators (const QString& in) {
441 QString ret;
442 ret.reserve (in.size ());
443 for (int i = 0; i < in.size (); ++i) {
444 QChar c = in[i];
445 if (c == QLatin1Char ('&')) {
446 if (++i < in.size ()) ret.append (in[i]);
447 } else {
448 ret.append (c);
449 }
450 }
451 return ret;
452 }
453
454