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