diff options
Diffstat (limited to 'src/libs/installer')
-rw-r--r-- | src/libs/installer/aspectratiolabel.cpp | 8 | ||||
-rw-r--r-- | src/libs/installer/componentmodel.cpp | 31 | ||||
-rw-r--r-- | src/libs/installer/componentmodel.h | 4 | ||||
-rw-r--r-- | src/libs/installer/componentselectionpage_p.cpp | 91 | ||||
-rw-r--r-- | src/libs/installer/componentselectionpage_p.h | 6 | ||||
-rw-r--r-- | src/libs/installer/componentsortfilterproxymodel.cpp | 152 | ||||
-rw-r--r-- | src/libs/installer/componentsortfilterproxymodel.h | 63 | ||||
-rw-r--r-- | src/libs/installer/fileutils.cpp | 32 | ||||
-rw-r--r-- | src/libs/installer/installer.pro | 2 |
9 files changed, 332 insertions, 57 deletions
diff --git a/src/libs/installer/aspectratiolabel.cpp b/src/libs/installer/aspectratiolabel.cpp index a9af93a55..c8c3c1693 100644 --- a/src/libs/installer/aspectratiolabel.cpp +++ b/src/libs/installer/aspectratiolabel.cpp @@ -81,9 +81,11 @@ QSize AspectRatioLabel::sizeHint() const */ QPixmap AspectRatioLabel::scaledPixmap() const { - return m_pixmap.isNull() - ? QPixmap() - : m_pixmap.scaled(size(), Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (m_pixmap.isNull()) + return QPixmap(); + + return m_pixmap.scaled(size() * m_pixmap.devicePixelRatio(), + Qt::KeepAspectRatio, Qt::SmoothTransformation); } /*! diff --git a/src/libs/installer/componentmodel.cpp b/src/libs/installer/componentmodel.cpp index 642828ad7..56ee57fc6 100644 --- a/src/libs/installer/componentmodel.cpp +++ b/src/libs/installer/componentmodel.cpp @@ -101,7 +101,6 @@ ComponentModel::ComponentModel(int columns, PackageManagerCore *core) , m_modelState(DefaultChecked) { m_headerData.insert(0, columns, QVariant()); - connect(this, &QAbstractItemModel::modelReset, this, &ComponentModel::slotModelReset); } /*! @@ -417,6 +416,7 @@ void ComponentModel::setRootComponents(QList<QInstaller::Component*> rootCompone m_rootComponentList.append(component); } endResetModel(); + postModelReset(); } /*! @@ -459,7 +459,16 @@ void ComponentModel::setCheckedState(QInstaller::ComponentModel::ModelStateFlag // -- private slots -void ComponentModel::slotModelReset() +void ComponentModel::onVirtualStateChanged() +{ + // If the virtual state of a component changes, force a reset of the component model. + setRootComponents(m_core->components(PackageManagerCore::ComponentType::Root)); +} + + +// -- private + +void ComponentModel::postModelReset() { ComponentList components = m_rootComponentList; if (!m_core->isUpdater()) { @@ -485,15 +494,6 @@ void ComponentModel::slotModelReset() updateAndEmitModelState(); // update the internal state } -void ComponentModel::onVirtualStateChanged() -{ - // If the virtual state of a component changes, force a reset of the component model. - setRootComponents(m_core->components(PackageManagerCore::ComponentType::Root)); -} - - -// -- private - void ComponentModel::updateAndEmitModelState() { m_modelState = ComponentModel::DefaultChecked; @@ -511,15 +511,6 @@ void ComponentModel::updateAndEmitModelState() } emit checkStateChanged(m_modelState); - - foreach (const Component *component, m_rootComponentList) { - emit dataChanged(indexFromComponentName(component->treeName()), - indexFromComponentName(component->treeName())); - QList<Component *> children = component->childItems(); - foreach (const Component *child, children) - emit dataChanged(indexFromComponentName(child->treeName()), - indexFromComponentName(child->treeName())); - } } void ComponentModel::collectComponents(Component *const component, const QModelIndex &parent) const diff --git a/src/libs/installer/componentmodel.h b/src/libs/installer/componentmodel.h index 8a9fbf884..e5cd2c57d 100644 --- a/src/libs/installer/componentmodel.h +++ b/src/libs/installer/componentmodel.h @@ -1,6 +1,6 @@ /************************************************************************** ** -** Copyright (C) 2017 The Qt Company Ltd. +** Copyright (C) 2021 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the Qt Installer Framework. @@ -94,10 +94,10 @@ Q_SIGNALS: void checkStateChanged(QInstaller::ComponentModel::ModelState state); private Q_SLOTS: - void slotModelReset(); void onVirtualStateChanged(); private: + void postModelReset(); void updateAndEmitModelState(); void collectComponents(Component *const component, const QModelIndex &parent) const; QSet<QModelIndex> updateCheckedState(const ComponentSet &components, Qt::CheckState state); diff --git a/src/libs/installer/componentselectionpage_p.cpp b/src/libs/installer/componentselectionpage_p.cpp index a180888d1..7717f4cf3 100644 --- a/src/libs/installer/componentselectionpage_p.cpp +++ b/src/libs/installer/componentselectionpage_p.cpp @@ -50,6 +50,7 @@ #include <QStackedLayout> #include <QStackedWidget> #include <QToolBox> +#include <QLineEdit> namespace QInstaller { @@ -71,8 +72,11 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP , m_descriptionBaseWidget(nullptr) , m_categoryWidget(Q_NULLPTR) , m_categoryLayoutVisible(false) + , m_proxyModel(new ComponentSortFilterProxyModel(q)) { m_treeView->setObjectName(QLatin1String("ComponentsTreeView")); + m_proxyModel->setRecursiveFilteringEnabled(true); + m_proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); m_descriptionBaseWidget = new QWidget(q); m_descriptionBaseWidget->setObjectName(QLatin1String("DescriptionBaseWidget")); @@ -155,9 +159,18 @@ ComponentSelectionPagePrivate::ComponentSelectionPagePrivate(ComponentSelectionP metaLayout->addWidget(m_progressBar); metaLayout->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding)); + m_searchLineEdit = new QLineEdit(q); + m_searchLineEdit->setObjectName(QLatin1String("SearchLineEdit")); + m_searchLineEdit->setPlaceholderText(ComponentSelectionPage::tr("Search")); + m_searchLineEdit->setClearButtonEnabled(true); + connect(m_searchLineEdit, &QLineEdit::textChanged, + this, &ComponentSelectionPagePrivate::setSearchPattern); + connect(q, &ComponentSelectionPage::entered, m_searchLineEdit, &QLineEdit::clear); + QVBoxLayout *treeViewVLayout = new QVBoxLayout; treeViewVLayout->setObjectName(QLatin1String("TreeviewLayout")); treeViewVLayout->addWidget(m_treeView, 3); + treeViewVLayout->addWidget(m_searchLineEdit); QWidget *mainStackedWidget = new QWidget(); m_mainGLayout = new QGridLayout(mainStackedWidget); @@ -292,15 +305,11 @@ void ComponentSelectionPagePrivate::updateTreeView() this, &ComponentSelectionPagePrivate::currentSelectedChanged); } + m_searchLineEdit->setVisible(!m_core->isUpdater()); m_currentModel = m_core->isUpdater() ? m_updaterModel : m_allModel; - m_treeView->setModel(m_currentModel); - m_treeView->setExpanded(m_currentModel->index(0, 0), true); - foreach (Component *component, m_core->components(PackageManagerCore::ComponentType::All)) { - if (component->isExpandedByDefault()) { - const QModelIndex index = m_currentModel->indexFromComponentName(component->treeName()); - m_treeView->setExpanded(index, true); - } - } + m_proxyModel->setSourceModel(m_currentModel); + m_treeView->setModel(m_proxyModel); + expandDefault(); const bool installActionColumnVisible = m_core->settings().installActionColumnVisible(); if (!installActionColumnVisible) @@ -341,7 +350,44 @@ void ComponentSelectionPagePrivate::updateTreeView() connect(m_treeView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ComponentSelectionPagePrivate::currentSelectedChanged); - m_treeView->setCurrentIndex(m_currentModel->index(0, 0)); + m_treeView->setCurrentIndex(m_proxyModel->index(0, 0)); +} + +/*! + Expands components that should be expanded by default. +*/ +void ComponentSelectionPagePrivate::expandDefault() +{ + m_treeView->setExpanded(m_proxyModel->index(0, 0), true); + foreach (auto *component, m_core->components(PackageManagerCore::ComponentType::All)) { + if (component->isExpandedByDefault()) { + const QModelIndex index = m_proxyModel->mapFromSource( + m_currentModel->indexFromComponentName(component->treeName())); + m_treeView->setExpanded(index, true); + } + } +} + +/*! + Expands components that were accepted by proxy models filter. +*/ +void ComponentSelectionPagePrivate::expandSearchResults() +{ + // Expand parents of root indexes accepted by filter + const QVector<QModelIndex> acceptedIndexes = m_proxyModel->directlyAcceptedIndexes(); + for (auto proxyModelIndex : acceptedIndexes) { + if (!proxyModelIndex.isValid()) + continue; + + QModelIndex index = proxyModelIndex.parent(); + while (index.isValid()) { + if (m_treeView->isExpanded(index)) + break; // Multiple direct matches in a branch, can be skipped + + m_treeView->expand(index); + index = index.parent(); + } + } } void ComponentSelectionPagePrivate::currentSelectedChanged(const QModelIndex ¤t) @@ -351,12 +397,12 @@ void ComponentSelectionPagePrivate::currentSelectedChanged(const QModelIndex &cu m_sizeLabel->setText(QString()); - QString description = m_currentModel->data(m_currentModel->index(current.row(), + QString description = m_proxyModel->data(m_proxyModel->index(current.row(), ComponentModelHelper::NameColumn, current.parent()), Qt::ToolTipRole).toString(); m_descriptionLabel->setText(description); - Component *component = m_currentModel->componentFromIndex(current); + Component *component = m_currentModel->componentFromIndex(m_proxyModel->mapToSource(current)); if ((m_core->isUninstaller()) || (!component)) return; @@ -429,6 +475,7 @@ void ComponentSelectionPagePrivate::fetchRepositoryCategories() QLatin1String("FailToFetchPackages"), tr("Error"), m_core->error()); } updateWidgetVisibility(false); + m_searchLineEdit->text().isEmpty() ? expandDefault() : expandSearchResults(); } void ComponentSelectionPagePrivate::customButtonClicked(int which) @@ -507,4 +554,26 @@ void ComponentSelectionPagePrivate::onModelStateChanged(QInstaller::ComponentMod currentSelectedChanged(m_treeView->selectionModel()->currentIndex()); } +/*! + Sets the new filter pattern to \a text and expands the tree nodes. +*/ +void ComponentSelectionPagePrivate::setSearchPattern(const QString &text) +{ + m_proxyModel->setFilterWildcard(text); + + m_treeView->collapseAll(); + if (text.isEmpty()) { + // Expand user selection and default expanded, ensure selected is visible + QModelIndex index = m_treeView->selectionModel()->currentIndex(); + while (index.isValid()) { + m_treeView->expand(index); + index = index.parent(); + } + expandDefault(); + m_treeView->scrollTo(m_treeView->selectionModel()->currentIndex()); + } else { + expandSearchResults(); + } +} + } // namespace QInstaller diff --git a/src/libs/installer/componentselectionpage_p.h b/src/libs/installer/componentselectionpage_p.h index d2d30e3f0..fc37ebdaa 100644 --- a/src/libs/installer/componentselectionpage_p.h +++ b/src/libs/installer/componentselectionpage_p.h @@ -34,6 +34,7 @@ #include "componentmodel.h" #include "packagemanagergui.h" +#include "componentsortfilterproxymodel.h" class QTreeView; class QLabel; @@ -70,6 +71,8 @@ public: void setupCategoryLayout(); void showCategoryLayout(bool show); void updateTreeView(); + void expandDefault(); + void expandSearchResults(); public slots: void currentSelectedChanged(const QModelIndex ¤t); @@ -84,6 +87,7 @@ public slots: void setTotalProgress(int totalProgress); void selectDefault(); void onModelStateChanged(QInstaller::ComponentModel::ModelState state); + void setSearchPattern(const QString &text); private: ComponentSelectionPage *q; @@ -107,6 +111,8 @@ private: ComponentModel *m_updaterModel; ComponentModel *m_currentModel; QStackedLayout *m_stackedLayout; + ComponentSortFilterProxyModel *m_proxyModel; + QLineEdit *m_searchLineEdit; }; } // namespace QInstaller diff --git a/src/libs/installer/componentsortfilterproxymodel.cpp b/src/libs/installer/componentsortfilterproxymodel.cpp new file mode 100644 index 000000000..ab736a7c9 --- /dev/null +++ b/src/libs/installer/componentsortfilterproxymodel.cpp @@ -0,0 +1,152 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#include "componentsortfilterproxymodel.h" + +namespace QInstaller { + +/*! + \class QInstaller::ComponentSortFilterProxyModel + \inmodule QtInstallerFramework + \brief The ComponentSortFilterProxyModel provides support for sorting and + filtering data passed between another model and a view. + + The class subclasses QSortFilterProxyModel. Compared to the base class, + filters affect also child indexes in the base model, meaning if a + certain row has a parent that is accepted by filter, it is also accepted. + A distinction is made betweed directly and indirectly accepted indexes. +*/ + +/*! + \enum ComponentSortFilterProxyModel::AcceptType + + This enum holds the possible values for filter acception type for model indexes. + + \value Direct + Index was accepted directly by filter. + \value Descendant + Index is a descendant of an accepted index. + \value Reject + Index was not accepted by filter. +*/ + +/*! + Constructs object with \a parent. +*/ +ComponentSortFilterProxyModel::ComponentSortFilterProxyModel(QObject *parent) + : QSortFilterProxyModel(parent) +{ +} + +/*! + Returns a list of source model indexes that were accepted directly by the filter. +*/ +QVector<QModelIndex> ComponentSortFilterProxyModel::directlyAcceptedIndexes() const +{ + QVector<QModelIndex> indexes; + for (int i = 0; i < rowCount(); i++) { + QModelIndex childIndex = index(i, 0, QModelIndex()); + findDirectlyAcceptedIndexes(childIndex, indexes); + } + return indexes; +} + +/*! + Returns \c true if the item in the row indicated by the given \a sourceRow and + \a sourceParent should be included in the model; otherwise returns \c false. +*/ +bool ComponentSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + return acceptsRow(sourceRow, sourceParent); +} + +/*! + Returns \c true if the item in the row indicated by the given \a sourceRow and + \a sourceParent should be included in the model; otherwise returns \c false. The + acception type can be retrieved with \a type. +*/ +bool ComponentSortFilterProxyModel::acceptsRow(int sourceRow, const QModelIndex &sourceParent, + AcceptType *type) const +{ + if (type) + *type = AcceptType::Rejected; + + if (QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) { + if (type) + *type = AcceptType::Direct; + return true; + } + + if (!isRecursiveFilteringEnabled()) // filter not applied for children + return false; + + if (!sourceParent.isValid()) // already root + return false; + + int currentRow = sourceParent.row(); + QModelIndex currentParent = sourceParent.parent(); + // Ascend and check if any parent is accepted + forever { + if (QSortFilterProxyModel::filterAcceptsRow(currentRow, currentParent)) { + if (type) + *type = AcceptType::Descendant; + return true; + } + if (!currentParent.isValid()) // we hit a root node + break; + + currentRow = currentParent.row(); + currentParent = currentParent.parent(); + } + return false; +} + +/*! + Finds directly accepted child \a indexes for parent index \a in. Returns \c true + if at least one accepted index was found, \c false otherwise. +*/ +bool ComponentSortFilterProxyModel::findDirectlyAcceptedIndexes(const QModelIndex &in, QVector<QModelIndex> &indexes) const +{ + bool found = false; + for (int i = 0; i < rowCount(in); i++) { + if (findDirectlyAcceptedIndexes(index(i, 0, in), indexes)) + found = true; + } + if (!hasChildren(in) || !found) { // No need to check current if any child matched + AcceptType acceptType; + const QModelIndex sourceIndex = mapToSource(in); + acceptsRow(sourceIndex.row(), sourceIndex.parent(), &acceptType); + if (acceptType == AcceptType::Direct) { + indexes.append(in); + found = true; + } + } + return found; +} + +} // namespace QInstaller diff --git a/src/libs/installer/componentsortfilterproxymodel.h b/src/libs/installer/componentsortfilterproxymodel.h new file mode 100644 index 000000000..a6167d17b --- /dev/null +++ b/src/libs/installer/componentsortfilterproxymodel.h @@ -0,0 +1,63 @@ +/************************************************************************** +** +** Copyright (C) 2021 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the Qt Installer Framework. +** +** $QT_BEGIN_LICENSE:GPL-EXCEPT$ +** 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. +** +** $QT_END_LICENSE$ +** +**************************************************************************/ + +#ifndef COMPONENTSORTFILTERPROXYMODEL_H +#define COMPONENTSORTFILTERPROXYMODEL_H + +#include "installer_global.h" + +#include <QSortFilterProxyModel> + +namespace QInstaller { + +class INSTALLER_EXPORT ComponentSortFilterProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + enum AcceptType { + Direct, + Descendant, + Rejected + }; + + explicit ComponentSortFilterProxyModel(QObject *parent = nullptr); + + QVector<QModelIndex> directlyAcceptedIndexes() const; + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const Q_DECL_OVERRIDE; + +private: + bool acceptsRow(int sourceRow, const QModelIndex &sourceParent, AcceptType *type = nullptr) const; + bool findDirectlyAcceptedIndexes(const QModelIndex &in, QVector<QModelIndex> &indexes) const; +}; + +} // namespace QInstaller + +#endif // COMPONENTSORTFILTERPROXYMODEL_H diff --git a/src/libs/installer/fileutils.cpp b/src/libs/installer/fileutils.cpp index 3c9f2ad1c..8c015a1c2 100644 --- a/src/libs/installer/fileutils.cpp +++ b/src/libs/installer/fileutils.cpp @@ -454,31 +454,21 @@ void QInstaller::mkpath(const QString &path) */ QString QInstaller::generateTemporaryFileName(const QString &templ) { - if (templ.isEmpty()) { - QTemporaryFile f; - if (!f.open()) { + static const QLatin1String staticPart("%1.tmp.XXXXXX"); + + QTemporaryFile f; + if (!templ.isEmpty()) + f.setFileTemplate(QString(staticPart).arg(templ)); + + if (!f.open()) { + if (!templ.isEmpty()) { + throw Error(QCoreApplication::translate("QInstaller", + "Cannot open temporary file for template %1: %2").arg(templ, f.errorString())); + } else { throw Error(QCoreApplication::translate("QInstaller", "Cannot open temporary file: %1").arg(f.errorString())); } - return f.fileName(); - } - - static const QString characters = QLatin1String("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890"); - QString suffix; - for (int i = 0; i < 5; ++i) - suffix += characters[QRandomGenerator::global()->generate() % characters.length()]; - - const QString tmp = QLatin1String("%1.tmp.%2.%3"); - int count = 1; - while (QFile::exists(tmp.arg(templ, suffix).arg(count))) - ++count; - - QFile f(tmp.arg(templ, suffix).arg(count)); - if (!f.open(QIODevice::WriteOnly)) { - throw Error(QCoreApplication::translate("QInstaller", - "Cannot open temporary file for template %1: %2").arg(templ, f.errorString())); } - f.remove(); return f.fileName(); } diff --git a/src/libs/installer/installer.pro b/src/libs/installer/installer.pro index 4721bb089..ed7d739ed 100644 --- a/src/libs/installer/installer.pro +++ b/src/libs/installer/installer.pro @@ -44,6 +44,7 @@ win32:QT += winextras HEADERS += packagemanagercore.h \ aspectratiolabel.h \ + componentsortfilterproxymodel.h \ loggingutils.h \ packagemanagercore_p.h \ packagemanagergui.h \ @@ -150,6 +151,7 @@ SOURCES += packagemanagercore.cpp \ aspectratiolabel.cpp \ directoryguard.cpp \ lib7zarchive.cpp \ + componentsortfilterproxymodel.cpp \ loggingutils.cpp \ packagemanagercore_p.cpp \ packagemanagergui.cpp \ |