diff --git a/src/plugins/qmldesigner/CMakeLists.txt b/src/plugins/qmldesigner/CMakeLists.txt index d08027197be..41e70cb1e95 100644 --- a/src/plugins/qmldesigner/CMakeLists.txt +++ b/src/plugins/qmldesigner/CMakeLists.txt @@ -300,6 +300,12 @@ extend_qtc_plugin(QmlDesigner extend_qtc_plugin(QmlDesigner SOURCES_PREFIX components/itemlibrary SOURCES + assetimportupdatedialog.cpp assetimportupdatedialog.h + assetimportupdatedialog.ui + assetimportupdatetreeitem.cpp assetimportupdatetreeitem.h + assetimportupdatetreeitemdelegate.cpp assetimportupdatetreeitemdelegate.h + assetimportupdatetreemodel.cpp assetimportupdatetreemodel.h + assetimportupdatetreeview.cpp assetimportupdatetreeview.h customfilesystemmodel.cpp customfilesystemmodel.h itemlibrary.qrc itemlibraryimageprovider.cpp itemlibraryimageprovider.h diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.cpp b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.cpp new file mode 100644 index 00000000000..bc420dc6385 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "assetimportupdatedialog.h" +#include "ui_assetimportupdatedialog.h" +#include "assetimportupdatetreeview.h" +#include "assetimportupdatetreemodel.h" + +#include +#include +#include +#include +#include + +namespace QmlDesigner { +namespace Internal { + +AssetImportUpdateDialog::AssetImportUpdateDialog( + const QString &importPath, const QSet &preSelectedFiles, + const QSet &hiddenEntries, QWidget *parent) + : QDialog(parent) + , ui(new Ui::AssetImportUpdateDialog) +{ + setModal(true); + ui->setupUi(this); + + connect(ui->buttonBox->button(QDialogButtonBox::Ok), &QPushButton::clicked, + this, &AssetImportUpdateDialog::accept); + connect(ui->buttonBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, + this, &AssetImportUpdateDialog::reject); + connect(ui->expandButton, &QPushButton::clicked, + this, &AssetImportUpdateDialog::expandAll); + connect(ui->collapseButton, &QPushButton::clicked, + this, &AssetImportUpdateDialog::collapseAll); + ui->buttonBox->button(QDialogButtonBox::Ok)->setDefault(true); + + QList infos; + infos.append(QFileInfo{importPath}); + QDirIterator it(importPath, {"*"}, QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot, + QDirIterator::Subdirectories); + while (it.hasNext()) { + it.next(); + const QString absFile = it.fileInfo().absoluteFilePath(); + if (!hiddenEntries.contains(absFile)) + infos.append(it.fileInfo()); + } + + ui->treeView->model()->createItems(infos, preSelectedFiles); + ui->treeView->expandAll(); +} + +AssetImportUpdateDialog::~AssetImportUpdateDialog() +{ + delete ui; +} + +QStringList AssetImportUpdateDialog::selectedFiles() const +{ + return ui->treeView->model()->checkedFiles(); +} + +void AssetImportUpdateDialog::collapseAll() +{ + ui->treeView->collapseAll(); +} + +void AssetImportUpdateDialog::expandAll() +{ + ui->treeView->expandAll(); +} + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.h b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.h new file mode 100644 index 00000000000..497fc6ca384 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.h @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ +#pragma once + +#include +#include +#include + +namespace QmlDesigner { +namespace Internal { + +namespace Ui { +class AssetImportUpdateDialog; +} + +class AssetImportUpdateDialog : public QDialog +{ + Q_OBJECT + +public: + explicit AssetImportUpdateDialog(const QString &importPath, + const QSet &preSelectedFiles, + const QSet &hiddenEntries, + QWidget *parent = nullptr); + ~AssetImportUpdateDialog(); + + QStringList selectedFiles() const; + +private: + void collapseAll(); + void expandAll(); + + Ui::AssetImportUpdateDialog *ui = nullptr; +}; + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.ui b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.ui new file mode 100644 index 00000000000..576987e5de0 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatedialog.ui @@ -0,0 +1,77 @@ + + + QmlDesigner::Internal::AssetImportUpdateDialog + + + + 0 + 0 + 472 + 360 + + + + Select Files to Update + + + + + + + + QAbstractItemView::NoSelection + + + + + + + + + Expand All + + + + + + + Collapse All + + + + + + + Qt::Horizontal + + + + 77 + 20 + + + + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + + + + AssetImportUpdateTreeView + QTreeView +
assetimportupdatetreeview.h
+
+
+ + +
diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitem.cpp b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitem.cpp new file mode 100644 index 00000000000..81f5a143924 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitem.cpp @@ -0,0 +1,87 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "assetimportupdatetreeitem.h" + +namespace QmlDesigner { +namespace Internal { + +AssetImportUpdateTreeItem::AssetImportUpdateTreeItem(const QFileInfo &info, + AssetImportUpdateTreeItem *parent) + : m_parent(parent) + , m_fileInfo(info) +{ + if (parent) + parent->appendChild(this); +} + +AssetImportUpdateTreeItem::~AssetImportUpdateTreeItem() +{ + if (m_parent) + m_parent->removeChild(this); + clear(); +} + +void AssetImportUpdateTreeItem::clear() +{ + qDeleteAll(m_children); + m_children.clear(); + m_fileInfo = {}; + m_parent = nullptr; +} + +int AssetImportUpdateTreeItem::childCount() const +{ + return m_children.count(); +} + +int AssetImportUpdateTreeItem::rowOfItem() const +{ + return m_parent ? m_parent->m_children.indexOf(const_cast(this)) + : 0; +} + +AssetImportUpdateTreeItem *AssetImportUpdateTreeItem::childAt(int index) const +{ + return m_children.at(index); +} + +AssetImportUpdateTreeItem *AssetImportUpdateTreeItem::parent() const +{ + return m_parent; +} + +void AssetImportUpdateTreeItem::removeChild(AssetImportUpdateTreeItem *item) +{ + m_children.removeOne(item); +} + +void AssetImportUpdateTreeItem::appendChild(AssetImportUpdateTreeItem *item) +{ + m_children.append(item); +} + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitem.h b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitem.h new file mode 100644 index 00000000000..ef944e2dece --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitem.h @@ -0,0 +1,65 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include + +namespace QmlDesigner { +namespace Internal { + +class AssetImportUpdateTreeItem +{ +public: + explicit AssetImportUpdateTreeItem(const QFileInfo &info, + AssetImportUpdateTreeItem *parent = nullptr); + virtual ~AssetImportUpdateTreeItem(); + + AssetImportUpdateTreeItem *parent() const; + AssetImportUpdateTreeItem *childAt(int index) const; + int childCount() const; + int rowOfItem() const; + void clear(); + + Qt::CheckState checkState() const { return m_checkState; } + void setCheckState(Qt::CheckState checkState) { m_checkState = checkState; } + const QFileInfo &fileInfo() const { return m_fileInfo; } + void setFileInfo(const QFileInfo &info) { m_fileInfo = info; } + void removeChild(AssetImportUpdateTreeItem *item); + const QList &children() const { return m_children; } + +private: + void appendChild(AssetImportUpdateTreeItem *item); + + AssetImportUpdateTreeItem *m_parent; + QList m_children; + Qt::CheckState m_checkState = Qt::Unchecked; + QFileInfo m_fileInfo; +}; + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitemdelegate.cpp b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitemdelegate.cpp new file mode 100644 index 00000000000..9a517f76b24 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitemdelegate.cpp @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "assetimportupdatetreeitemdelegate.h" +#include "assetimportupdatetreemodel.h" + +#include +#include + +namespace QmlDesigner { +namespace Internal { + +AssetImportUpdateTreeItemDelegate::AssetImportUpdateTreeItemDelegate(QObject *parent) + : QItemDelegate(parent) +{ +} + +AssetImportUpdateTreeItemDelegate::LayoutInfo AssetImportUpdateTreeItemDelegate::getLayoutInfo( + const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + LayoutInfo info; + info.option = setOptions(index, option); + + const bool checkable = (index.model()->flags(index) & Qt::ItemIsUserCheckable); + info.checkState = Qt::Unchecked; + if (checkable) { + QVariant checkStateData = index.data(Qt::CheckStateRole); + info.checkState = static_cast(checkStateData.toInt()); + info.checkRect = doCheck(info.option, info.option.rect, checkStateData); + } + + info.textRect = info.option.rect.adjusted(0, 0, info.checkRect.width(), 0); + + doLayout(info.option, &info.checkRect, &info.iconRect, &info.textRect, false); + + return info; +} + +void AssetImportUpdateTreeItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + painter->save(); + + const LayoutInfo info = getLayoutInfo(option, index); + + painter->setFont(info.option.font); + + drawBackground(painter, info.option, index); + drawText(painter, info.option, info.textRect, index); + drawCheck(painter, info.option, info.checkRect, info.checkState); + + painter->restore(); +} + +QSize AssetImportUpdateTreeItemDelegate::sizeHint(const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + const LayoutInfo info = getLayoutInfo(option, index); + const int height = index.data(Qt::SizeHintRole).value().height(); + // get text width, see QItemDelegatePrivate::displayRect + const QString text = index.data(Qt::DisplayRole).toString(); + const QRect textMaxRect(0, 0, INT_MAX / 256, height); + const QRect textLayoutRect = textRectangle(nullptr, textMaxRect, info.option.font, text); + const QRect textRect(info.textRect.x(), info.textRect.y(), textLayoutRect.width(), height); + const QRect layoutRect = info.checkRect | textRect; + return QSize(layoutRect.x(), layoutRect.y()) + layoutRect.size(); +} + +void AssetImportUpdateTreeItemDelegate::drawText(QPainter *painter, + const QStyleOptionViewItem &option, + const QRect &rect, + const QModelIndex &index) const +{ + const QString text = index.data(Qt::DisplayRole).toString(); + drawDisplay(painter, option, rect, text); +} + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitemdelegate.h b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitemdelegate.h new file mode 100644 index 00000000000..a066ebcfb41 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeitemdelegate.h @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#pragma once + +#include +#include + +namespace QmlDesigner { +namespace Internal { + +class AssetImportUpdateTreeItemDelegate : public QItemDelegate +{ +public: + AssetImportUpdateTreeItemDelegate(QObject *parent = nullptr); + void paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const override; + QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const override; +private: + struct LayoutInfo + { + QRect checkRect; + QRect textRect; + QRect iconRect; + Qt::CheckState checkState; + QStyleOptionViewItem option; + }; + + LayoutInfo getLayoutInfo(const QStyleOptionViewItem &option, const QModelIndex &index) const; + void drawText(QPainter *painter, const QStyleOptionViewItem &option, + const QRect &rect, const QModelIndex &index) const; +}; + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreemodel.cpp b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreemodel.cpp new file mode 100644 index 00000000000..fac0797d5ed --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreemodel.cpp @@ -0,0 +1,262 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "assetimportupdatetreemodel.h" +#include "assetimportupdatetreeitem.h" + +#include + +namespace QmlDesigner { +namespace Internal { + +AssetImportUpdateTreeModel::AssetImportUpdateTreeModel(QObject *parent) + : QAbstractItemModel(parent) +{ + m_rootItem = new AssetImportUpdateTreeItem {{}}; +} + +AssetImportUpdateTreeModel::~AssetImportUpdateTreeModel() +{ + delete m_rootItem; +} + +Qt::ItemFlags AssetImportUpdateTreeModel::flags(const QModelIndex &idx) const +{ + Qt::ItemFlags flags = QAbstractItemModel::flags(idx); + + if (idx.isValid()) + flags |= Qt::ItemIsUserCheckable; + + return flags; +} + +QModelIndex AssetImportUpdateTreeModel::index(int row, int column, + const QModelIndex &parent) const +{ + if (!hasIndex(row, column, parent)) + return QModelIndex(); + + const AssetImportUpdateTreeItem *parentItem; + + parentItem = parent.isValid() ? treeItemAtIndex(parent) : m_rootItem; + + const AssetImportUpdateTreeItem *childItem = parentItem->childAt(row); + if (childItem) + return createIndex(row, column, const_cast(childItem)); + else + return QModelIndex(); +} + +QModelIndex AssetImportUpdateTreeModel::index(AssetImportUpdateTreeItem *item) const +{ + return createIndex(item->rowOfItem(), 0, item); +} + +QVariant AssetImportUpdateTreeModel::data(const AssetImportUpdateTreeItem *row, int role) const +{ + if (role == Qt::DisplayRole) + return row->fileInfo().fileName(); + if (role == Qt::CheckStateRole) + return row->checkState(); + if (role == Qt::ToolTipRole) + return row->fileInfo().absoluteFilePath(); + return {}; +} + +QModelIndex AssetImportUpdateTreeModel::parent(const QModelIndex &idx) const +{ + if (!idx.isValid()) + return QModelIndex(); + + const AssetImportUpdateTreeItem *childItem = treeItemAtIndex(idx); + const AssetImportUpdateTreeItem *parentItem = childItem->parent(); + + if (parentItem == m_rootItem) + return QModelIndex(); + + return createIndex(parentItem->rowOfItem(), 0, const_cast(parentItem)); +} + +int AssetImportUpdateTreeModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + + return parent.isValid() ? treeItemAtIndex(parent)->childCount() + : m_rootItem->childCount(); +} + +int AssetImportUpdateTreeModel::columnCount(const QModelIndex &) const +{ + return 1; +} + +AssetImportUpdateTreeItem *AssetImportUpdateTreeModel::treeItemAtIndex(const QModelIndex &idx) +{ + return static_cast(idx.internalPointer()); +} + +QVariant AssetImportUpdateTreeModel::data(const QModelIndex &idx, int role) const +{ + if (!idx.isValid()) + return {}; + + if (role == Qt::SizeHintRole) + return QSize(0, 20); + + return data(treeItemAtIndex(idx), role); +} + +bool AssetImportUpdateTreeModel::setData(const QModelIndex &idx, const QVariant &value, int role) +{ + if (role == Qt::CheckStateRole) { + auto checkState = static_cast(value.toInt()); + return setCheckState(idx, checkState); + } + return QAbstractItemModel::setData(idx, value, role); +} + +bool AssetImportUpdateTreeModel::setCheckState(const QModelIndex &idx, Qt::CheckState checkState, + bool firstCall) +{ + AssetImportUpdateTreeItem *item = treeItemAtIndex(idx); + if (item->checkState() == checkState) + return false; + item->setCheckState(checkState); + if (firstCall) { + emit dataChanged(idx, idx); + // check parents + AssetImportUpdateTreeItem *parent = item->parent(); + QModelIndex parentIdx = idx.parent(); + while (parent) { + bool hasChecked = false; + bool hasUnchecked = false; + for (const auto child : parent->children()) { + if (child->checkState() == Qt::Checked) { + hasChecked = true; + } else if (child->checkState() == Qt::Unchecked) { + hasUnchecked = true; + } else if (child->checkState() == Qt::PartiallyChecked) { + hasChecked = true; + hasUnchecked = true; + } + } + if (hasChecked && hasUnchecked) + parent->setCheckState(Qt::PartiallyChecked); + else if (hasChecked) + parent->setCheckState(Qt::Checked); + else + parent->setCheckState(Qt::Unchecked); + emit dataChanged(parentIdx, parentIdx); + parent = parent->parent(); + parentIdx = parentIdx.parent(); + } + } + // check children + if (int children = item->childCount()) { + for (int i = 0; i < children; ++i) + setCheckState(index(i, 0, idx), checkState, false); + emit dataChanged(index(0, 0, idx), index(children - 1, 0, idx)); + } + return true; +} + +void AssetImportUpdateTreeModel::createItems(const QList &infos, + const QSet &preselectedFiles) +{ + beginResetModel(); + if (!infos.isEmpty()) { + QHash dirItems; + for (const auto &info : infos) { + auto parent = dirItems.value(info.absolutePath()); + if (!parent) + parent = m_rootItem; + auto item = new AssetImportUpdateTreeItem(info, parent); + if (info.isDir()) { + dirItems.insert(info.absoluteFilePath(), item); + } else { + m_fileItems.append(item); + if (preselectedFiles.contains(info.absoluteFilePath())) + item->setCheckState(Qt::Checked); + } + } + // Remove dir items that have no children from the model + for (auto dirItem : qAsConst(dirItems)) { + if (dirItem->childCount() == 0) + delete dirItem; + } + std::function updateDirCheckStatesRecursive; + updateDirCheckStatesRecursive = [&](AssetImportUpdateTreeItem *item) -> Qt::CheckState { + bool hasChecked = false; + bool hasUnchecked = false; + for (const auto child : item->children()) { + Qt::CheckState childState = child->childCount() > 0 + ? updateDirCheckStatesRecursive(child) + : child->checkState(); + if (childState == Qt::Checked) { + hasChecked = true; + } else if (childState == Qt::Unchecked) { + hasUnchecked = true; + } else { + hasChecked = true; + hasUnchecked = true; + break; + } + } + Qt::CheckState retval = Qt::Unchecked; + if (hasChecked && hasUnchecked) + retval = Qt::PartiallyChecked; + else if (hasChecked) + retval = Qt::Checked; + item->setCheckState(retval); + return retval; + }; + m_rootItem->setCheckState(updateDirCheckStatesRecursive(m_rootItem)); + } + endResetModel(); +} + +QStringList AssetImportUpdateTreeModel::checkedFiles() const +{ + QStringList retList; + + for (const auto item : qAsConst(m_fileItems)) { + if (item->checkState() == Qt::Checked) + retList.append(item->fileInfo().absoluteFilePath()); + } + + return retList; +} + +void AssetImportUpdateTreeModel::clear() +{ + beginResetModel(); + m_fileItems.clear(); + m_rootItem->clear(); // Deletes all children + endResetModel(); +} + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreemodel.h b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreemodel.h new file mode 100644 index 00000000000..adba52f0379 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreemodel.h @@ -0,0 +1,73 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace QmlDesigner { +namespace Internal { + +class AssetImportUpdateTreeItem; + +class AssetImportUpdateTreeModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + AssetImportUpdateTreeModel(QObject *parent = nullptr); + ~AssetImportUpdateTreeModel() override; + + Qt::ItemFlags flags(const QModelIndex &index) const override; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; + QModelIndex parent(const QModelIndex &child) const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + int columnCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override; + + void createItems(const QList &infos, const QSet &preselectedFiles); + QStringList checkedFiles() const; + + static AssetImportUpdateTreeItem *treeItemAtIndex(const QModelIndex &idx); + +public slots: + void clear(); + +private: + QModelIndex index(AssetImportUpdateTreeItem *item) const; + QVariant data(const AssetImportUpdateTreeItem *row, int role) const; + bool setCheckState(const QModelIndex &idx, Qt::CheckState checkState, bool firstCall = true); + + AssetImportUpdateTreeItem *m_rootItem; + QList m_fileItems; +}; + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeview.cpp b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeview.cpp new file mode 100644 index 00000000000..87e9fc1c803 --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeview.cpp @@ -0,0 +1,57 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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 "assetimportupdatetreeview.h" +#include "assetimportupdatetreemodel.h" +#include "assetimportupdatetreeitemdelegate.h" + +#include + +namespace QmlDesigner { +namespace Internal { + +AssetImportUpdateTreeView::AssetImportUpdateTreeView(QWidget *parent) + : Utils::TreeView(parent) + , m_model(new AssetImportUpdateTreeModel(this)) +{ + setModel(m_model); + setItemDelegate(new AssetImportUpdateTreeItemDelegate(this)); + setUniformRowHeights(true); + setExpandsOnDoubleClick(true); + header()->hide(); +} + +void AssetImportUpdateTreeView::clear() +{ + m_model->clear(); +} + +AssetImportUpdateTreeModel *AssetImportUpdateTreeView::model() const +{ + return m_model; +} + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeview.h b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeview.h new file mode 100644 index 00000000000..3c5e040cfaf --- /dev/null +++ b/src/plugins/qmldesigner/components/itemlibrary/assetimportupdatetreeview.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** +** Copyright (C) 2021 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. +** +****************************************************************************/ + +#pragma once + +#include "assetimportupdatetreeitem.h" + +#include + +namespace QmlDesigner { +namespace Internal { + +class AssetImportUpdateTreeModel; + +class AssetImportUpdateTreeView : public Utils::TreeView +{ + Q_OBJECT + +public: + explicit AssetImportUpdateTreeView(QWidget *parent = nullptr); + + AssetImportUpdateTreeModel *model() const; + +public slots: + void clear(); + +protected: + AssetImportUpdateTreeModel *m_model; +}; + +} // namespace Internal +} // namespace QmlDesigner diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri index 3f05f1ab0b1..8099c878536 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibrary.pri @@ -15,7 +15,12 @@ HEADERS += itemlibraryview.h \ itemlibraryaddimportmodel.h \ itemlibraryassetimportdialog.h \ itemlibraryassetimporter.h \ - customfilesystemmodel.h + customfilesystemmodel.h \ + assetimportupdatedialog.h \ + assetimportupdatetreeitem.h \ + assetimportupdatetreeitemdelegate.h \ + assetimportupdatetreemodel.h \ + assetimportupdatetreeview.h SOURCES += itemlibraryview.cpp \ $$PWD/itemlibraryiconimageprovider.cpp \ @@ -31,7 +36,13 @@ SOURCES += itemlibraryview.cpp \ itemlibraryaddimportmodel.cpp \ itemlibraryassetimportdialog.cpp \ itemlibraryassetimporter.cpp \ - customfilesystemmodel.cpp + customfilesystemmodel.cpp \ + assetimportupdatedialog.cpp \ + assetimportupdatetreeitem.cpp \ + assetimportupdatetreeitemdelegate.cpp \ + assetimportupdatetreemodel.cpp \ + assetimportupdatetreeview.cpp RESOURCES += itemlibrary.qrc -FORMS += itemlibraryassetimportdialog.ui +FORMS += itemlibraryassetimportdialog.ui \ + assetimportupdatedialog.ui diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp index 847be2c5f25..69924f20080 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.cpp @@ -23,6 +23,7 @@ ** ****************************************************************************/ #include "itemlibraryassetimporter.h" +#include "assetimportupdatedialog.h" #include "qmldesignerplugin.h" #include "qmldesignerconstants.h" @@ -41,6 +42,7 @@ #include #include #include +#include namespace { @@ -262,11 +264,43 @@ bool ItemLibraryAssetImporter::preParseQuick3DAsset(const QString &file, ParseDa pd.assetName = assetDirs[0]; pd.targetDirPath = pd.targetDir.filePath(pd.assetName); } - if (!confirmAssetOverwrite(pd.assetName)) { + OverwriteResult result = confirmAssetOverwrite(pd.assetName); + if (result == OverwriteResult::Skip) { addWarning(tr("Skipped import of existing asset: \"%1\"").arg(pd.assetName)); return false; + } else if (result == OverwriteResult::Update) { + // Add generated icons and existing source asset file, as those will always need + // to be overwritten + QSet alwaysOverwrite; + QString iconPath = pd.targetDirPath + '/' + Constants::QUICK_3D_ASSET_ICON_DIR; + // Note: Despite the name, QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX is not a traditional file + // suffix. It's guaranteed to be in the generated icon filename, though. + QStringList filters {QStringLiteral("*%1*").arg(Constants::QUICK_3D_ASSET_LIBRARY_ICON_SUFFIX)}; + QDirIterator iconIt(iconPath, filters, QDir::Files); + while (iconIt.hasNext()) { + iconIt.next(); + alwaysOverwrite.insert(iconIt.fileInfo().absoluteFilePath()); + } + alwaysOverwrite.insert(sourceSceneTargetFilePath(pd)); + + Internal::AssetImportUpdateDialog dlg {pd.targetDirPath, {}, alwaysOverwrite, + qobject_cast(parent())}; + int exitVal = dlg.exec(); + + QStringList overwriteFiles; + if (exitVal == QDialog::Accepted) + overwriteFiles = dlg.selectedFiles(); + if (!overwriteFiles.isEmpty()) { + overwriteFiles.append(QStringList::fromSet(alwaysOverwrite)); + m_overwrittenImports.insert(pd.targetDirPath, overwriteFiles); + } else { + addWarning(tr("No files selected for overwrite, skipping import: \"%1\"").arg(pd.assetName)); + return false; + } + + } else { + m_overwrittenImports.insert(pd.targetDirPath, {}); } - m_overwrittenImports << pd.targetDirPath; } pd.outDir.mkpath(pd.assetName); @@ -395,8 +429,7 @@ void ItemLibraryAssetImporter::postParseQuick3DAsset(const ParseData &pd) } // Copy the original asset into a subdirectory - assetFiles.insert(pd.sourceInfo.absoluteFilePath(), - pd.targetDirPath + QStringLiteral("/source scene/") + pd.sourceInfo.fileName()); + assetFiles.insert(pd.sourceInfo.absoluteFilePath(), sourceSceneTargetFilePath(pd)); m_importFiles.insert(assetFiles); } @@ -408,11 +441,22 @@ void ItemLibraryAssetImporter::copyImportedFiles() notifyProgress(0, progressTitle); int counter = 0; - for (const QString &dirPath : qAsConst(m_overwrittenImports)) { - QDir dir(dirPath); - if (dir.exists()) - dir.removeRecursively(); + auto it = m_overwrittenImports.constBegin(); + while (it != m_overwrittenImports.constEnd()) { + QDir dir(it.key()); + if (dir.exists()) { + const auto &overwrittenFiles = it.value(); + if (overwrittenFiles.isEmpty()) { + // Overwrite entire import + dir.removeRecursively(); + } else { + // Overwrite just selected files + for (const auto &fileName : overwrittenFiles) + QFile::remove(fileName); + } + } notifyProgress((100 * ++counter) / m_overwrittenImports.size(), progressTitle); + ++it; } } @@ -430,7 +474,7 @@ void ItemLibraryAssetImporter::copyImportedFiles() // by filesystem watchers. QHash::const_iterator it = assetFiles.begin(); while (it != assetFiles.end()) { - if (QFileInfo::exists(it.key())) { + if (QFileInfo::exists(it.key()) && !QFileInfo::exists(it.value())) { QDir targetDir = QFileInfo(it.value()).dir(); if (!targetDir.exists()) targetDir.mkpath("."); @@ -461,13 +505,26 @@ void ItemLibraryAssetImporter::keepUiAlive() const QApplication::processEvents(); } -bool ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName) +ItemLibraryAssetImporter::OverwriteResult ItemLibraryAssetImporter::confirmAssetOverwrite(const QString &assetName) { const QString title = tr("Overwrite Existing Asset?"); - const QString question = tr("Asset already exists. Overwrite?\n\"%1\"").arg(assetName); - return QMessageBox::question(qobject_cast(parent()), - title, question, - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes; + const QString question = tr("Asset already exists. Overwrite existing or skip?\n\"%1\"").arg(assetName); + + QMessageBox msgBox {QMessageBox::Question, title, question, QMessageBox::NoButton, + qobject_cast(parent())}; + QPushButton *updateButton = msgBox.addButton(tr("Overwrite Selected Files"), QMessageBox::NoRole); + QPushButton *overwriteButton = msgBox.addButton(tr("Overwrite All Files"), QMessageBox::NoRole); + QPushButton *skipButton = msgBox.addButton(tr("Skip"), QMessageBox::NoRole); + msgBox.setDefaultButton(overwriteButton); + msgBox.setEscapeButton(skipButton); + + msgBox.exec(); + + if (msgBox.clickedButton() == updateButton) + return OverwriteResult::Update; + else if (msgBox.clickedButton() == overwriteButton) + return OverwriteResult::Overwrite; + return OverwriteResult::Skip; } bool ItemLibraryAssetImporter::startImportProcess(const ParseData &pd) @@ -623,6 +680,11 @@ void ItemLibraryAssetImporter::finalizeQuick3DImport() } } +QString ItemLibraryAssetImporter::sourceSceneTargetFilePath(const ParseData &pd) +{ + return pd.targetDirPath + QStringLiteral("/source scene/") + pd.sourceInfo.fileName(); +} + bool ItemLibraryAssetImporter::isCancelled() const { keepUiAlive(); diff --git a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h index 022859a8527..56bd8ec8f29 100644 --- a/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h +++ b/src/plugins/qmldesigner/components/itemlibrary/itemlibraryassetimporter.h @@ -99,14 +99,22 @@ private: void notifyProgress(int value, const QString &text); void notifyProgress(int value); void keepUiAlive() const; - bool confirmAssetOverwrite(const QString &assetName); + + enum class OverwriteResult { + Skip, + Overwrite, + Update + }; + + OverwriteResult confirmAssetOverwrite(const QString &assetName); bool startImportProcess(const ParseData &pd); bool startIconProcess(int size, const QString &iconFile, const QString &iconSource); void postImport(); void finalizeQuick3DImport(); + QString sourceSceneTargetFilePath(const ParseData &pd); QSet> m_importFiles; - QSet m_overwrittenImports; + QHash m_overwrittenImports; bool m_isImporting = false; bool m_cancelled = false; QString m_importPath; diff --git a/src/plugins/qmldesigner/qmldesignerplugin.qbs b/src/plugins/qmldesigner/qmldesignerplugin.qbs index 0011be7a786..bd6c94cbb62 100644 --- a/src/plugins/qmldesigner/qmldesignerplugin.qbs +++ b/src/plugins/qmldesigner/qmldesignerplugin.qbs @@ -603,6 +603,17 @@ Project { "integration/stackedutilitypanelcontroller.h", "integration/utilitypanelcontroller.cpp", "integration/utilitypanelcontroller.h", + "itemlibrary/assetimportupdatedialog.cpp", + "itemlibrary/assetimportupdatedialog.h", + "itemlibrary/assetimportupdatedialog.ui", + "itemlibrary/assetimportupdatetreeitem.cpp", + "itemlibrary/assetimportupdatetreeitem.h", + "itemlibrary/assetimportupdatetreeitemdelegate.cpp", + "itemlibrary/assetimportupdatetreeitemdelegate.h", + "itemlibrary/assetimportupdatetreemodel.cpp", + "itemlibrary/assetimportupdatetreemodel.h", + "itemlibrary/assetimportupdatetreeview.cpp", + "itemlibrary/assetimportupdatetreeview.h", "itemlibrary/itemlibrary.qrc", "itemlibrary/itemlibraryaddimportmodel.cpp", "itemlibrary/itemlibraryaddimportmodel.h",