1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "configmodel.h"
27
28 #include <utils/algorithm.h>
29 #include <utils/qtcassert.h>
30 #include <utils/theme/theme.h>
31
32 #include <QCoreApplication>
33 #include <QFont>
34 #include <QSortFilterProxyModel>
35
36 namespace CMakeProjectManager {
37
ConfigModel(QObject * parent)38 ConfigModel::ConfigModel(QObject *parent) : Utils::TreeModel<>(parent)
39 {
40 setHeader({tr("Key"), tr("Value")});
41 }
42
data(const QModelIndex & idx,int role) const43 QVariant ConfigModel::data(const QModelIndex &idx, int role) const
44 {
45 // Hide/show groups according to "isAdvanced" setting:
46 auto item = static_cast<const Utils::TreeItem *>(idx.internalPointer());
47 if (role == ItemIsAdvancedRole && item->childCount() > 0) {
48 const bool hasNormalChildren = item->findAnyChild([](const Utils::TreeItem *ti) {
49 if (auto cmti = dynamic_cast<const Internal::ConfigModelTreeItem*>(ti))
50 return !cmti->dataItem->isAdvanced;
51 return false;
52 }) != nullptr;
53 return hasNormalChildren ? "0" : "1";
54 }
55 return Utils::TreeModel<>::data(idx, role);
56 }
57
58 ConfigModel::~ConfigModel() = default;
59
appendConfiguration(const QString & key,const QString & value,const ConfigModel::DataItem::Type type,const QString & description,const QStringList & values)60 void ConfigModel::appendConfiguration(const QString &key,
61 const QString &value,
62 const ConfigModel::DataItem::Type type,
63 const QString &description,
64 const QStringList &values)
65 {
66 DataItem item;
67 item.key = key;
68 item.type = type;
69 item.value = value;
70 item.description = description;
71 item.values = values;
72
73 InternalDataItem internalItem(item);
74 internalItem.isUserNew = true;
75
76 if (m_kitConfiguration.contains(key))
77 internalItem.kitValue = m_kitConfiguration.value(key);
78
79 m_configuration.append(internalItem);
80 setConfiguration(m_configuration);
81 }
82
setConfiguration(const QList<DataItem> & config)83 void ConfigModel::setConfiguration(const QList<DataItem> &config)
84 {
85 setConfiguration(Utils::transform(config, [](const DataItem &di) { return InternalDataItem(di); }));
86 }
87
setConfigurationFromKit(const QHash<QString,QString> & kitConfig)88 void ConfigModel::setConfigurationFromKit(const QHash<QString, QString> &kitConfig)
89 {
90 m_kitConfiguration = kitConfig;
91
92 for (InternalDataItem &i : m_configuration) {
93 if (m_kitConfiguration.contains(i.key))
94 i.kitValue = m_kitConfiguration.value(i.key);
95 }
96 setConfiguration(m_configuration);
97 }
98
flush()99 void ConfigModel::flush()
100 {
101 setConfiguration(QList<InternalDataItem>());
102 }
103
resetAllChanges()104 void ConfigModel::resetAllChanges()
105 {
106 const QList<InternalDataItem> tmp
107 = Utils::filtered(m_configuration,
108 [](const InternalDataItem &i) { return !i.isUserNew; });
109
110 setConfiguration(Utils::transform(tmp, [](const InternalDataItem &i) {
111 InternalDataItem ni(i);
112 ni.newValue.clear();
113 ni.isUserChanged = false;
114 ni.isUnset = false;
115 return ni;
116 }));
117 }
118
hasChanges() const119 bool ConfigModel::hasChanges() const
120 {
121 return Utils::contains(m_configuration, [](const InternalDataItem &i) {
122 return i.isUserChanged || i.isUserNew || i.isUnset;
123 });
124 }
125
canForceTo(const QModelIndex & idx,const ConfigModel::DataItem::Type type) const126 bool ConfigModel::canForceTo(const QModelIndex &idx, const ConfigModel::DataItem::Type type) const
127 {
128 if (idx.model() != const_cast<ConfigModel *>(this) || idx.column() != 1)
129 return false;
130 Utils::TreeItem *item = itemForIndex(idx);
131 auto cmti = dynamic_cast<Internal::ConfigModelTreeItem *>(item);
132 return cmti && (cmti->dataItem->type != type);
133 }
134
forceTo(const QModelIndex & idx,const ConfigModel::DataItem::Type type)135 void ConfigModel::forceTo(const QModelIndex &idx, const ConfigModel::DataItem::Type type)
136 {
137 QTC_ASSERT(canForceTo(idx, type), return);
138 Utils::TreeItem *item = itemForIndex(idx);
139 auto cmti = dynamic_cast<Internal::ConfigModelTreeItem *>(item);
140
141 cmti->dataItem->type = type;
142 const QModelIndex valueIdx = idx.sibling(idx.row(), 1);
143 emit dataChanged(valueIdx, valueIdx);
144 }
145
toggleUnsetFlag(const QModelIndex & idx)146 void ConfigModel::toggleUnsetFlag(const QModelIndex &idx)
147 {
148 Utils::TreeItem *item = itemForIndex(idx);
149 auto cmti = dynamic_cast<Internal::ConfigModelTreeItem *>(item);
150
151 QTC_ASSERT(cmti, return);
152
153 cmti->dataItem->isUnset = !cmti->dataItem->isUnset;
154 const QModelIndex valueIdx = idx.sibling(idx.row(), 1);
155 const QModelIndex keyIdx = idx.sibling(idx.row(), 0);
156 emit dataChanged(keyIdx, valueIdx);
157 }
158
dataItemFromIndex(const QModelIndex & idx)159 ConfigModel::DataItem ConfigModel::dataItemFromIndex(const QModelIndex &idx)
160 {
161 const QAbstractItemModel *m = idx.model();
162 QModelIndex mIdx = idx;
163 while (auto sfpm = qobject_cast<const QSortFilterProxyModel *>(m)) {
164 m = sfpm->sourceModel();
165 mIdx = sfpm->mapToSource(mIdx);
166 }
167 auto model = qobject_cast<const ConfigModel *>(m);
168 QTC_ASSERT(model, return DataItem());
169 const QModelIndex modelIdx = mIdx;
170
171 Utils::TreeItem *item = model->itemForIndex(modelIdx);
172 auto cmti = dynamic_cast<Internal::ConfigModelTreeItem *>(item);
173
174 if (cmti && cmti->dataItem) {
175 DataItem di;
176 di.key = cmti->dataItem->key;
177 di.type = cmti->dataItem->type;
178 di.isHidden = cmti->dataItem->isHidden;
179 di.isAdvanced = cmti->dataItem->isAdvanced;
180 di.inCMakeCache = cmti->dataItem->inCMakeCache;
181 di.value = cmti->dataItem->currentValue();
182 di.description = cmti->dataItem->description;
183 di.values = cmti->dataItem->values;
184 di.isUnset = cmti->dataItem->isUnset;
185 return di;
186 }
187 return DataItem();
188 }
189
configurationForCMake() const190 QList<ConfigModel::DataItem> ConfigModel::configurationForCMake() const
191 {
192 const QList<InternalDataItem> tmp
193 = Utils::filtered(m_configuration, [](const InternalDataItem &i) {
194 return i.isUserChanged || i.isUserNew || !i.inCMakeCache || i.isUnset;
195 });
196 return Utils::transform(tmp, [](const InternalDataItem &item) {
197 DataItem newItem(item);
198 if (item.isUserChanged)
199 newItem.value = item.newValue;
200 return newItem;
201 });
202 }
203
setConfiguration(const CMakeConfig & config)204 void ConfigModel::setConfiguration(const CMakeConfig &config)
205 {
206 setConfiguration(Utils::transform(config.toList(), [](const CMakeConfigItem &i) {
207 return DataItem(i);
208 }));
209 }
210
setBatchEditConfiguration(const CMakeConfig & config)211 void ConfigModel::setBatchEditConfiguration(const CMakeConfig &config)
212 {
213 for (const auto &c: config) {
214 DataItem di(c);
215 auto existing = std::find(m_configuration.begin(), m_configuration.end(), di);
216 if (existing != m_configuration.end()) {
217 existing->isUnset = c.isUnset;
218 if (!c.isUnset) {
219 existing->isUserChanged = true;
220 existing->setType(c.type);
221 existing->value = QString::fromUtf8(c.value);
222 existing->newValue = existing->value;
223 }
224 } else if (!c.isUnset) {
225 InternalDataItem i(di);
226 i.isUserNew = true;
227 i.newValue = di.value;
228 m_configuration.append(i);
229 }
230 }
231
232 generateTree();
233 }
234
setConfiguration(const QList<ConfigModel::InternalDataItem> & config)235 void ConfigModel::setConfiguration(const QList<ConfigModel::InternalDataItem> &config)
236 {
237 QList<InternalDataItem> tmp = config;
238 auto newIt = tmp.constBegin();
239 auto newEndIt = tmp.constEnd();
240 auto oldIt = m_configuration.constBegin();
241 auto oldEndIt = m_configuration.constEnd();
242
243 QList<InternalDataItem> result;
244 while (newIt != newEndIt && oldIt != oldEndIt) {
245 if (oldIt->isUnset) {
246 ++oldIt;
247 } else if (newIt->isHidden || newIt->isUnset) {
248 ++newIt;
249 } else if (newIt->key < oldIt->key) {
250 // Add new entry:
251 result << *newIt;
252 ++newIt;
253 } else if (newIt->key > oldIt->key) {
254 // Keep old user settings, but skip other entries:
255 if (oldIt->isUserChanged || oldIt->isUserNew)
256 result << InternalDataItem(*oldIt);
257 ++oldIt;
258 } else {
259 // merge old/new entry:
260 InternalDataItem item(*newIt);
261 item.newValue = (newIt->value != oldIt->newValue) ? oldIt->newValue : QString();
262 item.isUserChanged = !item.newValue.isEmpty() && (item.newValue != item.value);
263 result << item;
264 ++newIt;
265 ++oldIt;
266 }
267 }
268
269 // Add remaining new entries:
270 for (; newIt != newEndIt; ++newIt) {
271 if (newIt->isHidden)
272 continue;
273 result << InternalDataItem(*newIt);
274 }
275
276 m_configuration = result;
277
278 generateTree();
279 }
280
prefix(const QString & key)281 static QString prefix(const QString &key)
282 {
283 int pos = key.indexOf('_');
284 if (pos > 0)
285 return key.left(pos);
286 return key;
287 }
288
generateTree()289 void ConfigModel::generateTree()
290 {
291 QList<QString> prefixList;
292
293 // Generate nodes for *all* prefixes
294 QHash<QString, QList<Utils::TreeItem *>> prefixes;
295 for (const InternalDataItem &di : qAsConst(m_configuration)) {
296 const QString p = prefix(di.key);
297 if (!prefixes.contains(p)) {
298 prefixes.insert(p, {});
299 prefixList.append(p);
300 }
301 }
302
303 // Fill prefix nodes:
304 for (InternalDataItem &di : m_configuration)
305 prefixes[prefix(di.key)].append(new Internal::ConfigModelTreeItem(&di));
306
307 auto root = new Utils::TreeItem;
308
309 for (const QString &p : qAsConst(prefixList)) {
310 const QList<Utils::TreeItem *> &prefixItemList = prefixes.value(p);
311 QTC_ASSERT(!prefixItemList.isEmpty(), continue);
312
313 if (prefixItemList.count() == 1) {
314 root->appendChild(prefixItemList.at(0));
315 } else {
316 Utils::TreeItem *sti = new Utils::StaticTreeItem(p);
317 for (Utils::TreeItem *const ti : prefixItemList)
318 sti->appendChild(ti);
319 root->appendChild(sti);
320 }
321 prefixes.remove(p);
322 }
323 QTC_CHECK(prefixes.isEmpty());
324
325 setRootItem(root);
326 }
327
InternalDataItem(const ConfigModel::DataItem & item)328 ConfigModel::InternalDataItem::InternalDataItem(const ConfigModel::DataItem &item) : DataItem(item)
329 { }
330
toolTip() const331 QString ConfigModel::InternalDataItem::toolTip() const
332 {
333 QString desc = description;
334 if (isAdvanced)
335 desc += QCoreApplication::translate("CMakeProjectManager::ConfigModel", " (ADVANCED)");
336 QStringList tooltip(desc);
337 if (inCMakeCache) {
338 if (value != newValue)
339 tooltip << QCoreApplication::translate("CMakeProjectManager", "Current CMake: %1").arg(value);
340 } else {
341 tooltip << QCoreApplication::translate("CMakeProjectManager", "Not in CMakeCache.txt").arg(value);
342 }
343 if (!kitValue.isEmpty())
344 tooltip << QCoreApplication::translate("CMakeProjectManager::ConfigModel", "Current kit: %1").arg(kitValue);
345 return tooltip.join("<br>");
346 }
347
currentValue() const348 QString ConfigModel::InternalDataItem::currentValue() const
349 {
350 if (isUnset)
351 return value;
352 return isUserChanged ? newValue : value;
353 }
354
355 namespace Internal {
356
357 ConfigModelTreeItem::~ConfigModelTreeItem() = default;
358
data(int column,int role) const359 QVariant ConfigModelTreeItem::data(int column, int role) const
360 {
361 QTC_ASSERT(column >= 0 && column < 2, return QVariant());
362
363 QTC_ASSERT(dataItem, return QVariant());
364
365 if (firstChild()) {
366 // Node with children: Only ever show name:
367 if (column == 0)
368 return dataItem->key;
369 return QVariant();
370 }
371
372 // Leaf node:
373 if (role == ConfigModel::ItemIsAdvancedRole)
374 return dataItem->isAdvanced ? "1" : "0";
375
376 switch (column) {
377 case 0:
378 switch (role) {
379 case Qt::DisplayRole:
380 return dataItem->key.isEmpty() ? QCoreApplication::translate("CMakeProjectManager::ConfigModel", "<UNSET>") : dataItem->key;
381 case Qt::EditRole:
382 return dataItem->key;
383 case Qt::ToolTipRole:
384 return toolTip();
385 case Qt::FontRole: {
386 QFont font;
387 font.setBold(dataItem->isUserNew);
388 font.setStrikeOut((!dataItem->inCMakeCache && !dataItem->isUserNew) || dataItem->isUnset);
389 return font;
390 }
391 default:
392 return QVariant();
393 }
394 case 1: {
395 const QString value = currentValue();
396 const auto boolValue = CMakeConfigItem::toBool(value);
397 const bool isTrue = boolValue.has_value() && boolValue.value();
398
399 switch (role) {
400 case Qt::CheckStateRole:
401 return (dataItem->type == ConfigModel::DataItem::BOOLEAN)
402 ? QVariant(isTrue ? Qt::Checked : Qt::Unchecked) : QVariant();
403 case Qt::DisplayRole:
404 return value;
405 case Qt::EditRole:
406 return (dataItem->type == ConfigModel::DataItem::BOOLEAN) ? QVariant(isTrue) : QVariant(value);
407 case Qt::FontRole: {
408 QFont font;
409 font.setBold((dataItem->isUserChanged || dataItem->isUserNew) && !dataItem->isUnset);
410 font.setStrikeOut((!dataItem->inCMakeCache && !dataItem->isUserNew) || dataItem->isUnset);
411 return font;
412 }
413 case Qt::ForegroundRole:
414 return Utils::creatorTheme()->color((!dataItem->kitValue.isNull() && dataItem->kitValue != value)
415 ? Utils::Theme::TextColorHighlight : Utils::Theme::TextColorNormal);
416 case Qt::ToolTipRole: {
417 return toolTip();
418 }
419 default:
420 return QVariant();
421 }
422 }
423 default:
424 return QVariant();
425 }
426 }
427
setData(int column,const QVariant & value,int role)428 bool ConfigModelTreeItem::setData(int column, const QVariant &value, int role)
429 {
430 QTC_ASSERT(column >= 0 && column < 2, return false);
431 QTC_ASSERT(dataItem, return false);
432 if (dataItem->isUnset)
433 return false;
434
435 QString newValue = value.toString();
436 if (role == Qt::CheckStateRole) {
437 if (column != 1)
438 return false;
439 newValue = QString::fromLatin1(value.toInt() == 0 ? "OFF" : "ON");
440 } else if (role != Qt::EditRole) {
441 return false;
442 }
443
444 switch (column) {
445 case 0:
446 if (!dataItem->key.isEmpty() && !dataItem->isUserNew)
447 return false;
448 dataItem->key = newValue;
449 dataItem->isUserNew = true;
450 return true;
451 case 1:
452 if (dataItem->value == newValue) {
453 dataItem->newValue.clear();
454 dataItem->isUserChanged = false;
455 } else {
456 dataItem->newValue = newValue;
457 dataItem->isUserChanged = true;
458 }
459 return true;
460 default:
461 return false;
462 }
463 }
464
flags(int column) const465 Qt::ItemFlags ConfigModelTreeItem::flags(int column) const
466 {
467 if (column < 0 || column >= 2)
468 return Qt::NoItemFlags;
469
470 QTC_ASSERT(dataItem, return Qt::NoItemFlags);
471
472 if (dataItem->isUnset)
473 return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
474
475 if (column == 1) {
476 if (dataItem->type == ConfigModel::DataItem::BOOLEAN)
477 return Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsSelectable;
478 else
479 return Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemIsSelectable;
480 } else {
481 Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
482 if (dataItem->isUserNew)
483 return flags |= Qt::ItemIsEditable;
484 return flags;
485 }
486 }
487
toolTip() const488 QString ConfigModelTreeItem::toolTip() const
489 {
490 QTC_ASSERT(dataItem, return QString());
491 QStringList tooltip(dataItem->description);
492 if (!dataItem->kitValue.isEmpty())
493 tooltip << QCoreApplication::translate("CMakeProjectManager", "Value requested by kit: %1").arg(dataItem->kitValue);
494 if (dataItem->inCMakeCache) {
495 if (dataItem->value != dataItem->newValue)
496 tooltip << QCoreApplication::translate("CMakeProjectManager", "Current CMake: %1").arg(dataItem->value);
497 } else {
498 tooltip << QCoreApplication::translate("CMakeProjectManager", "Not in CMakeCache.txt");
499 }
500 return tooltip.join("<br>");
501 }
502
currentValue() const503 QString ConfigModelTreeItem::currentValue() const
504 {
505 QTC_ASSERT(dataItem, return QString());
506 return dataItem->isUserChanged ? dataItem->newValue : dataItem->value;
507 }
508
509 } // namespace Internal
510 } // namespace CMakeProjectManager
511