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 "itemlibrarymodel.h"
27 #include "itemlibrarycategoriesmodel.h"
28 #include "itemlibraryimport.h"
29 #include "itemlibrarycategory.h"
30 #include "itemlibraryitem.h"
31 #include "itemlibraryinfo.h"
32
33 #include <designermcumanager.h>
34 #include <model.h>
35 #include <nodehints.h>
36 #include <nodemetainfo.h>
37 #include <projectexplorer/project.h>
38 #include <projectexplorer/session.h>
39 #include "qmldesignerplugin.h"
40 #include <utils/algorithm.h>
41 #include <utils/qtcassert.h>
42
43 #include <QIODevice>
44 #include <QLoggingCategory>
45 #include <QMetaProperty>
46 #include <QMimeData>
47 #include <QVariant>
48
49 namespace QmlDesigner {
50
51 // sectionName can be an import url or a category name
saveExpandedState(bool expanded,const QString & sectionName)52 void ItemLibraryModel::saveExpandedState(bool expanded, const QString §ionName)
53 {
54 expandedStateHash.insert(sectionName, expanded);
55 }
56
loadExpandedState(const QString & sectionName)57 bool ItemLibraryModel::loadExpandedState(const QString §ionName)
58 {
59 return expandedStateHash.value(sectionName, true);
60 }
61
saveCategoryVisibleState(bool isVisible,const QString & categoryName,const QString & importName)62 void ItemLibraryModel::saveCategoryVisibleState(bool isVisible, const QString &categoryName, const
63 QString &importName)
64 {
65 categoryVisibleStateHash.insert(categoryName + '_' + importName, isVisible);
66 }
67
loadCategoryVisibleState(const QString & categoryName,const QString & importName)68 bool ItemLibraryModel::loadCategoryVisibleState(const QString &categoryName, const QString &importName)
69 {
70 return categoryVisibleStateHash.value(categoryName + '_' + importName, true);
71 }
72
showHiddenCategories()73 void ItemLibraryModel::showHiddenCategories()
74 {
75 for (const QPointer<ItemLibraryImport> &import : std::as_const(m_importList)) {
76 if (import->hasCategories())
77 import->showAllCategories(true);
78 }
79
80 categoryVisibleStateHash.clear();
81 }
82
getIsAnyCategoryHidden() const83 bool ItemLibraryModel::getIsAnyCategoryHidden() const
84 {
85 for (const bool &catState : std::as_const(categoryVisibleStateHash)) {
86 if (!catState)
87 return true;
88 }
89
90 return false;
91 }
92
isAnyCategoryHidden() const93 bool ItemLibraryModel::isAnyCategoryHidden() const
94 {
95 return m_isAnyCategoryHidden;
96 }
97
setIsAnyCategoryHidden(bool state)98 void ItemLibraryModel::setIsAnyCategoryHidden(bool state)
99 {
100 if (state != m_isAnyCategoryHidden) {
101 m_isAnyCategoryHidden = state;
102 emit isAnyCategoryHiddenChanged();
103 }
104 }
105
expandAll()106 void ItemLibraryModel::expandAll()
107 {
108 int i = 0;
109 for (const QPointer<ItemLibraryImport> &import : std::as_const(m_importList)) {
110 if (!import->importExpanded()) {
111 import->setImportExpanded();
112 emit dataChanged(index(i), index(i), {m_roleNames.key("importExpanded")});
113 saveExpandedState(true, import->importUrl());
114 }
115 import->expandCategories(true);
116 ++i;
117 }
118 }
119
collapseAll()120 void ItemLibraryModel::collapseAll()
121 {
122 int i = 0;
123 for (const QPointer<ItemLibraryImport> &import : std::as_const(m_importList)) {
124 if (import->hasCategories() && import->importExpanded()) {
125 import->setImportExpanded(false);
126 emit dataChanged(index(i), index(i), {m_roleNames.key("importExpanded")});
127 saveExpandedState(false, import->importUrl());
128 }
129 ++i;
130 }
131 }
132
setFlowMode(bool b)133 void ItemLibraryModel::setFlowMode(bool b)
134 {
135 m_flowMode = b;
136 bool changed;
137 updateVisibility(&changed);
138 }
139
ItemLibraryModel(QObject * parent)140 ItemLibraryModel::ItemLibraryModel(QObject *parent)
141 : QAbstractListModel(parent)
142 {
143 addRoleNames();
144 }
145
~ItemLibraryModel()146 ItemLibraryModel::~ItemLibraryModel()
147 {
148 clearSections();
149 }
150
rowCount(const QModelIndex &) const151 int ItemLibraryModel::rowCount(const QModelIndex & /*parent*/) const
152 {
153 return m_importList.count();
154 }
155
data(const QModelIndex & index,int role) const156 QVariant ItemLibraryModel::data(const QModelIndex &index, int role) const
157 {
158 if (!index.isValid() || index.row() >= m_importList.count())
159 return {};
160
161 if (m_roleNames.contains(role)) {
162 QVariant value = m_importList.at(index.row())->property(m_roleNames.value(role));
163
164 auto model = qobject_cast<ItemLibraryCategoriesModel *>(value.value<QObject *>());
165 if (model)
166 return QVariant::fromValue(model);
167
168 return value;
169 }
170
171 qWarning() << Q_FUNC_INFO << "invalid role requested";
172
173 return {};
174 }
175
setData(const QModelIndex & index,const QVariant & value,int role)176 bool ItemLibraryModel::setData(const QModelIndex &index, const QVariant &value, int role)
177 {
178 // currently only importExpanded property is updatable
179 if (index.isValid() && m_roleNames.contains(role)) {
180 QVariant currValue = m_importList.at(index.row())->property(m_roleNames.value(role));
181 if (currValue != value) {
182 m_importList[index.row()]->setProperty(m_roleNames.value(role), value);
183 if (m_roleNames.value(role) == "importExpanded")
184 saveExpandedState(value.toBool(), m_importList[index.row()]->importUrl());
185 emit dataChanged(index, index, {role});
186 return true;
187 }
188 }
189 return false;
190 }
191
roleNames() const192 QHash<int, QByteArray> ItemLibraryModel::roleNames() const
193 {
194 return m_roleNames;
195 }
196
searchText() const197 QString ItemLibraryModel::searchText() const
198 {
199 return m_searchText;
200 }
201
setSearchText(const QString & searchText)202 void ItemLibraryModel::setSearchText(const QString &searchText)
203 {
204 QString lowerSearchText = searchText.toLower();
205
206 if (m_searchText != lowerSearchText) {
207 m_searchText = lowerSearchText;
208
209 bool changed = false;
210 updateVisibility(&changed);
211 }
212 }
213
entryToImport(const ItemLibraryEntry & entry)214 Import ItemLibraryModel::entryToImport(const ItemLibraryEntry &entry)
215 {
216 if (entry.majorVersion() == -1 && entry.minorVersion() == -1)
217 return Import::createFileImport(entry.requiredImport());
218
219 return Import::createLibraryImport(entry.requiredImport(), QString::number(entry.majorVersion()) + QLatin1Char('.') +
220 QString::number(entry.minorVersion()));
221
222 }
223
224 // Returns true if first import version is higher or equal to second import version
compareVersions(const QString & version1,const QString & version2)225 static bool compareVersions(const QString &version1, const QString &version2)
226 {
227 if (version2.isEmpty() || version1 == version2)
228 return true;
229 const QStringList version1List = version1.split(QLatin1Char('.'));
230 const QStringList version2List = version2.split(QLatin1Char('.'));
231 if (version1List.count() == 2 && version2List.count() == 2) {
232 int major1 = version1List.constFirst().toInt();
233 int major2 = version2List.constFirst().toInt();
234 if (major1 > major2) {
235 return true;
236 } else if (major1 == major2) {
237 int minor1 = version1List.constLast().toInt();
238 int minor2 = version2List.constLast().toInt();
239 if (minor1 >= minor2)
240 return true;
241 }
242 }
243 return false;
244 }
245
update(ItemLibraryInfo * itemLibraryInfo,Model * model)246 void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model)
247 {
248 if (!model)
249 return;
250
251 beginResetModel();
252 clearSections();
253
254 Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName();
255 ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName);
256 QString projectName = project ? project->displayName() : "";
257
258 // create import sections
259 const QList<Import> usedImports = model->usedImports();
260 QHash<QString, ItemLibraryImport *> importHash;
261 for (const Import &import : model->imports()) {
262 if (import.url() != projectName) {
263 bool addNew = true;
264 bool isQuick3DAsset = import.url().startsWith("Quick3DAssets.");
265 QString importUrl = import.url();
266 if (isQuick3DAsset)
267 importUrl = ItemLibraryImport::quick3DAssetsTitle();
268 else if (import.isFileImport())
269 importUrl = import.toString(true, true).remove("\"");
270
271 ItemLibraryImport *oldImport = importHash.value(importUrl);
272 if (oldImport && oldImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets
273 && isQuick3DAsset) {
274 addNew = false; // add only 1 Quick3DAssets import section
275 } else if (oldImport && oldImport->importEntry().url() == import.url()) {
276 // Retain the higher version if multiples exist
277 if (compareVersions(oldImport->importEntry().version(), import.version()))
278 addNew = false;
279 else
280 delete oldImport;
281 }
282
283 if (addNew) {
284 auto sectionType = isQuick3DAsset ? ItemLibraryImport::SectionType::Quick3DAssets
285 : ItemLibraryImport::SectionType::Default;
286 ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this, sectionType);
287 itemLibImport->setImportUsed(usedImports.contains(import));
288 importHash.insert(importUrl, itemLibImport);
289 }
290 }
291 }
292
293 for (const auto itemLibImport : qAsConst(importHash)) {
294 m_importList.append(itemLibImport);
295 itemLibImport->setImportExpanded(loadExpandedState(itemLibImport->importUrl()));
296 }
297
298 const QList<ItemLibraryEntry> itemLibEntries = itemLibraryInfo->entries();
299 for (const ItemLibraryEntry &entry : itemLibEntries) {
300 NodeMetaInfo metaInfo = model->metaInfo(entry.typeName());
301
302 bool valid = metaInfo.isValid()
303 && (metaInfo.majorVersion() >= entry.majorVersion()
304 || metaInfo.majorVersion() < 0);
305
306 bool isItem = valid && metaInfo.isSubclassOf("QtQuick.Item");
307 bool forceVisibility = valid && NodeHints::fromItemLibraryEntry(entry).visibleInLibrary();
308
309 if (m_flowMode && metaInfo.isValid()) {
310 isItem = metaInfo.isSubclassOf("FlowView.FlowItem")
311 || metaInfo.isSubclassOf("FlowView.FlowWildcard")
312 || metaInfo.isSubclassOf("FlowView.FlowDecision");
313 forceVisibility = isItem;
314 }
315
316 bool blocked = false;
317 const DesignerMcuManager &mcuManager = DesignerMcuManager::instance();
318 if (mcuManager.isMCUProject()) {
319 const QSet<QString> blockTypes = mcuManager.bannedItems();
320
321 if (blockTypes.contains(QString::fromUtf8(entry.typeName())))
322 blocked = true;
323 }
324
325 Import import = entryToImport(entry);
326 bool hasImport = model->hasImport(import, true, true);
327 bool isImportPossible = false;
328 if (!hasImport)
329 isImportPossible = model->isImportPossible(import, true, true);
330 bool isUsable = (valid && (isItem || forceVisibility))
331 && (entry.requiredImport().isEmpty() || hasImport);
332 if (!blocked && (isUsable || isImportPossible)) {
333 ItemLibraryImport *importSection = nullptr;
334 QString catName = entry.category();
335 if (isUsable) {
336 if (catName == ItemLibraryImport::userComponentsTitle()) {
337 if (entry.requiredImport().isEmpty()) { // user components
338 importSection = importHash[ItemLibraryImport::userComponentsTitle()];
339 if (!importSection) {
340 importSection = new ItemLibraryImport(
341 {}, this, ItemLibraryImport::SectionType::User);
342 m_importList.append(importSection);
343 importHash.insert(ItemLibraryImport::userComponentsTitle(), importSection);
344 importSection->setImportExpanded(loadExpandedState(catName));
345 }
346 } else { // directory import
347 importSection = importHash[entry.requiredImport()];
348
349 }
350 } else if (catName == ItemLibraryImport::quick3DAssetsTitle()) {
351 importSection = importHash[ItemLibraryImport::quick3DAssetsTitle()];
352 } else {
353 if (catName.contains("Qt Quick - ")) {
354 QString sortingName = catName;
355 catName = catName.mid(11 + catName.indexOf("Qt Quick - ")); // remove "Qt Quick - " or "x.Qt Quick - "
356 categorySortingHash.insert(catName, sortingName);
357 }
358
359 importSection = importHash[entry.requiredImport().isEmpty() ? "QtQuick"
360 : entry.requiredImport()];
361 }
362 } else {
363 catName = ItemLibraryImport::unimportedComponentsTitle();
364 importSection = importHash[catName];
365 if (!importSection) {
366 importSection = new ItemLibraryImport(
367 {}, this, ItemLibraryImport::SectionType::Unimported);
368 m_importList.append(importSection);
369 importHash.insert(ItemLibraryImport::unimportedComponentsTitle(), importSection);
370 importSection->setImportExpanded(loadExpandedState(catName));
371 }
372 }
373
374 if (!importSection) {
375 qWarning() << __FUNCTION__ << "No import section found! skipping entry: " << entry.name();
376 continue;
377 }
378
379 // get or create category section
380 ItemLibraryCategory *categorySection = importSection->getCategorySection(catName);
381 if (!categorySection) {
382 categorySection = new ItemLibraryCategory(catName, importSection);
383 importSection->addCategory(categorySection);
384 }
385 if (importSection->sectionType() == ItemLibraryImport::SectionType::Default
386 && !importSection->hasSingleCategory()) {
387 categorySection->setExpanded(loadExpandedState(categorySection->categoryName()));
388 }
389
390 // create item
391 auto item = new ItemLibraryItem(entry, isUsable, categorySection);
392 categorySection->addItem(item);
393 }
394 }
395
396 sortSections();
397 bool changed = false;
398 updateVisibility(&changed);
399 endResetModel();
400 }
401
getMimeData(const ItemLibraryEntry & itemLibraryEntry)402 QMimeData *ItemLibraryModel::getMimeData(const ItemLibraryEntry &itemLibraryEntry)
403 {
404 auto mimeData = new QMimeData();
405
406 QByteArray data;
407 QDataStream stream(&data, QIODevice::WriteOnly);
408 stream << itemLibraryEntry;
409 mimeData->setData(QStringLiteral("application/vnd.bauhaus.itemlibraryinfo"), data);
410
411 mimeData->removeFormat(QStringLiteral("text/plain"));
412
413 return mimeData;
414 }
415
clearSections()416 void ItemLibraryModel::clearSections()
417 {
418 qDeleteAll(m_importList);
419 m_importList.clear();
420 }
421
registerQmlTypes()422 void ItemLibraryModel::registerQmlTypes()
423 {
424 qmlRegisterAnonymousType<QmlDesigner::ItemLibraryModel>("ItemLibraryModel", 1);
425 }
426
importByUrl(const QString & importUrl) const427 ItemLibraryImport *ItemLibraryModel::importByUrl(const QString &importUrl) const
428 {
429 for (ItemLibraryImport *itemLibraryImport : std::as_const(m_importList)) {
430 if (itemLibraryImport->importUrl() == importUrl
431 || (importUrl.isEmpty() && itemLibraryImport->importUrl() == "QtQuick")
432 || (importUrl == ItemLibraryImport::userComponentsTitle()
433 && itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::User)
434 || (importUrl == ItemLibraryImport::quick3DAssetsTitle()
435 && itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::Quick3DAssets)
436 || (importUrl == ItemLibraryImport::unimportedComponentsTitle()
437 && itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::Unimported)) {
438 return itemLibraryImport;
439 }
440 }
441
442 return nullptr;
443 }
444
updateUsedImports(const QList<Import> & usedImports)445 void ItemLibraryModel::updateUsedImports(const QList<Import> &usedImports)
446 {
447 // imports in the excludeList are not marked used and thus can always be removed even when in use.
448 const QList<QString> excludeList = {"SimulinkConnector"};
449
450 for (ItemLibraryImport *importSection : std::as_const(m_importList)) {
451 if (!excludeList.contains(importSection->importUrl()))
452 importSection->setImportUsed(usedImports.contains(importSection->importEntry()));
453 }
454 }
455
updateVisibility(bool * changed)456 void ItemLibraryModel::updateVisibility(bool *changed)
457 {
458 for (ItemLibraryImport *import : std::as_const(m_importList)) {
459 bool categoryChanged = false;
460 bool hasVisibleItems = import->updateCategoryVisibility(m_searchText, &categoryChanged);
461 *changed |= categoryChanged;
462
463 if (import->sectionType() == ItemLibraryImport::SectionType::Unimported)
464 *changed |= import->setVisible(!m_searchText.isEmpty());
465
466 // expand import if it has an item matching search criteria
467 if (!m_searchText.isEmpty() && hasVisibleItems && !import->importExpanded())
468 import->setImportExpanded();
469 }
470
471 if (changed) {
472 beginResetModel();
473 endResetModel();
474 }
475 }
476
addRoleNames()477 void ItemLibraryModel::addRoleNames()
478 {
479 int role = 0;
480 const QMetaObject meta = ItemLibraryImport::staticMetaObject;
481 for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i)
482 m_roleNames.insert(role++, meta.property(i).name());
483 }
484
sortSections()485 void ItemLibraryModel::sortSections()
486 {
487 auto sectionSort = [](ItemLibraryImport *first, ItemLibraryImport *second) {
488 return QString::localeAwareCompare(first->sortingName(), second->sortingName()) < 0;
489 };
490
491 std::sort(m_importList.begin(), m_importList.end(), sectionSort);
492
493 for (ItemLibraryImport *itemLibImport : qAsConst(m_importList))
494 itemLibImport->sortCategorySections();
495 }
496
497 } // namespace QmlDesigner
498
499