/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "itemlibrarymodel.h" #include "itemlibrarycategoriesmodel.h" #include "itemlibraryimport.h" #include "itemlibrarycategory.h" #include "itemlibraryitem.h" #include "itemlibraryinfo.h" #include #include #include #include #include #include #include "qmldesignerplugin.h" #include #include #include #include #include #include #include namespace QmlDesigner { // sectionName can be an import url or a category name void ItemLibraryModel::saveExpandedState(bool expanded, const QString §ionName) { expandedStateHash.insert(sectionName, expanded); } bool ItemLibraryModel::loadExpandedState(const QString §ionName) { return expandedStateHash.value(sectionName, true); } void ItemLibraryModel::expandAll() { int i = 0; for (const QPointer &import : std::as_const(m_importList)) { if (!import->importExpanded()) { import->setImportExpanded(); emit dataChanged(index(i), index(i), {m_roleNames.key("importExpanded")}); saveExpandedState(true, import->importUrl()); } import->expandCategories(true); ++i; } } void ItemLibraryModel::collapseAll() { int i = 0; for (const QPointer &import : std::as_const(m_importList)) { if (import->hasCategories() && import->importExpanded()) { import->setImportExpanded(false); emit dataChanged(index(i), index(i), {m_roleNames.key("importExpanded")}); saveExpandedState(false, import->importUrl()); } ++i; } } void ItemLibraryModel::setFlowMode(bool b) { m_flowMode = b; bool changed; updateVisibility(&changed); } ItemLibraryModel::ItemLibraryModel(QObject *parent) : QAbstractListModel(parent) { addRoleNames(); } ItemLibraryModel::~ItemLibraryModel() { clearSections(); } int ItemLibraryModel::rowCount(const QModelIndex & /*parent*/) const { return m_importList.count(); } QVariant ItemLibraryModel::data(const QModelIndex &index, int role) const { if (!index.isValid() || index.row() >= m_importList.count()) return {}; if (m_roleNames.contains(role)) { QVariant value = m_importList.at(index.row())->property(m_roleNames.value(role)); auto model = qobject_cast(value.value()); if (model) return QVariant::fromValue(model); return value; } qWarning() << Q_FUNC_INFO << "invalid role requested"; return {}; } bool ItemLibraryModel::setData(const QModelIndex &index, const QVariant &value, int role) { // currently only importExpanded property is updatable if (index.isValid() && m_roleNames.contains(role)) { QVariant currValue = m_importList.at(index.row())->property(m_roleNames.value(role)); if (currValue != value) { m_importList[index.row()]->setProperty(m_roleNames.value(role), value); if (m_roleNames.value(role) == "importExpanded") saveExpandedState(value.toBool(), m_importList[index.row()]->importUrl()); emit dataChanged(index, index, {role}); return true; } } return false; } QHash ItemLibraryModel::roleNames() const { return m_roleNames; } QString ItemLibraryModel::searchText() const { return m_searchText; } void ItemLibraryModel::setSearchText(const QString &searchText) { QString lowerSearchText = searchText.toLower(); if (m_searchText != lowerSearchText) { m_searchText = lowerSearchText; bool changed = false; updateVisibility(&changed); } } Import ItemLibraryModel::entryToImport(const ItemLibraryEntry &entry) { if (entry.majorVersion() == -1 && entry.minorVersion() == -1) return Import::createFileImport(entry.requiredImport()); return Import::createLibraryImport(entry.requiredImport(), QString::number(entry.majorVersion()) + QLatin1Char('.') + QString::number(entry.minorVersion())); } void ItemLibraryModel::update(ItemLibraryInfo *itemLibraryInfo, Model *model) { if (!model) return; beginResetModel(); clearSections(); Utils::FilePath qmlFileName = QmlDesignerPlugin::instance()->currentDesignDocument()->fileName(); ProjectExplorer::Project *project = ProjectExplorer::SessionManager::projectForFile(qmlFileName); QString projectName = project ? project->displayName() : ""; // create import sections for (const Import &import : model->imports()) { if (import.isLibraryImport() && import.url() != projectName) { ItemLibraryImport *itemLibImport = new ItemLibraryImport(import, this); m_importList.append(itemLibImport); itemLibImport->setImportExpanded(loadExpandedState(import.url())); } } const QList itemLibEntries = itemLibraryInfo->entries(); for (const ItemLibraryEntry &entry : itemLibEntries) { NodeMetaInfo metaInfo = model->metaInfo(entry.typeName()); bool valid = metaInfo.isValid() && metaInfo.majorVersion() == entry.majorVersion(); bool isItem = valid && metaInfo.isSubclassOf("QtQuick.Item"); bool forceVisibility = valid && NodeHints::fromItemLibraryEntry(entry).visibleInLibrary(); if (m_flowMode && metaInfo.isValid()) { isItem = metaInfo.isSubclassOf("FlowView.FlowItem") || metaInfo.isSubclassOf("FlowView.FlowWildcard") || metaInfo.isSubclassOf("FlowView.FlowDecision"); forceVisibility = isItem; } bool blocked = false; const DesignerMcuManager &mcuManager = DesignerMcuManager::instance(); if (mcuManager.isMCUProject()) { const QSet blockTypes = mcuManager.bannedItems(); if (blockTypes.contains(QString::fromUtf8(entry.typeName()))) blocked = true; } Import import = entryToImport(entry); bool hasImport = model->hasImport(import, true, true); bool isImportPossible = false; if (!hasImport) isImportPossible = model->isImportPossible(import, true, true); bool isUsable = (valid && (isItem || forceVisibility)) && (entry.requiredImport().isEmpty() || hasImport); if (!blocked && (isUsable || isImportPossible)) { ItemLibraryImport *importSection = nullptr; QString catName = entry.category(); if (isUsable) { if (catName == ItemLibraryImport::userComponentsTitle()) { // create an import section for user components importSection = importByUrl(ItemLibraryImport::userComponentsTitle()); if (!importSection) { importSection = new ItemLibraryImport( {}, this, ItemLibraryImport::SectionType::User); m_importList.append(importSection); importSection->setImportExpanded(loadExpandedState(catName)); } } else { if (catName.startsWith("Qt Quick - ")) catName = catName.mid(11); // remove "Qt Quick - " importSection = importByUrl(entry.requiredImport()); } } else { catName = ItemLibraryImport::unimportedComponentsTitle(); importSection = importByUrl(catName); if (!importSection) { importSection = new ItemLibraryImport( {}, this, ItemLibraryImport::SectionType::Unimported); m_importList.append(importSection); importSection->setImportExpanded(loadExpandedState(catName)); } } if (!importSection) { qWarning() << __FUNCTION__ << "No import section found! skipping entry: " << entry.name(); continue; } // get or create category section ItemLibraryCategory *categorySection = importSection->getCategorySection(catName); if (!categorySection) { categorySection = new ItemLibraryCategory(catName, importSection); importSection->addCategory(categorySection); if (importSection->sectionType() == ItemLibraryImport::SectionType::Default) categorySection->setExpanded(loadExpandedState(categorySection->categoryName())); } // create item auto item = new ItemLibraryItem(entry, isUsable, categorySection); categorySection->addItem(item); } } sortSections(); bool changed = false; updateVisibility(&changed); endResetModel(); } QMimeData *ItemLibraryModel::getMimeData(const ItemLibraryEntry &itemLibraryEntry) { auto mimeData = new QMimeData(); QByteArray data; QDataStream stream(&data, QIODevice::WriteOnly); stream << itemLibraryEntry; mimeData->setData(QStringLiteral("application/vnd.bauhaus.itemlibraryinfo"), data); mimeData->removeFormat(QStringLiteral("text/plain")); return mimeData; } void ItemLibraryModel::clearSections() { qDeleteAll(m_importList); m_importList.clear(); } void ItemLibraryModel::registerQmlTypes() { qmlRegisterAnonymousType("ItemLibraryModel", 1); } ItemLibraryImport *ItemLibraryModel::importByUrl(const QString &importUrl) const { for (ItemLibraryImport *itemLibraryImport : std::as_const(m_importList)) { if (itemLibraryImport->importUrl() == importUrl || (importUrl.isEmpty() && itemLibraryImport->importUrl() == "QtQuick") || (importUrl == ItemLibraryImport::userComponentsTitle() && itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::User) || (importUrl == ItemLibraryImport::unimportedComponentsTitle() && itemLibraryImport->sectionType() == ItemLibraryImport::SectionType::Unimported)) { return itemLibraryImport; } } return nullptr; } void ItemLibraryModel::updateUsedImports(const QList &usedImports) { // imports in the excludeList are not marked used and thus can always be removed even when in use. const QList excludeList = {"SimulinkConnector"}; for (ItemLibraryImport *importSection : std::as_const(m_importList)) { if (!excludeList.contains(importSection->importUrl())) importSection->setImportUsed(usedImports.contains(importSection->importEntry())); } } void ItemLibraryModel::updateVisibility(bool *changed) { for (ItemLibraryImport *import : std::as_const(m_importList)) { bool categoryChanged = false; bool hasVisibleItems = import->updateCategoryVisibility(m_searchText, &categoryChanged); *changed |= categoryChanged; if (import->sectionType() == ItemLibraryImport::SectionType::Unimported) *changed |= import->setVisible(!m_searchText.isEmpty()); // expand import if it has an item matching search criteria if (hasVisibleItems && !import->importExpanded()) import->setImportExpanded(); } if (changed) { beginResetModel(); endResetModel(); } } void ItemLibraryModel::addRoleNames() { int role = 0; const QMetaObject meta = ItemLibraryImport::staticMetaObject; for (int i = meta.propertyOffset(); i < meta.propertyCount(); ++i) m_roleNames.insert(role++, meta.property(i).name()); } void ItemLibraryModel::sortSections() { auto sectionSort = [](ItemLibraryImport *first, ItemLibraryImport *second) { return QString::localeAwareCompare(first->sortingName(), second->sortingName()) < 0; }; std::sort(m_importList.begin(), m_importList.end(), sectionSort); for (ItemLibraryImport *itemLibImport : qAsConst(m_importList)) itemLibImport->sortCategorySections(); } } // namespace QmlDesigner