1 /***************************************************************************
2 rkoptionset - description
3 -------------------
4 begin : Mon Oct 31 2011
5 copyright : (C) 2011, 2012, 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 "rkoptionset.h"
19
20 #include <QVBoxLayout>
21 #include <QTreeWidget>
22 #include <QHeaderView>
23 #include <QPushButton>
24 #include <QStackedWidget>
25 #include <QLabel>
26 #include <QMimeData>
27
28 #include <KLocalizedString>
29
30 #include "rkstandardcomponent.h"
31 #include "../misc/rkcommonfunctions.h"
32 #include "../misc/rkaccordiontable.h"
33 #include "../misc/rkstandardicons.h"
34 #include "../misc/xmlhelper.h"
35
36 #include "../debug.h"
37
38 #define KEYCOLUMN_UNINITIALIZED_VALUE QString ("___#!RK!___Keycol_uninitialized")
39
RKOptionSet(const QDomElement & element,RKComponent * parent_component,QWidget * parent_widget)40 RKOptionSet::RKOptionSet (const QDomElement &element, RKComponent *parent_component, QWidget *parent_widget) : RKComponent (parent_component, parent_widget) {
41 RK_TRACE (PLUGIN);
42
43 XMLHelper *xml = parent_component->xmlHelper ();
44 updating = false;
45 last_known_status = Processing;
46 n_invalid_rows = n_unfinished_rows = 0;
47
48 min_rows = xml->getIntAttribute (element, "min_rows", 0, DL_INFO);
49 min_rows_if_any = xml->getIntAttribute (element, "min_rows_if_any", 1, DL_INFO);
50 max_rows = xml->getIntAttribute (element, "max_rows", INT_MAX, DL_INFO);
51
52 // build UI framework
53 QVBoxLayout *layout = new QVBoxLayout (this);
54 layout->setContentsMargins (0, 0, 0, 0);
55 switcher = new QStackedWidget (this);
56 layout->addWidget (switcher);
57 accordion = new RKAccordionTable (this);
58 switcher->addWidget (accordion);
59
60 connect (accordion, static_cast<void (RKAccordionTable::*)(int)>(&RKAccordionTable::activated), this, &RKOptionSet::currentRowChanged);
61 connect (accordion, &RKAccordionTable::addRow, this, &RKOptionSet::addRow);
62 connect (accordion, &RKAccordionTable::removeRow, this, &RKOptionSet::removeRow);
63
64 updating_notice = new QLabel (i18n ("Updating status, please wait"), this);
65 switcher->addWidget (updating_notice);
66 update_timer.setInterval (0);
67 update_timer.setSingleShot (true);
68 connect (&update_timer, &QTimer::timeout, this, &RKOptionSet::slotUpdateUnfinishedRows);
69
70 // create some meta properties
71 serialization_of_set = new RKComponentPropertyBase (this, false);
72 addChild ("serialized", serialization_of_set);
73 connect (serialization_of_set, &RKComponentPropertyBase::valueChanged, this, &RKOptionSet::serializationPropertyChanged);
74
75 row_count = new RKComponentPropertyInt (this, false, 0);
76 row_count->setInternal (true);
77 addChild ("row_count", row_count); // NOTE: read-only
78 return_to_row = active_row = -1;
79 current_row = new RKComponentPropertyInt (this, false, active_row);
80 current_row->setInternal (true);
81 addChild ("current_row", current_row); // NOTE: read-write
82 connect (current_row, &RKComponentPropertyBase::valueChanged, this, &RKOptionSet::currentRowPropertyChanged);
83
84 // first build the contents, as we will need to refer to the elements inside, later
85 model = new RKOptionSetDisplayModel (this);
86 contents_container = new RKComponent (this, accordion->editorWidget ());
87 accordion->editorWidget ()->layout ()->addWidget (contents_container);
88 QDomElement content_element = xml->getChildElement (element, "content", DL_ERROR);
89 RKComponentBuilder *builder = new RKComponentBuilder (contents_container, content_element);
90 builder->buildElement (content_element, *xml, accordion->editorWidget (), false); // NOTE that parent widget != parent component, here, by intention. The point is that the display should not be disabled along with the contents
91 builder->parseLogic (xml->getChildElement (element, "logic", DL_INFO), *xml, false);
92 builder->makeConnections ();
93 addChild ("contents", contents_container);
94 connect (standardComponent (), &RKStandardComponent::standardInitializationComplete, this, &RKOptionSet::fetchDefaults);
95
96 // create columns
97 XMLChildList options = xml->getChildElements (element, "optioncolumn", DL_WARNING);
98
99 QStringList visible_column_labels;
100 for (int i = 0; i < options.size (); ++i) {
101 const QDomElement &e = options.at (i);
102 QString id = xml->getStringAttribute (e, "id", QString (), DL_ERROR);
103 QString label = xml->i18nStringAttribute (e, "label", QString (), DL_DEBUG);
104 QString governor = xml->getStringAttribute (e, "connect", QString (), DL_INFO);
105 bool external = xml->getBoolAttribute (e, "external", false, DL_INFO);
106
107 while (child_map.contains (id)) {
108 RK_DEBUG (PLUGIN, DL_ERROR, "optionset already contains a property named %s. Renaming to _%s", qPrintable (id), qPrintable (id));
109 id = '_' + id;
110 }
111
112 ColumnInfo col_inf;
113 col_inf.column_name = id;
114 col_inf.external = external;
115 col_inf.governor = governor;
116 if (external && e.hasAttribute ("default")) col_inf.default_value = xml->getStringAttribute (e, "default", QString (), DL_ERROR);
117
118 RKComponentPropertyStringList *column_property = new RKComponentPropertyStringList (this, false);
119 column_property->setInternal (external); // Yes, looks strange, indeed. External properties should simply not be serialized / restored...
120 addChild (id, column_property);
121 connect (column_property, &RKComponentPropertyBase::valueChanged, this, &RKOptionSet::columnPropertyChanged);
122
123 if (!label.isEmpty ()) {
124 col_inf.display_index = visible_column_labels.size ();
125 col_inf.column_label = label;
126 visible_column_labels.append (label);
127 visible_columns.append (column_property);
128 } else {
129 col_inf.display_index = -1;
130 }
131
132 column_map.insert (column_property, col_inf);
133 }
134
135 keycolumn = 0;
136 QString keycol = xml->getStringAttribute (element, "keycolumn", QString (), DL_DEBUG);
137 if (!keycol.isEmpty ()) {
138 keycolumn = static_cast<RKComponentPropertyStringList*> (child_map.value (keycol));
139 if (!column_map.contains (keycolumn)) {
140 RK_DEBUG (PLUGIN, DL_ERROR, "optionset does not contain an optioncolumn named %s. Falling back to manual insertion mode", qPrintable (keycol));
141 keycolumn = 0;
142 } else if (!column_map[keycolumn].external) {
143 RK_DEBUG (PLUGIN, DL_ERROR, "keycolumn (%s) is not marked as external. Falling back to manual insertion mode", qPrintable (keycol));
144 keycolumn = 0;
145 } else {
146 updating = true;
147 keycolumn->setValue (KEYCOLUMN_UNINITIALIZED_VALUE);
148 updating = false;
149 }
150 }
151
152 QMap<RKComponentPropertyStringList *, ColumnInfo>::iterator it = column_map.begin ();
153 for (; it != column_map.end (); ++it) {
154 ColumnInfo &ci = it.value ();
155 if (!ci.governor.isEmpty ()) { // there *can* be columns without governor for driven or connected option sets
156 // Establish connections between columns and their respective governors. Since the format differs, the connection is done indirectly, through this component.
157 // So, here, we set up a map of properties to columns, and connect to the change signals.
158 RKComponentBase *governor = contents_container->lookupComponent (ci.governor, &ci.governor_modifier);
159 if (governor && governor->isProperty ()) {
160 RKComponentPropertyBase *gov_prop = static_cast<RKComponentPropertyBase*> (governor);
161 if (ci.external) {
162 if (!ci.governor_modifier.isEmpty ()) {
163 RK_DEBUG (PLUGIN, DL_ERROR, "Cannot connect external column '%s' in optionset to property with modifier (%s).", qPrintable (ci.column_name), qPrintable (ci.governor));
164 continue;
165 }
166 }
167 columns_to_update.insertMulti (gov_prop, it.key ());
168 connect (gov_prop, &RKComponentPropertyBase::valueChanged, this, &RKOptionSet::governingPropertyChanged);
169 } else {
170 RK_DEBUG (PLUGIN, DL_ERROR, "did not find governing property %s for column %s of optionset", qPrintable (ci.governor), qPrintable (ci.column_name));
171 }
172 }
173 }
174
175 model->column_labels = visible_column_labels;
176 accordion->setShowAddRemoveButtons (!keycolumn);
177 accordion->setModel (model);
178 }
179
~RKOptionSet()180 RKOptionSet::~RKOptionSet () {
181 RK_TRACE (PLUGIN);
182 }
183
fetchDefaults()184 void RKOptionSet::fetchDefaults () {
185 RK_TRACE (PLUGIN);
186 RK_ASSERT (default_row_state.isEmpty ());
187 contents_container->fetchPropertyValuesRecursive (&default_row_state, false, QString (), true);
188 if (min_rows && !keycolumn && (rowCount () <= 0)) addRow (rowCount ());
189 contents_container->enablednessProperty ()->setBoolValue (rowCount () > 0); // no current row; Do this *after* fetching default values, however. Otherwise most values will *not* be read, as the element is disabled
190 }
191
serializeList(const QStringList & list)192 QString serializeList (const QStringList &list) {
193 QString ret;
194 for (int i = 0; i < list.size (); ++i) {
195 if (i > 0) ret.append ('\t');
196 ret.append (RKCommonFunctions::escape (list[i]));
197 }
198 return ret;
199 }
200
unserializeList(const QString & serial)201 QStringList unserializeList (const QString &serial) {
202 QStringList ret = serial.split ('\t', QString::KeepEmptyParts);
203 for (int i = 0; i < ret.size (); ++i) {
204 ret[i] = RKCommonFunctions::unescape (ret[i]);
205 }
206 return ret;
207 }
208
serializeMap(const RKComponent::PropertyValueMap & map)209 QString serializeMap (const RKComponent::PropertyValueMap &map) {
210 QString ret;
211
212 RKComponent::PropertyValueMap::const_iterator it;
213 for (it = map.constBegin (); it != map.constEnd (); ++it) {
214 if (!ret.isEmpty ()) ret.append ('\t');
215 ret.append (RKCommonFunctions::escape (it.key () + '=' + it.value ()));
216 }
217 return ret;
218 }
219
unserializeMap(const QString & serial)220 RKComponent::PropertyValueMap unserializeMap (const QString &serial) {
221 RKComponent::PropertyValueMap ret;
222 QStringList l = serial.split ('\t', QString::KeepEmptyParts);
223 for (int i = 0; i < l.size (); ++i) {
224 QString &line = l[i];
225 int sep = line.indexOf ('=');
226 ret.insert (RKCommonFunctions::unescape (line.left (sep)), RKCommonFunctions::unescape (line.mid (sep+1)));
227 }
228 return ret;
229 }
230
fetchPropertyValuesRecursive(PropertyValueMap * list,bool include_top_level,const QString & prefix,bool include_inactive_elements) const231 void RKOptionSet::fetchPropertyValuesRecursive (PropertyValueMap *list, bool include_top_level, const QString &prefix, bool include_inactive_elements) const {
232 RK_TRACE (PLUGIN);
233 RK_ASSERT (include_top_level);
234 RK_ASSERT (!include_inactive_elements);
235
236 QString serialization;
237
238 if (keycolumn) {
239 serialization.append ("keys=" + serializeList (old_keys));
240 }
241
242 for (int r = 0; r < rows.size (); ++r) {
243 if (!serialization.isEmpty ()) serialization.append ("\n");
244 if (r == active_row) {
245 PropertyValueMap map; // current row may have changes which have not yet been stored to the state map
246 contents_container->fetchPropertyValuesRecursive (&map);
247 serialization.append ("_row=" + serializeMap (map));
248 } else {
249 serialization.append ("_row=" + serializeMap (rows[r].full_row_map));
250 }
251 }
252
253 list->insert (prefix + "serialized", serialization);
254 }
255
serializationPropertyChanged(RKComponentPropertyBase * property)256 void RKOptionSet::serializationPropertyChanged (RKComponentPropertyBase* property) {
257 if (updating) return;
258 updating = true;
259 if (model) model->layoutAboutToBeChanged ();
260
261 RK_TRACE (PLUGIN);
262 RK_ASSERT (property == serialization_of_set);
263 /* What happens when deserializing a plugin, with a driven optionset, and
264 * the property connected to the keycolumn is restored *before* the optionset itself has been de-serialized?
265 *
266 * We have to special-case this: If we go into setPropertyValues, and
267 * the key column has already been touched, we have to
268 * - apply property values from the serialization
269 * - trigger handleKeycolumnUpdate(), delayed
270 *
271 */
272 bool update_key_col = false;
273 if (keycolumn && (keycolumn->value () != KEYCOLUMN_UNINITIALIZED_VALUE)) {
274 update_key_col = true;
275 } else {
276 RK_ASSERT (rows.isEmpty ());
277 }
278
279 QList<RowInfo> new_rows;
280 int row = 0;
281 QStringList items = fetchStringValue (property).split ('\n');
282 bool keys_missing = (keycolumn != 0);
283 for (int i = 0; i < items.size (); ++i) {
284 const QString &item = items[i];
285 int sep = item.indexOf ('=');
286 if (sep < 0) {
287 RK_DEBUG (PLUGIN, DL_WARNING, "Bad format while trying to de-serialize optionset, line %d", i);
288 continue;
289 }
290 QString tag = item.left (sep);
291 QString value = item.mid (sep + 1);
292
293 if (tag == QLatin1String ("keys")) {
294 if (!keys_missing) {
295 RK_DEBUG (PLUGIN, DL_WARNING, "Unexpected specification of keys while trying to de-serialize optionset, line %d", i);
296 continue;
297 }
298 old_keys = unserializeList (value);
299 keys_missing = false;
300 } else if (tag == QLatin1String ("_row")) {
301 new_rows.append (RowInfo (unserializeMap (value)));
302 ++row;
303 } else {
304 RK_DEBUG (PLUGIN, DL_WARNING, "Unexpected tag %s while trying to de-serialize optionset, line %d", qPrintable (tag), i);
305 continue;
306 }
307 }
308
309 if (keycolumn) {
310 RK_ASSERT (new_rows.size () == old_keys.size ());
311 RK_ASSERT (!keys_missing);
312 }
313
314 // reset all non-external columns to default values
315 QMap<RKComponentPropertyStringList*, ColumnInfo>::const_iterator it;
316 for (it = column_map.constBegin (); it != column_map.constEnd (); ++it) {
317 const ColumnInfo &col = it.value ();
318 if (col.external) continue;
319 QStringList def;
320 for (int i = 0; i < row; ++i) {
321 def.append (getDefaultValue (col, i));
322 }
323 it.key ()->setValueList (def);
324 }
325
326 rows = new_rows;
327 n_unfinished_rows = n_invalid_rows = row;
328 row_count->setIntValue (row);
329 serialization_of_set->setValue (QString ()); // free some mem, and don't make users think, this can actually be queried in real-time
330 updating = false;
331 if (update_key_col) handleKeycolumnUpdate ();
332
333 active_row = -1;
334 current_row->setIntValue (qMin (0, row - 1));
335
336 if (model) model->layoutChanged ();
337 changed ();
338 }
339
slotUpdateUnfinishedRows()340 void RKOptionSet::slotUpdateUnfinishedRows () {
341 updateUnfinishedRows ();
342 }
343
updateUnfinishedRows()344 void RKOptionSet::updateUnfinishedRows () {
345 if (updating) return;
346 if ((active_row >= 0) && (active_row < rows.size ())) {
347 if (!rows[active_row].finished) {
348 return; // if the current row is unfinished: let's wait for this one, first.
349 }
350 }
351
352 RK_TRACE (PLUGIN);
353
354 if (!n_unfinished_rows) { // done
355 if (switcher->currentWidget () != updating_notice) return;
356 current_row->setIntValue (return_to_row);
357 switcher->setCurrentWidget (accordion);
358 return;
359 }
360
361 if (switcher->currentWidget () != updating_notice) {
362 switcher->setCurrentWidget (updating_notice);
363 return_to_row = active_row;
364 }
365 for (int i = 0; i < rows.size (); ++i) {
366 if (!rows[i].finished) {
367 current_row->setIntValue (i);
368 return;
369 }
370 }
371
372 RK_ASSERT (false); // This would mean, we did not find any unfinished row, even though we tested for n_unfinished_rows, above.
373 }
374
addRow(int row)375 void RKOptionSet::addRow (int row) {
376 RK_TRACE (PLUGIN);
377
378 storeRowSerialization (active_row);
379
380 int nrows = rowCount ();
381 if (row < 0) row = nrows;
382 RK_ASSERT (!keycolumn);
383
384 model->beginInsertRows (QModelIndex (), row, row);
385 // adjust values
386 updating = true;
387 QMap<RKComponentPropertyStringList *, ColumnInfo>::iterator it = column_map.begin ();
388 for (; it != column_map.end (); ++it) {
389 RKComponentPropertyStringList* col = it.key ();
390 ColumnInfo &column = it.value ();
391 QStringList values = col->values ();
392 values.insert (row, getDefaultValue (column, row));
393 col->setValueList (values);
394 }
395 updating = false;
396
397 // adjust status info
398 RowInfo ri (default_row_state);
399 ri.valid = false;
400 ri.finished = false;
401 rows.insert (row, ri);
402 ++n_unfinished_rows;
403 ++n_invalid_rows;
404 row_count->setIntValue (nrows + 1);
405 current_row->setIntValue (active_row = row);
406 setContentsForRow (active_row);
407 model->endInsertRows ();
408
409 current_row->setIntValue (row); // Setting this _again_, as the view might have messed with it following endInsertRows ()
410
411 changed ();
412 }
413
removeRow(int row)414 void RKOptionSet::removeRow (int row) {
415 RK_TRACE (PLUGIN);
416
417 int nrows = rowCount ();
418 if (row < 0) {
419 RK_ASSERT (false);
420 return;
421 }
422 RK_ASSERT (!keycolumn);
423
424 model->beginRemoveRows (QModelIndex (), row, row);
425 updating = true;
426 // adjust values
427 QMap<RKComponentPropertyStringList *, ColumnInfo>::iterator it = column_map.begin ();
428 for (; it != column_map.end (); ++it) {
429 RKComponentPropertyStringList* col = it.key ();
430 QStringList values = col->values ();
431 values.removeAt (row);
432 col->setValueList (values);
433 }
434 updating = false;
435
436 // adjust status info
437 if (!rows[row].valid) --n_invalid_rows;
438 if (!rows[row].finished) --n_unfinished_rows;
439 rows.removeAt (row);
440
441 --row;
442 if ((row < 0) && (nrows > 1)) row = 0;
443 row_count->setIntValue (nrows - 1);
444 current_row->setIntValue (active_row = row);
445 setContentsForRow (row);
446 model->endRemoveRows ();
447
448 current_row->setIntValue (row); // Setting this _again_, as the view might have messed with it following endRemoveRows ()
449
450 changed ();
451 }
452
moveRow(int old_index,int new_index)453 void RKOptionSet::moveRow (int old_index, int new_index) {
454 RK_TRACE (PLUGIN);
455
456 int nrows = rowCount ();
457 if (old_index < 0 || old_index >= nrows) {
458 RK_ASSERT (false);
459 return;
460 }
461
462 if (new_index < 0 || new_index > nrows) {
463 new_index = nrows;
464 }
465
466 storeRowSerialization (active_row);
467 PropertyValueMap backup = rows[old_index].full_row_map;
468 removeRow (old_index);
469 if (new_index > old_index) new_index -= 1;
470 addRow (new_index);
471 rows[new_index].full_row_map = backup;
472 setContentsForRow (new_index);
473
474 changed ();
475 }
476
getDefaultValue(const RKOptionSet::ColumnInfo & ci,int row)477 QString getDefaultValue (const RKOptionSet::ColumnInfo& ci, int row) {
478 // let's not trace this simple helper fun
479 Q_UNUSED (row);
480 // TODO: should also allow scripted defaults?
481 return ci.default_value;
482 }
483
setRowState(int row,bool finished,bool valid)484 void RKOptionSet::setRowState (int row, bool finished, bool valid) {
485 bool changed = false;
486 RK_ASSERT (row < rows.size ());
487 if (rows[row].finished != finished) {
488 rows[row].finished = finished;
489 finished ? --n_unfinished_rows : ++n_unfinished_rows;
490 changed = true;
491 }
492 if (rows[row].valid != valid) {
493 rows[row].valid = valid;
494 valid ? --n_invalid_rows : ++n_invalid_rows;
495 changed = true;
496 }
497 if (changed && model) model->dataChanged (model->index (row, 0), model->index (row, model->columnCount () - 1));
498 }
499
changed()500 void RKOptionSet::changed () {
501 int row = active_row;
502
503 if (row >= 0) {
504 ComponentStatus cs = contents_container->recursiveStatus ();
505 setRowState (row, cs != Processing, cs == Satisfied);
506 }
507
508 update_timer.start ();
509
510 ComponentStatus s = recursiveStatus ();
511 if (s != last_known_status) {
512 last_known_status = s;
513 if (model) model->headerDataChanged (Qt::Horizontal, 0, model->columnCount () - 1);
514 }
515
516 RKComponent::changed ();
517 }
518
519 // This function is called when a property of the current row of the optionset changes
governingPropertyChanged(RKComponentPropertyBase * property)520 void RKOptionSet::governingPropertyChanged (RKComponentPropertyBase *property) {
521 RK_TRACE (PLUGIN);
522
523 int row = active_row;
524 if (row < 0) return;
525 if (updating) return;
526 updating = true;
527
528 QList<RKComponentPropertyStringList *> cols = columns_to_update.values (property);
529 for (int i = 0; i < cols.size (); ++i) {
530 RKComponentPropertyStringList *target = cols.at (i);
531 ColumnInfo &inf = column_map[target];
532 QString value = fetchStringValue (property, inf.governor_modifier);
533 target->setValueAt (row, value);
534
535 if (model && (inf.display_index >= 0)) {
536 model->dataChanged (model->index (inf.display_index, row), model->index (inf.display_index, row));
537 }
538 }
539
540 updating = false;
541 }
542
543 // This function is called, when a column of the set is changed, typically from external logic
columnPropertyChanged(RKComponentPropertyBase * property)544 void RKOptionSet::columnPropertyChanged (RKComponentPropertyBase *property) {
545 RK_TRACE (PLUGIN);
546
547 if (updating) return;
548
549 RKComponentPropertyStringList *target = static_cast<RKComponentPropertyStringList *> (property);
550 RK_ASSERT (column_map.contains (target));
551 ColumnInfo& ci = column_map[target];
552 if (!ci.external) {
553 RK_DEBUG (PLUGIN, DL_ERROR, "Column %s was touched externally, although it is not marked as external", qPrintable (ci.column_name));
554 return;
555 }
556
557 if (target == keycolumn) handleKeycolumnUpdate ();
558 else {
559 if (model) model->dataChanged (model->index (ci.display_index, 0), model->index (ci.display_index, model->rowCount ()));
560 applyContentsFromExternalColumn (target, active_row);
561 }
562 }
563
handleKeycolumnUpdate()564 void RKOptionSet::handleKeycolumnUpdate () {
565 RK_TRACE (PLUGIN);
566
567 int activate_row = active_row;
568 QStringList new_keys = keycolumn->values ();
569 QMap<int, int> position_changes;
570 QSet<int> found_rows;
571
572 for (int pos = 0; pos < new_keys.size (); ++pos) {
573 QString key = new_keys[pos];
574 if (old_keys.value (pos) != key) { // NOTE: old_keys could be shorter than new_keys!
575 int old_pos = old_keys.indexOf (key); // NOTE: -1 for new keys
576 if (old_pos == active_row) activate_row = pos;
577 position_changes.insert (pos, old_pos);
578 if (old_pos >= 0) found_rows.insert (old_pos);
579 } else found_rows.insert (pos);
580 }
581
582 if (position_changes.isEmpty () && (old_keys.size () == new_keys.size ())) {
583 return; // no change
584 }
585
586 // get state of current row (which may subsequently be moved or even deleted
587 storeRowSerialization (active_row);
588 updating = true;
589
590 // as a first step, take a backup of any rows that have been removed.
591 for (int i = old_keys.size () - 1; i >= 0; --i) {
592 if (!found_rows.contains (i)) {
593 former_row_states.insert (old_keys[i], rows[i].full_row_map);
594 }
595 }
596
597 // update all columns
598 QMap<RKComponentPropertyStringList *, ColumnInfo>::iterator it = column_map.begin ();
599 for (; it != column_map.end (); ++it) {
600 RKComponentPropertyStringList* col = it.key ();
601 ColumnInfo &column = it.value ();
602 if (column.external) continue;
603
604 // Ok, we'll have to adjust this column. We start by copying the old values, and padding to the
605 // new length (if that is greater than the old).
606 QStringList old_values = col->values ();
607 QStringList new_values = old_values;
608 for (int i = (new_keys.size () - new_values.size ()); i > 0; --i) new_values.append (QString ());
609
610 // adjust all positions that have changed
611 for (int pos = 0; pos < new_keys.size (); ++pos) {
612 QMap<int, int>::const_iterator pit = position_changes.find (pos);
613 if (pit != position_changes.constEnd ()) { // some change
614 int old_pos = pit.value ();
615 if (old_pos < 0) { // a new key
616 new_values[pos] = getDefaultValue (column, pos);
617 activate_row = pos;
618 } else { // old key changed position
619 new_values[pos] = old_values[old_pos];
620 } // NOTE: not visible: old key is gone without replacement
621 }
622 }
623
624 // strip excess length (if any), and apply
625 new_values = new_values.mid (0, new_keys.size ());
626 col->setValueList (new_values);
627 }
628
629 // update status info
630 QList<RowInfo> new_row_info = rows;
631 for (int i = (new_keys.size () - new_row_info.size ()); i > 0; --i) new_row_info.append (RowInfo (default_row_state));
632 for (int pos = 0; pos < new_keys.size (); ++pos) {
633 QMap<int, int>::const_iterator pit = position_changes.find (pos);
634 if (pit != position_changes.constEnd ()) { // some change
635 int old_pos = pit.value ();
636 if (old_pos < 0) { // a new key (but it might have been known, formerly)
637 new_row_info[pos] = RowInfo (former_row_states.value (new_keys[pos], default_row_state));
638 } else { // old key changed position
639 new_row_info[pos] = rows[old_pos];
640 } // NOTE: not visible: old key is gone without replacement
641 }
642 }
643 rows = new_row_info.mid (0, new_keys.size ());
644 n_invalid_rows = n_unfinished_rows = 0;
645 for (int i = 0; i < rows.size (); ++i) {
646 if (!rows[i].finished) ++n_unfinished_rows;
647 if (!rows[i].valid) ++n_invalid_rows;
648 }
649
650 old_keys = new_keys;
651
652 int nrows = new_keys.size ();
653 row_count->setIntValue (nrows);
654 if (model) model->triggerReset ();
655 updating = false;
656 activate_row = qMin (nrows - 1, activate_row);
657 setContentsForRow (active_row = activate_row);
658 current_row->setIntValue (active_row);
659 changed ();
660 }
661
applyContentsFromExternalColumn(RKComponentPropertyStringList * column,int row)662 void RKOptionSet::applyContentsFromExternalColumn (RKComponentPropertyStringList* column, int row) {
663 RK_TRACE (PLUGIN);
664
665 const ColumnInfo &ci = column_map[column];
666 if (!ci.external) return;
667 if (ci.governor.isEmpty ()) return;
668
669 updating = true;
670 QString dummy;
671 RKComponentBase *governor = contents_container->lookupComponent (ci.governor, &dummy);
672 if (governor && governor->isProperty ()) {
673 RK_ASSERT (dummy.isEmpty ());
674
675 QString value;
676 if (row >= 0) value = column->valueAt (row);
677 else value = getDefaultValue (ci, row);
678
679 static_cast<RKComponentPropertyBase*> (governor)->setValue (value);
680 } else {
681 RK_DEBUG (PLUGIN, DL_WARNING, "Lookup error while trying to restore row %d of optionset: %s. Remainder: %s", row, qPrintable (ci.governor), qPrintable (dummy));
682 RK_ASSERT (false);
683 }
684 updating = false;
685 }
686
setContentsForRow(int row)687 void RKOptionSet::setContentsForRow (int row) {
688 RK_TRACE (PLUGIN);
689
690 RK_ASSERT (rows.size () > row);
691 if (row >= 0) {
692 const PropertyValueMap *map = &(rows[row].full_row_map);
693 // If some elements are disabled, these will *not* be contained in the serialization of a row (unless that is still at the default).
694 // They *are* contained in the default_row_state, however, and thus we will apply any properties for which we do not have a value
695 // from the default_row_state, instead.
696 for (PropertyValueMap::const_iterator it = default_row_state.constBegin (); it != default_row_state.constEnd (); ++it) {
697 if (!map->contains (it.key ())) {
698 RKComponentPropertyBase *prop = contents_container->lookupProperty (it.key (), 0, true);
699 if (prop) { // found a property
700 RK_ASSERT (!prop->isInternal ());
701 prop->setValue (it.value ());
702 }
703 }
704 }
705 contents_container->setPropertyValues (map, false);
706 } else {
707 contents_container->setPropertyValues (&default_row_state, false);
708 }
709 QMap<RKComponentPropertyStringList *, ColumnInfo>::const_iterator it = column_map.constBegin ();
710 for (; it != column_map.constEnd (); ++it) {
711 RKComponentPropertyStringList* col = it.key ();
712 applyContentsFromExternalColumn (col, row);
713 }
714 contents_container->enablednessProperty ()->setBoolValue (row >= 0);
715 }
716
storeRowSerialization(int row)717 void RKOptionSet::storeRowSerialization (int row) {
718 RK_TRACE (PLUGIN);
719
720 if (row < 0) return; // No row was active
721 RK_ASSERT (rows.size () > row);
722 rows[row].full_row_map.clear ();
723 contents_container->fetchPropertyValuesRecursive (&(rows[row].full_row_map));
724 }
725
updateCurrentRowInDisplay()726 void RKOptionSet::updateCurrentRowInDisplay () {
727 if (!(accordion && model)) return; // can happen during initialization
728
729 if (active_row < 0) accordion->collapseAll ();
730 else accordion->activateRow (active_row);
731 }
732
currentRowChanged(int row)733 void RKOptionSet::currentRowChanged (int row) {
734 RK_TRACE (PLUGIN);
735
736 if (active_row != row) current_row->setIntValue (row);
737 // --> currentRowPropertyChanged ()
738 }
739
currentRowPropertyChanged(RKComponentPropertyBase * property)740 void RKOptionSet::currentRowPropertyChanged (RKComponentPropertyBase *property) {
741 RK_TRACE (PLUGIN);
742
743 RK_ASSERT (property == current_row);
744 int row = qMin (row_count->intValue () - 1, current_row->intValue ());
745 if (row != active_row) { // May or may not be the case. True, e.g. if a row was removed
746 storeRowSerialization (active_row);
747 active_row = row;
748 setContentsForRow (active_row);
749 }
750
751 updateCurrentRowInDisplay (); // Doing this, even if the current row _seeems_ unchanged, helps fixing up selection problems
752 }
753
754
755 /** reimplemented from RKComponent */
recursiveStatus()756 RKComponent::ComponentStatus RKOptionSet::recursiveStatus () {
757 RK_TRACE (PLUGIN);
758
759 ComponentStatus s = RKComponent::recursiveStatus ();
760 if (s == Dead) return s;
761 if (n_unfinished_rows > 0) return Processing;
762 return s;
763 }
764
isValid()765 bool RKOptionSet::isValid () {
766 if (n_invalid_rows > n_unfinished_rows) return false;
767 int count = row_count->intValue ();
768 if (count < min_rows) return false;
769 if ((count > 0) && (count < min_rows_if_any)) return false;
770 if (count > max_rows) return false;
771 return true;
772 }
773
774
775
776
RKOptionSetDisplayModel(RKOptionSet * parent)777 RKOptionSetDisplayModel::RKOptionSetDisplayModel (RKOptionSet* parent) : QAbstractTableModel (parent) {
778 RK_TRACE (PLUGIN);
779 set = parent;
780 connect (&reset_timer, &QTimer::timeout, this, &RKOptionSetDisplayModel::doResetNow);
781 reset_timer.setInterval (0);
782 reset_timer.setSingleShot (true);
783 }
784
~RKOptionSetDisplayModel()785 RKOptionSetDisplayModel::~RKOptionSetDisplayModel () {
786 RK_TRACE (PLUGIN);
787 }
788
columnCount(const QModelIndex & parent) const789 int RKOptionSetDisplayModel::columnCount (const QModelIndex& parent) const {
790 if (parent.isValid ()) return 0;
791 return column_labels.size ();
792 }
793
rowCount(const QModelIndex & parent) const794 int RKOptionSetDisplayModel::rowCount (const QModelIndex& parent) const {
795 if (parent.isValid ()) return 0;
796 return set->rowCount ();
797 }
798
data(const QModelIndex & index,int role) const799 QVariant RKOptionSetDisplayModel::data (const QModelIndex& index, int role) const {
800 if (!index.isValid ()) return QVariant ();
801 int row = index.row ();
802 int column = index.column ();
803
804 if (role == Qt::DisplayRole) {
805 RKComponentPropertyStringList *p = set->visible_columns.value (column);
806 if (p) {
807 return QVariant (p->valueAt (row));
808 } else {
809 RK_ASSERT (false);
810 }
811 } else if (role == Qt::BackgroundRole) {
812 const RKOptionSet::RowInfo &ri = set->rows[row];
813 if (!ri.finished) return QColor (Qt::yellow);
814 if (!ri.valid) return QColor (Qt::red);
815 } else if ((role == Qt::ToolTipRole) || role == (Qt::StatusTipRole)) {
816 const RKOptionSet::RowInfo &ri = set->rows[row];
817 if (!ri.finished) return i18n ("This row has not yet been processed.");
818 if (!ri.valid) return i18n ("This row contains invalid settings.");
819 }
820
821 return QVariant ();
822 }
823
doResetNow()824 void RKOptionSetDisplayModel::doResetNow () {
825 RK_TRACE (PLUGIN);
826 emit (layoutChanged ());
827 set->updateCurrentRowInDisplay ();
828 }
829
headerData(int section,Qt::Orientation orientation,int role) const830 QVariant RKOptionSetDisplayModel::headerData (int section, Qt::Orientation orientation, int role) const {
831 if (orientation == Qt::Horizontal) {
832 if (role == Qt::DisplayRole) return (column_labels.value (section));
833 if (role == Qt::BackgroundRole) {
834 if (set->n_unfinished_rows > 0) return QColor (Qt::yellow);
835 if (!set->isValid ()) return QColor (Qt::red);
836 }
837 if ((role == Qt::ToolTipRole) || role == (Qt::StatusTipRole)) {
838 if (set->n_unfinished_rows > 0) return i18n ("Please wait while settings are being processed");
839 if (!set->isValid ()) {
840 QStringList probs;
841 if (set->n_invalid_rows > set->n_unfinished_rows) probs.append (i18n ("One or more rows contain invalid settings."));
842 if ((set->rowCount () > 0) && (set->rowCount () < set->min_rows_if_any)) probs.append (i18n ("At least %1 rows have to be defined (if any)", set->min_rows_if_any));
843 if (set->rowCount () < set->min_rows) probs.append (i18n ("At least %1 rows have to be defined", set->min_rows));
844 if (set->rowCount () > set->max_rows) probs.append (i18n ("At most %1 rows may be defined", set->max_rows));
845 return (QString ("<p>%1</p><ul><li>%2</li></ul>").arg (i18n ("This element is not valid for the following reason(s):")).arg (probs.join ("</li>\n<li>")));
846 }
847 }
848 }
849 return QVariant ();
850 }
851
triggerReset()852 void RKOptionSetDisplayModel::triggerReset() {
853 RK_TRACE (PLUGIN);
854 if (!reset_timer.isActive ()) {
855 emit (layoutAboutToBeChanged ());
856 reset_timer.start ();
857 }
858 }
859
860 QString optionsetdisplaymodel_mt ("application/x-rkaccordiontableitem");
mimeTypes() const861 QStringList RKOptionSetDisplayModel::mimeTypes () const {
862 return QStringList (optionsetdisplaymodel_mt);
863 }
864
mimeData(const QModelIndexList & indexes) const865 QMimeData* RKOptionSetDisplayModel::mimeData (const QModelIndexList& indexes) const {
866 RK_ASSERT (indexes.length () >= 1);
867 QMimeData *ret = new QMimeData ();
868 ret->setData (optionsetdisplaymodel_mt, QByteArray (QString::number (indexes.first ().row ()).toLatin1 ()));
869 return (ret);
870 }
871
dropMimeData(const QMimeData * data,Qt::DropAction action,int row,int column,const QModelIndex & parent)872 bool RKOptionSetDisplayModel::dropMimeData (const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) {
873 Q_UNUSED (column);
874 if (action == Qt::IgnoreAction) return true;
875 if (action == Qt::MoveAction) {
876 if (parent.isValid ()) return false;
877 int srow = QString::fromLatin1 (data->data (optionsetdisplaymodel_mt)).toInt ();
878 set->moveRow (srow, row);
879 }
880 return false;
881 }
882
flags(const QModelIndex & index) const883 Qt::ItemFlags RKOptionSetDisplayModel::flags (const QModelIndex& index) const {
884 return QAbstractItemModel::flags (index) | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
885 }
886
supportedDropActions() const887 Qt::DropActions RKOptionSetDisplayModel::supportedDropActions () const {
888 return Qt::MoveAction;
889 }
890
supportedDragActions() const891 Qt::DropActions RKOptionSetDisplayModel::supportedDragActions() const {
892 return Qt::MoveAction;
893 }
894