1 /*
2 * Copyright (c) 2015-2020, The University of Oxford
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * 1. Redistributions of source code must retain the above copyright notice,
8 * this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright notice,
10 * this list of conditions and the following disclaimer in the documentation
11 * and/or other materials provided with the distribution.
12 * 3. Neither the name of the University of Oxford nor the names of its
13 * contributors may be used to endorse or promote products derived from this
14 * software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26 * POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "gui/oskar_SettingsModel.h"
30 #include "settings/oskar_settings_types.h"
31 #include "settings/oskar_SettingsTree.h"
32 #include "settings/oskar_SettingsNode.h"
33 #include "settings/oskar_SettingsKey.h"
34 #include "settings/oskar_SettingsValue.h"
35 #include <QApplication>
36 #include <QFile>
37 #include <QFileInfo>
38 #include <QFontMetrics>
39 #include <QIcon>
40 #include <QModelIndex>
41 #include <QPalette>
42 #include <QSize>
43 #include <QStringList>
44 #include <QVariant>
45
46 using namespace std;
47
48 namespace oskar {
49
SettingsModel(SettingsTree * settings,QObject * parent)50 SettingsModel::SettingsModel(SettingsTree* settings, QObject* parent)
51 : QAbstractItemModel(parent),
52 settings_(settings),
53 iconOpen_(QIcon(":/icons/open.png")),
54 iconSave_(QIcon(":/icons/save.png")),
55 lastModified_(QDateTime::currentDateTime()),
56 displayKey_(false)
57 {
58 }
59
~SettingsModel()60 SettingsModel::~SettingsModel()
61 {
62 }
63
beginReset()64 void SettingsModel::beginReset()
65 {
66 beginResetModel();
67 }
68
endReset()69 void SettingsModel::endReset()
70 {
71 endResetModel();
72 }
73
columnCount(const QModelIndex &) const74 int SettingsModel::columnCount(const QModelIndex& /*parent*/) const
75 {
76 return 2;
77 }
78
data(const QModelIndex & index,int role) const79 QVariant SettingsModel::data(const QModelIndex& index, int role) const
80 {
81 // Get a pointer to the item.
82 if (!index.isValid())
83 return QVariant();
84
85 const SettingsNode* node = get_node(index);
86 const SettingsValue& value = node->settings_value();
87 const SettingsValue::TypeId type = value.type();
88 const char* key = node->key();
89
90 // Check for roles common to all columns.
91 switch (role)
92 {
93 case Qt::ForegroundRole:
94 {
95 QPalette palette = QApplication::palette((QWidget*) 0);
96 if (!settings_->dependencies_satisfied(key))
97 return palette.color(QPalette::Disabled, QPalette::Text);
98 if (settings_->is_critical(key))
99 return QColor(Qt::white);
100 if (node->value_or_child_set())
101 return palette.color(QPalette::Normal, QPalette::Link);
102 return palette.color(QPalette::Normal, QPalette::Text);
103 }
104 case Qt::BackgroundRole:
105 {
106 bool disabled = !settings_->dependencies_satisfied(key);
107 if (settings_->is_critical(key) && !disabled)
108 {
109 if (index.column() == 0)
110 return QColor(0, 48, 255, 160);
111 else if (node->item_type() != SettingsItem::LABEL)
112 return QColor(255, 64, 64, 255);
113 }
114 if (index.column() == 1)
115 return QColor(0, 0, 192, 12);
116 break;
117 }
118 case Qt::ToolTipRole:
119 {
120 QString tooltip = QString(node->description());
121 if (!tooltip.isEmpty())
122 {
123 tooltip = "<p>" + tooltip + "</p>";
124 if (node->is_required())
125 tooltip.append(" [Required]");
126 // if (node->item_type() == SettingsItem::SETTING)
127 // tooltip.append(" [" + QString::fromStdString(
128 // node->value().type_name()) + "]");
129 }
130 return tooltip;
131 }
132 case Qt::EditRole:
133 {
134 if (node->item_type() == SettingsItem::SETTING)
135 return QString(node->value());
136 break;
137 }
138 case KeyRole:
139 return QString(key);
140 case ValueRole:
141 return QString(node->value());
142 case DefaultRole:
143 return QString(node->default_value());
144 case TypeRole:
145 return type;
146 case ItemTypeRole:
147 return node->item_type();
148 case RangeRole:
149 {
150 QList<QVariant> range;
151 switch (type)
152 {
153 case SettingsValue::INT_RANGE:
154 {
155 range.append(value.get<IntRange>().min());
156 range.append(value.get<IntRange>().max());
157 return range;
158 }
159 case SettingsValue::DOUBLE_RANGE:
160 {
161 range.append(value.get<DoubleRange>().min());
162 range.append(value.get<DoubleRange>().max());
163 return range;
164 }
165 default:
166 break;
167 }
168 return range;
169 }
170 case ExtRangeRole:
171 {
172 QList<QVariant> range;
173 switch (type)
174 {
175 case SettingsValue::INT_RANGE_EXT:
176 {
177 range.append(value.get<IntRangeExt>().min());
178 range.append(value.get<IntRangeExt>().max());
179 range.append(QString(value.get<IntRangeExt>().ext_min()));
180 range.append(QString(value.get<IntRangeExt>().ext_max()));
181 return range;
182 }
183 case SettingsValue::DOUBLE_RANGE_EXT:
184 {
185 range.append(value.get<DoubleRangeExt>().min());
186 range.append(value.get<DoubleRangeExt>().max());
187 range.append(QString(value.get<DoubleRangeExt>().ext_min()));
188 range.append(QString(value.get<DoubleRangeExt>().ext_max()));
189 return range;
190 }
191 default:
192 break;
193 }
194 return range;
195 }
196 case OptionsRole:
197 {
198 QStringList options;
199 if (type == SettingsValue::OPTION_LIST)
200 {
201 const OptionList& l = value.get<OptionList>();
202 for (int i = 0; i < l.size(); ++i)
203 options.push_back(QString(l.option(i)));
204 }
205 return options;
206 }
207 default:
208 break;
209 }
210
211 // Check for roles in specific columns.
212 if (index.column() == 0)
213 {
214 switch (role)
215 {
216 case Qt::SizeHintRole:
217 {
218 int width = QApplication::fontMetrics().width(
219 (data(index, Qt::DisplayRole)).toString()) + 20;
220 return QSize(width, 26);
221 }
222 case Qt::DisplayRole:
223 return displayKey_ ? QString(key) : QString(node->label());
224 default:
225 break;
226 }
227 }
228 else if (index.column() == 1)
229 {
230 switch (role)
231 {
232 case Qt::DisplayRole:
233 {
234 if (node->item_type() == SettingsItem::SETTING)
235 return QString(node->value());
236 break;
237 }
238 case Qt::CheckStateRole:
239 {
240 if (type == SettingsValue::BOOL)
241 return value.get<Bool>().value() ? Qt::Checked : Qt::Unchecked;
242 break;
243 }
244 case Qt::DecorationRole:
245 {
246 if (type == SettingsValue::INPUT_FILE ||
247 type == SettingsValue::INPUT_FILE_LIST ||
248 type == SettingsValue::INPUT_DIRECTORY)
249 return iconOpen_;
250 else if (type == SettingsValue::OUTPUT_FILE)
251 return iconSave_;
252 break;
253 }
254 default:
255 break;
256 }
257 }
258
259 return QVariant();
260 }
261
flags(const QModelIndex & index) const262 Qt::ItemFlags SettingsModel::flags(const QModelIndex& index) const
263 {
264 if (!index.isValid())
265 return 0;
266
267 const SettingsNode* node = get_node(index);
268 if (!settings_->dependencies_satisfied(node->key()))
269 return Qt::ItemIsSelectable;
270 int column = index.column();
271 if (column == 0 || node->item_type() == SettingsItem::LABEL)
272 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
273 if (column == 1 && node->settings_value().type() == SettingsValue::BOOL)
274 return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable;
275
276 return Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsSelectable;
277 }
278
headerData(int section,Qt::Orientation orientation,int role) const279 QVariant SettingsModel::headerData(int section,
280 Qt::Orientation orientation, int role) const
281 {
282 if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
283 {
284 if (section == 0)
285 return "Setting";
286 else if (section == 1)
287 return "Value";
288 }
289
290 return QVariant();
291 }
292
index(int row,int column,const QModelIndex & parent) const293 QModelIndex SettingsModel::index(int row, int column,
294 const QModelIndex& parent) const
295 {
296 if (parent.isValid() && parent.column() != 0)
297 return QModelIndex();
298
299 const SettingsNode* child_node = get_node(parent)->child(row);
300 // This is from the Qt documentation: const_cast is unavoidable here.
301 SettingsNode* node = const_cast<SettingsNode*>(child_node);
302 if (child_node)
303 return createIndex(row, column, static_cast<void*>(node));
304 else
305 return QModelIndex();
306 }
307
load_settings_file(const QString & filename)308 void SettingsModel::load_settings_file(const QString& filename)
309 {
310 if (!filename.isEmpty()) filename_ = filename;
311 lastModified_ = QDateTime::currentDateTime();
312 settings_->load(filename.toLatin1().constData());
313 refresh(QModelIndex());
314 }
315
save_settings_file(const QString & filename)316 void SettingsModel::save_settings_file(const QString& filename)
317 {
318 if (!filename.isEmpty()) filename_ = filename;
319 lastModified_ = QDateTime::currentDateTime();
320 settings_->save(filename.toLatin1().constData());
321 }
322
parent(const QModelIndex & index) const323 QModelIndex SettingsModel::parent(const QModelIndex& index) const
324 {
325 if (!index.isValid())
326 return QModelIndex();
327
328 const SettingsNode* parent = get_node(index)->parent();
329 if (parent == settings_->root_node())
330 return QModelIndex();
331
332 SettingsNode* node = const_cast<SettingsNode*>(parent);
333 return createIndex(parent->child_number(), 0, static_cast<void*>(node));
334 }
335
refresh()336 void SettingsModel::refresh()
337 {
338 refresh(QModelIndex());
339 }
340
rowCount(const QModelIndex & parent) const341 int SettingsModel::rowCount(const QModelIndex& parent) const
342 {
343 return get_node(parent)->num_children();
344 }
345
setData(const QModelIndex & idx,const QVariant & value,int role)346 bool SettingsModel::setData(const QModelIndex& idx, const QVariant& value,
347 int role)
348 {
349 // Check for roles that do not depend on the index.
350 if (role == CheckExternalChangesRole)
351 {
352 if (!QFile::exists(filename_)) return false;
353 QFileInfo fileInfo(filename_);
354 if (fileInfo.lastModified() > lastModified_.addMSecs(200))
355 {
356 load_settings_file(filename_);
357 lastModified_ = QDateTime::currentDateTime();
358 emit fileReloaded();
359 }
360 return true;
361 }
362 else if (role == DisplayKeysRole)
363 {
364 displayKey_ = value.toBool();
365 refresh(QModelIndex());
366 return true;
367 }
368
369 if (!idx.isValid())
370 return false;
371
372 // Get a pointer to the item.
373 const SettingsNode* node = get_node(idx);
374
375 // Get model indexes for the row.
376 QModelIndex topLeft = idx.sibling(idx.row(), 0);
377 QModelIndex bottomRight = idx.sibling(idx.row(), columnCount() - 1);
378
379 // Check for role type.
380 if (role == Qt::EditRole || role == Qt::CheckStateRole)
381 {
382 QVariant data = value;
383 if (role == Qt::CheckStateRole)
384 data = value.toBool() ? QString("true") : QString("false");
385
386 if (idx.column() == 1)
387 {
388 lastModified_ = QDateTime::currentDateTime();
389 QString value;
390 if (node->settings_value().type() == SettingsValue::INPUT_FILE_LIST)
391 {
392 QStringList l = data.toStringList();
393 for (int i = 0; i < l.size(); ++i) {
394 value += l[i];
395 if (i < l.size()) value += ",";
396 }
397 }
398 else {
399 value = data.toString();
400 }
401 settings_->set_value(node->key(), value.toLatin1().constData());
402
403 QModelIndex i(idx);
404 while (i.isValid())
405 {
406 emit dataChanged(i.sibling(i.row(), 0),
407 i.sibling(i.row(), columnCount()-1));
408 i = i.parent();
409 }
410 emit dataChanged(topLeft, bottomRight);
411 return true;
412 }
413 }
414 else if (role == SettingsModel::ResetGroupRole)
415 {
416 if (idx.column() == 1) {
417 reset_group_(node);
418 lastModified_ = QDateTime::currentDateTime();
419 // TODO(BM) call dataChanged on all children and parents too.
420 // seems to work at the moment on the basis of luck or the right
421 // click action used to call reset calling a redraw.
422 emit dataChanged(topLeft, bottomRight);
423 // QModelIndex i(idx);
424 // while (i.isValid())
425 // {
426 // emit dataChanged(i.sibling(i.row(), 0),
427 // i.sibling(i.row(), columnCount()-1));
428 // i = i.parent();
429 // }
430 return true;
431 }
432 }
433 return false;
434 }
435
436
437 // Private methods.
438
reset_group_(const SettingsNode * node)439 void SettingsModel::reset_group_(const SettingsNode* node)
440 {
441 for (int i = 0; i < node->num_children(); ++i) {
442 const SettingsNode* child = node->child(i);
443 settings_->set_value(child->key(), child->default_value());
444 reset_group_(child);
445 }
446 }
447
get_node(const QModelIndex & index) const448 const SettingsNode* SettingsModel::get_node(const QModelIndex& index) const
449 {
450 if (index.isValid())
451 {
452 SettingsNode* node = static_cast<SettingsNode*>(index.internalPointer());
453 if (node) return node;
454 }
455 return settings_->root_node();
456 }
457
refresh(const QModelIndex & parent)458 void SettingsModel::refresh(const QModelIndex& parent)
459 {
460 int rows = rowCount(parent);
461 for (int i = 0; i < rows; ++i)
462 {
463 QModelIndex idx = index(i, 0, parent);
464 if (idx.isValid())
465 {
466 emit dataChanged(idx, idx.sibling(idx.row(), 1));
467 refresh(idx);
468 }
469 }
470 }
471
472
SettingsModelFilter(QObject * parent)473 SettingsModelFilter::SettingsModelFilter(QObject* parent)
474 : QSortFilterProxyModel(parent)
475 {
476 setDynamicSortFilter(true);
477 }
478
~SettingsModelFilter()479 SettingsModelFilter::~SettingsModelFilter()
480 {
481 }
482
data(const QModelIndex & index,int role) const483 QVariant SettingsModelFilter::data(const QModelIndex& index, int role) const
484 {
485 if (!filterRegExp().isEmpty())
486 {
487 if (role == Qt::BackgroundRole && index.column() == 0)
488 {
489 QString label = QSortFilterProxyModel::data(index,
490 Qt::DisplayRole).toString();
491 if (label.contains(filterRegExp().pattern(), Qt::CaseInsensitive))
492 return QColor("#FFFF9F");
493 }
494 }
495 return QSortFilterProxyModel::data(index, role);
496 }
497
setFilterRegExp(const QString & pattern)498 void SettingsModelFilter::setFilterRegExp(const QString& pattern)
499 {
500 QSortFilterProxyModel::setFilterRegExp(pattern);
501 invalidate();
502 }
503
504 // Protected methods.
505
filterAcceptsChildren(int sourceRow,const QModelIndex & sourceParent) const506 bool SettingsModelFilter::filterAcceptsChildren(int sourceRow,
507 const QModelIndex& sourceParent) const
508 {
509 QModelIndex idx = sourceModel()->index(sourceRow, 0, sourceParent);
510 if (!idx.isValid())
511 return false;
512
513 int childCount = idx.model()->rowCount(idx);
514 for (int i = 0; i < childCount; ++i)
515 {
516 if (filterAcceptsCurrentRow(i, idx))
517 return true;
518 if (filterAcceptsChildren(i, idx))
519 return true;
520 }
521 return false;
522 }
523
filterAcceptsCurrentRow(const QModelIndex & idx) const524 bool SettingsModelFilter::filterAcceptsCurrentRow(const QModelIndex& idx) const
525 {
526 QString labelCurrent = sourceModel()->data(idx, Qt::DisplayRole).toString();
527 return labelCurrent.contains(filterRegExp().pattern(), Qt::CaseInsensitive);
528 }
529
filterAcceptsCurrentRow(int sourceRow,const QModelIndex & sourceParent) const530 bool SettingsModelFilter::filterAcceptsCurrentRow(int sourceRow,
531 const QModelIndex& sourceParent) const
532 {
533 QModelIndex idx = sourceModel()->index(sourceRow, 0, sourceParent);
534 return filterAcceptsCurrentRow(idx);
535 }
536
filterAcceptsRow(int sourceRow,const QModelIndex & sourceParent) const537 bool SettingsModelFilter::filterAcceptsRow(int sourceRow,
538 const QModelIndex& sourceParent) const
539 {
540 // Check if filter accepts this row.
541 QModelIndex idx = sourceModel()->index(sourceRow, 0, sourceParent);
542 if (filterAcceptsCurrentRow(idx))
543 return true;
544
545 // Check if filter accepts any parent.
546 QModelIndex parent = sourceParent;
547 while (parent.isValid())
548 {
549 if (filterAcceptsCurrentRow(parent.row(), parent.parent()))
550 return true;
551 parent = parent.parent();
552 }
553
554 // Check if filter accepts any child.
555 if (filterAcceptsChildren(sourceRow, sourceParent))
556 return true;
557
558 return false;
559 }
560
561 } // namespace oskar
562