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 (&current, 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