1 /****************************************************************************
2 **
3 ** Copyright (C) 2021 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 "assetimportupdatetreemodel.h"
27 #include "assetimportupdatetreeitem.h"
28 
29 #include <QSize>
30 
31 namespace QmlDesigner {
32 namespace Internal {
33 
AssetImportUpdateTreeModel(QObject * parent)34 AssetImportUpdateTreeModel::AssetImportUpdateTreeModel(QObject *parent)
35     : QAbstractItemModel(parent)
36 {
37     m_rootItem = new AssetImportUpdateTreeItem {{}};
38 }
39 
~AssetImportUpdateTreeModel()40 AssetImportUpdateTreeModel::~AssetImportUpdateTreeModel()
41 {
42     delete m_rootItem;
43 }
44 
flags(const QModelIndex & idx) const45 Qt::ItemFlags AssetImportUpdateTreeModel::flags(const QModelIndex &idx) const
46 {
47     Qt::ItemFlags flags = QAbstractItemModel::flags(idx);
48 
49     if (idx.isValid())
50         flags |= Qt::ItemIsUserCheckable;
51 
52     return flags;
53 }
54 
index(int row,int column,const QModelIndex & parent) const55 QModelIndex AssetImportUpdateTreeModel::index(int row, int column,
56                                               const QModelIndex &parent) const
57 {
58     if (!hasIndex(row, column, parent))
59         return QModelIndex();
60 
61     const AssetImportUpdateTreeItem *parentItem;
62 
63     parentItem = parent.isValid() ? treeItemAtIndex(parent) : m_rootItem;
64 
65     const AssetImportUpdateTreeItem *childItem = parentItem->childAt(row);
66     if (childItem)
67         return createIndex(row, column, const_cast<AssetImportUpdateTreeItem *>(childItem));
68     else
69         return QModelIndex();
70 }
71 
index(AssetImportUpdateTreeItem * item) const72 QModelIndex AssetImportUpdateTreeModel::index(AssetImportUpdateTreeItem *item) const
73 {
74     return createIndex(item->rowOfItem(), 0, item);
75 }
76 
data(const AssetImportUpdateTreeItem * row,int role) const77 QVariant AssetImportUpdateTreeModel::data(const AssetImportUpdateTreeItem *row, int role) const
78 {
79     if (role == Qt::DisplayRole)
80         return row->fileInfo().fileName();
81     if (role == Qt::CheckStateRole)
82         return row->checkState();
83     if (role == Qt::ToolTipRole)
84         return row->fileInfo().absoluteFilePath();
85     return {};
86 }
87 
parent(const QModelIndex & idx) const88 QModelIndex AssetImportUpdateTreeModel::parent(const QModelIndex &idx) const
89 {
90     if (!idx.isValid())
91         return QModelIndex();
92 
93     const AssetImportUpdateTreeItem *childItem = treeItemAtIndex(idx);
94     const AssetImportUpdateTreeItem *parentItem = childItem->parent();
95 
96     if (parentItem == m_rootItem)
97         return QModelIndex();
98 
99     return createIndex(parentItem->rowOfItem(), 0, const_cast<AssetImportUpdateTreeItem *>(parentItem));
100 }
101 
rowCount(const QModelIndex & parent) const102 int AssetImportUpdateTreeModel::rowCount(const QModelIndex &parent) const
103 {
104     if (parent.column() > 0)
105         return 0;
106 
107     return parent.isValid() ? treeItemAtIndex(parent)->childCount()
108                             : m_rootItem->childCount();
109 }
110 
columnCount(const QModelIndex &) const111 int AssetImportUpdateTreeModel::columnCount(const QModelIndex &) const
112 {
113     return 1;
114 }
115 
treeItemAtIndex(const QModelIndex & idx)116 AssetImportUpdateTreeItem *AssetImportUpdateTreeModel::treeItemAtIndex(const QModelIndex &idx)
117 {
118     return static_cast<AssetImportUpdateTreeItem*>(idx.internalPointer());
119 }
120 
data(const QModelIndex & idx,int role) const121 QVariant AssetImportUpdateTreeModel::data(const QModelIndex &idx, int role) const
122 {
123     if (!idx.isValid())
124         return {};
125 
126     if (role == Qt::SizeHintRole)
127         return QSize(0, 20);
128 
129     return data(treeItemAtIndex(idx), role);
130 }
131 
setData(const QModelIndex & idx,const QVariant & value,int role)132 bool AssetImportUpdateTreeModel::setData(const QModelIndex &idx, const QVariant &value, int role)
133 {
134     if (role == Qt::CheckStateRole) {
135         auto checkState = static_cast<Qt::CheckState>(value.toInt());
136         return setCheckState(idx, checkState);
137     }
138     return QAbstractItemModel::setData(idx, value, role);
139 }
140 
setCheckState(const QModelIndex & idx,Qt::CheckState checkState,bool firstCall)141 bool AssetImportUpdateTreeModel::setCheckState(const QModelIndex &idx, Qt::CheckState checkState,
142                                                bool firstCall)
143 {
144     AssetImportUpdateTreeItem *item = treeItemAtIndex(idx);
145     if (item->checkState() == checkState)
146         return false;
147     item->setCheckState(checkState);
148     if (firstCall) {
149         emit dataChanged(idx, idx);
150         // check parents
151         AssetImportUpdateTreeItem *parent = item->parent();
152         QModelIndex parentIdx = idx.parent();
153         while (parent) {
154             bool hasChecked = false;
155             bool hasUnchecked = false;
156             for (const auto child : parent->children()) {
157                 if (child->checkState() == Qt::Checked) {
158                     hasChecked = true;
159                 } else if (child->checkState() == Qt::Unchecked) {
160                     hasUnchecked = true;
161                 } else if (child->checkState() == Qt::PartiallyChecked) {
162                     hasChecked = true;
163                     hasUnchecked = true;
164                 }
165             }
166             if (hasChecked && hasUnchecked)
167                 parent->setCheckState(Qt::PartiallyChecked);
168             else if (hasChecked)
169                 parent->setCheckState(Qt::Checked);
170             else
171                 parent->setCheckState(Qt::Unchecked);
172             emit dataChanged(parentIdx, parentIdx);
173             parent = parent->parent();
174             parentIdx = parentIdx.parent();
175         }
176     }
177     // check children
178     if (int children = item->childCount()) {
179         for (int i = 0; i < children; ++i)
180             setCheckState(index(i, 0, idx), checkState, false);
181         emit dataChanged(index(0, 0, idx), index(children - 1, 0, idx));
182     }
183     return true;
184 }
185 
createItems(const QList<QFileInfo> & infos,const QSet<QString> & preselectedFiles)186 void AssetImportUpdateTreeModel::createItems(const QList<QFileInfo> &infos,
187                                              const QSet<QString> &preselectedFiles)
188 {
189     beginResetModel();
190     if (!infos.isEmpty()) {
191         QHash<QString, AssetImportUpdateTreeItem *> dirItems;
192         for (const auto &info : infos) {
193             auto parent = dirItems.value(info.absolutePath());
194             if (!parent)
195                 parent = m_rootItem;
196             auto item = new AssetImportUpdateTreeItem(info, parent);
197             if (info.isDir()) {
198                 dirItems.insert(info.absoluteFilePath(), item);
199             } else {
200                 m_fileItems.append(item);
201                 if (preselectedFiles.contains(info.absoluteFilePath()))
202                     item->setCheckState(Qt::Checked);
203             }
204         }
205         // Remove dir items that have no children from the model
206         for (auto dirItem : qAsConst(dirItems)) {
207             if (dirItem->childCount() == 0)
208                 delete dirItem;
209         }
210         std::function<Qt::CheckState (AssetImportUpdateTreeItem *)> updateDirCheckStatesRecursive;
211         updateDirCheckStatesRecursive = [&](AssetImportUpdateTreeItem *item) -> Qt::CheckState {
212             bool hasChecked = false;
213             bool hasUnchecked = false;
214             for (const auto child : item->children()) {
215                 Qt::CheckState childState = child->childCount() > 0
216                         ? updateDirCheckStatesRecursive(child)
217                         : child->checkState();
218                 if (childState == Qt::Checked) {
219                     hasChecked = true;
220                 } else if (childState == Qt::Unchecked) {
221                     hasUnchecked = true;
222                 } else {
223                     hasChecked = true;
224                     hasUnchecked = true;
225                     break;
226                 }
227             }
228             Qt::CheckState retval = Qt::Unchecked;
229             if (hasChecked && hasUnchecked)
230                 retval = Qt::PartiallyChecked;
231             else if (hasChecked)
232                 retval = Qt::Checked;
233             item->setCheckState(retval);
234             return retval;
235         };
236         m_rootItem->setCheckState(updateDirCheckStatesRecursive(m_rootItem));
237     }
238     endResetModel();
239 }
240 
checkedFiles() const241 QStringList AssetImportUpdateTreeModel::checkedFiles() const
242 {
243     QStringList retList;
244 
245     for (const auto item : qAsConst(m_fileItems)) {
246         if (item->checkState() == Qt::Checked)
247             retList.append(item->fileInfo().absoluteFilePath());
248     }
249 
250     return retList;
251 }
252 
clear()253 void AssetImportUpdateTreeModel::clear()
254 {
255     beginResetModel();
256     m_fileItems.clear();
257     m_rootItem->clear(); // Deletes all children
258     endResetModel();
259 }
260 
261 } // namespace Internal
262 } // namespace QmlDesigner
263