From 7796a25de086de6276d5128d50469ab260528eeb Mon Sep 17 00:00:00 2001 From: kh1 Date: Mon, 11 Mar 2013 14:51:16 +0100 Subject: Add component model behavior auto test and fix broken model. Task-number: QTIFW-84 Task-number: QTIFW-213 Change-Id: I8fc9035ba3b14c2c12a1672b74869bece68c8ee9 Reviewed-by: Niels Weber Reviewed-by: Rainer Keller Reviewed-by: Kai Koehne --- src/libs/installer/component_p.cpp | 53 ++-- src/libs/installer/component_p.h | 4 +- src/libs/installer/componentmodel.cpp | 448 +++++++++++++++--------------- src/libs/installer/componentmodel.h | 51 ++-- src/libs/installer/packagemanagercore.cpp | 3 +- src/libs/installer/packagemanagergui.cpp | 66 +++-- 6 files changed, 313 insertions(+), 312 deletions(-) (limited to 'src/libs') diff --git a/src/libs/installer/component_p.cpp b/src/libs/installer/component_p.cpp index 51455c27f..7af7a43b2 100644 --- a/src/libs/installer/component_p.cpp +++ b/src/libs/installer/component_p.cpp @@ -202,8 +202,8 @@ ComponentModelHelper::ComponentModelHelper() } /*! - Returns the number of child components. Depending if virtual components are visible or not the count might - differ from what one will get if calling Component::childComponents(...).count(). + Returns the number of child components. Depending if virtual components are visible or not, + the count might differ from what one will get if calling Component::childComponents(...).count(). */ int ComponentModelHelper::childCount() const { @@ -213,51 +213,38 @@ int ComponentModelHelper::childCount() const } /*! - Returns the index of this component as seen from it's parent. + Returns the component at index position in the list. Index must be a valid position in + the list (i.e., index >= 0 && index < childCount()). Otherwise it returns 0. */ -int ComponentModelHelper::indexInParent() const +Component *ComponentModelHelper::childAt(int index) const { - int index = 0; - if (Component *parent = m_componentPrivate->m_parentComponent->parentComponent()) { - index = parent->childComponents(Component::DirectChildrenOnly) - .indexOf(m_componentPrivate->m_parentComponent); - } - return (index >= 0 ? index : 0); + if (index < 0 && index >= childCount()) + return 0; + + if (m_componentPrivate->m_core->virtualComponentsVisible()) + return m_componentPrivate->m_allChildComponents.value(index, 0); + return m_componentPrivate->m_childComponents.value(index, 0); } /*! - Returns all children and whose children depending if virtual components are visible or not. + Returns all descendants of this component depending if virtual components are visible or not. */ -QList ComponentModelHelper::childs() const +QList ComponentModelHelper::childItems() const { QList *components = &m_componentPrivate->m_childComponents; if (m_componentPrivate->m_core->virtualComponentsVisible()) components = &m_componentPrivate->m_allChildComponents; QList result; - foreach (Component *component, *components) { + foreach (Component *const component, *components) { result.append(component); - result += component->childs(); + result += component->childItems(); } return result; } /*! - Returns the component at index position in the list. Index must be a valid position in - the list (i.e., index >= 0 && index < childCount()). Otherwise it returns 0. -*/ -Component *ComponentModelHelper::childAt(int index) const -{ - if (index >= 0 && index < childCount()) { - if (m_componentPrivate->m_core->virtualComponentsVisible()) - return m_componentPrivate->m_allChildComponents.value(index, 0); - return m_componentPrivate->m_childComponents.value(index, 0); - } - return 0; -} - -/*! - Determines if the components installations status can be changed. The default value is true. + Determines if the installation status of the component can be changed. The default value is true. */ bool ComponentModelHelper::isEnabled() const { @@ -265,7 +252,7 @@ bool ComponentModelHelper::isEnabled() const } /*! - Enables oder disables ability to change the components installations status. + Enables or disables the ability to change the installation status of the components. */ void ComponentModelHelper::setEnabled(bool enabled) { @@ -273,7 +260,7 @@ void ComponentModelHelper::setEnabled(bool enabled) } /*! - Returns whether the component is tristate; that is, if it's checkable with three separate states. + Returns whether the component is tri-state; that is, if it's checkable with three separate states. The default value is false. */ bool ComponentModelHelper::isTristate() const @@ -282,10 +269,10 @@ bool ComponentModelHelper::isTristate() const } /*! - Sets whether the component is tristate. If tristate is true, the component is checkable with three + Sets whether the component is tri-state. If tri-state is true, the component is checkable with three separate states; otherwise, the component is checkable with two states. - (Note that this also requires that the component is checkable; see isCheckable().) + Note: this also requires that the component is checkable. \sa isCheckable() */ void ComponentModelHelper::setTristate(bool tristate) { diff --git a/src/libs/installer/component_p.h b/src/libs/installer/component_p.h index 241888a3f..9101021ae 100644 --- a/src/libs/installer/component_p.h +++ b/src/libs/installer/component_p.h @@ -125,10 +125,8 @@ public: explicit ComponentModelHelper(); int childCount() const; - int indexInParent() const; - - QList childs() const; Component* childAt(int index) const; + QList childItems() const; bool isEnabled() const; void setEnabled(bool enabled); diff --git a/src/libs/installer/componentmodel.cpp b/src/libs/installer/componentmodel.cpp index 264f69650..f65611ffe 100644 --- a/src/libs/installer/componentmodel.cpp +++ b/src/libs/installer/componentmodel.cpp @@ -47,17 +47,18 @@ namespace QInstaller { /*! - \fn void defaultCheckStateChanged(bool changed) + \fn void checkStateChanged(const QModelIndex &index) - This signal is emitted whenever the default check state of a model is changed. The \a changed value - indicates whether the model has it's initial checked state or some components changed it's checked state. + This signal is emitted whenever the check state of a component is changed. The \a index value indicates + the QModelIndex representation of the component as seen from the model. */ /*! - \fn void checkStateChanged(const QModelIndex &index) + \fn void checkStateChanged(QInstaller::ComponentModel::ModelState state) - This signal is emitted whenever the default check state of a component is changed. The \a index value - indicates the QModelIndex representation of the component as seen from the model. + This signal is emitted whenever the check state of a model is changed after all check state + calculations have taken place. The \a state value indicates whether the model has its default checked + state, all components are checked/ unchecked or some individual components checked state has changed. */ @@ -67,12 +68,10 @@ namespace QInstaller { ComponentModel::ComponentModel(int columns, PackageManagerCore *core) : QAbstractItemModel(core) , m_core(core) - , m_rootIndex(0) + , m_modelState(DefaultChecked) { m_headerData.insert(0, columns, QVariant()); - connect(this, SIGNAL(modelReset()), this, SLOT(slotModelReset())); - connect(this, SIGNAL(checkStateChanged(QModelIndex)), this, SLOT(slotCheckStateChanged(QModelIndex))); } /*! @@ -83,8 +82,25 @@ ComponentModel::~ComponentModel() } /*! - Returns the number of items under the given \a parent. When the parent is valid it means that rowCount is - returning the number of items of parent. + Returns the item flags for the given \a index. + + The class implementation returns a combination of flags that enables the item (Qt::ItemIsEnabled), allows + it to be selected (Qt::ItemIsSelectable) and to be checked (Qt::ItemIsUserCheckable). +*/ +Qt::ItemFlags ComponentModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::NoItemFlags; + + if (Component *component = componentFromIndex(index)) + return component->flags(); + + return Qt::ItemFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable); +} + +/*! + Returns the number of items under the given \a parent. When the parent is valid it means that rowCount + is returning the number of items of parent. */ int ComponentModel::rowCount(const QModelIndex &parent) const { @@ -160,9 +176,9 @@ QVariant ComponentModel::data(const QModelIndex &index, int role) const } /*! - Sets the \a role data for the item at \a index to \a value. Returns true if successful; otherwise returns - false. The dataChanged() signal is emitted if the data was successfully set. The checkStateChanged() and - defaultCheckStateChanged() signal are emitted in addition if the check state of the item is set. + Sets the \a role data for the item at \a index to \a value. Returns true if successful; + otherwise returns false. The dataChanged() signal is emitted if the data was successfully set. + The checkStateChanged() signals are emitted in addition if the check state of the item is set. */ bool ComponentModel::setData(const QModelIndex &index, const QVariant &value, int role) { @@ -173,14 +189,17 @@ bool ComponentModel::setData(const QModelIndex &index, const QVariant &value, in if (!component) return false; - component->setData(value, role); - - emit dataChanged(index, index); if (role == Qt::CheckStateRole) { - emit checkStateChanged(index); - foreach (Component* comp, m_rootComponentList) { - comp->updateUncompressedSize(); + ComponentSet nodes = component->childItems().toSet(); + QSet changed = updateCheckedState(nodes << component, Qt::CheckState(value.toInt())); + foreach (const QModelIndex &index, changed) { + emit dataChanged(index, index); + emit checkStateChanged(index); } + updateAndEmitModelState(); // update the internal state + } else { + component->setData(value, role); + emit dataChanged(index, index); } return true; @@ -217,72 +236,69 @@ bool ComponentModel::setHeaderData(int section, Qt::Orientation orientation, con } /*! - Returns the item flags for the given \a index. - - The class implementation returns a combination of flags that enables the item (Qt::ItemIsEnabled), allows - it to be selected (Qt::ItemIsSelectable) and to be checked (Qt::ItemIsUserCheckable). + Returns a list of checked components. */ -Qt::ItemFlags ComponentModel::flags(const QModelIndex &index) const +QSet ComponentModel::checked() const { - if (!index.isValid()) - return Qt::NoItemFlags; - - if (Component *component = componentFromIndex(index)) - return component->flags(); + return m_currentCheckedState[Qt::Checked]; +} - return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; +/*! + Returns a list of partially checked components. +*/ +QSet ComponentModel::partially() const +{ + return m_currentCheckedState[Qt::PartiallyChecked]; } /*! - Returns a pointer to the PackageManagerCore this model belongs to. + Returns a list of unchecked components. */ -PackageManagerCore *ComponentModel::packageManagerCore() const +QSet ComponentModel::unchecked() const { - return m_core; + return m_currentCheckedState[Qt::Unchecked]; } /*! - Returns true if no changes to the components checked state have been done, otherwise returns false. + Returns a list of components whose check state can't be changed. If package manager core is run with no + forced installation argument, the list will always be empty. */ -bool ComponentModel::defaultCheckState() const +QSet ComponentModel::uncheckable() const { - return m_initialCheckedSet == m_currentCheckedSet; + return m_uncheckable; } /*! - Returns true if this model has checked components, otherwise returns false. + Returns a pointer to the PackageManagerCore this model belongs to. */ -bool ComponentModel::hasCheckedComponents() const +PackageManagerCore *ComponentModel::core() const { - return !m_currentCheckedSet.isEmpty(); + return m_core; } /*! - Returns a list of checked components. + Returns the current state check state of the model. */ -QList ComponentModel::checkedComponents() const +ComponentModel::ModelState ComponentModel::checkedState() const { - QList list; - foreach (const QString &name, m_currentCheckedSet) - list.append(componentFromIndex(indexFromComponentName(name))); - return list; + return m_modelState; } /*! - Translates between a given component \a name and it's associated QModelIndex. Returns the QModelIndex that - represents the component or an invalid QModelIndex if the component does not exist in the model. + Translates between a given component \a name and its associated QModelIndex. Returns the QModelIndex + that represents the component or an invalid QModelIndex if the component does not exist in the model. */ QModelIndex ComponentModel::indexFromComponentName(const QString &name) const { if (m_indexByNameCache.isEmpty()) { for (int i = 0; i < m_rootComponentList.count(); ++i) - updateCache(index(i, 0, QModelIndex())); + collectComponents(m_rootComponentList.at(i), index(i, 0, QModelIndex())); } return m_indexByNameCache.value(name, QModelIndex()); } /*! - Translates between a given QModelIndex \a index and it's associated Component. Returns the Component if + Translates between a given QModelIndex \a index and its associated Component. Returns the component if the index is valid or 0 if an invalid QModelIndex is given. */ Component *ComponentModel::componentFromIndex(const QModelIndex &index) const @@ -292,242 +308,218 @@ Component *ComponentModel::componentFromIndex(const QModelIndex &index) const return 0; } -// -- public slots - -/*! - Invoking this slot results in an checked state for every component the has a visual representation in the - model. Note that components are not changed if they are not checkable. The checkStateChanged() and - defaultCheckStateChanged() signal are emitted. -*/ -void ComponentModel::selectAll() -{ - m_currentCheckedSet = m_currentCheckedSet.unite(select(Qt::Checked)); - emit defaultCheckStateChanged(m_initialCheckedSet != m_currentCheckedSet); -} -/*! - Invoking this slot results in an unchecked state for every component the has a visual representation in - the model. Note that components are not changed if they are not checkable. The checkStateChanged() and - defaultCheckStateChanged() signal are emitted. -*/ -void ComponentModel::deselectAll() -{ - m_currentCheckedSet = m_currentCheckedSet.subtract(select(Qt::Unchecked)); - emit defaultCheckStateChanged(m_initialCheckedSet != m_currentCheckedSet); -} +// -- public slots /*! - Invoking this slot results in an checked state for every component the has a visual representation in the - model when the model was setup during setRootComponents() or appendRootComponents(). Note that components - are not changed it they are not checkable. The checkStateChanged() and defaultCheckStateChanged() signal - are emitted. -*/ -void ComponentModel::selectDefault() -{ - m_currentCheckedSet = m_currentCheckedSet.subtract(select(Qt::Unchecked)); - foreach (const QString &name, m_initialCheckedSet) - setData(indexFromComponentName(name), Qt::Checked, Qt::CheckStateRole); - emit defaultCheckStateChanged(m_initialCheckedSet != m_currentCheckedSet); -} + Sets the passed \a rootComponents to be the list of currently shown components. -/*! - Set's the passed \a rootComponents to be list of currently shown components. The model is repopulated and - the individual component checked state is used to show the check mark in front of the visual component - representation. The modelAboutToBeReset() and modelReset() signals are emitted. + The model is repopulated and the individual component checked state is used to show the check mark in + front of the visual component representation. The modelAboutToBeReset() and modelReset() signals are + emitted. */ void ComponentModel::setRootComponents(QList rootComponents) { beginResetModel(); + m_uncheckable.clear(); m_indexByNameCache.clear(); - m_rootComponentList.clear(); - m_initialCheckedSet.clear(); - m_currentCheckedSet.clear(); - - m_rootIndex = 0; - m_rootComponentList = rootComponents; - + m_initialCheckedState.clear(); + m_currentCheckedState.clear(); + m_modelState = DefaultChecked; + + // show virtual components only in case we run as updater or if the core engine is set to show them + const bool showVirtuals = m_core->isUpdater() || m_core->virtualComponentsVisible(); + foreach (Component *const component, rootComponents) { + if ((!showVirtuals) && component->isVirtual()) + continue; + m_rootComponentList.append(component); + } endResetModel(); } /*! - Appends the passed \a rootComponents to the currently shown list of components. The model is repopulated - and the individual component checked state is used to show the check mark in front of the visual component - representation. Already changed check states on the previous model are preserved. The modelAboutToBeReset() - and modelReset() signals are emitted. + Sets the check state of every component in the model to be \a state. + + The ComponentModel::PartiallyChecked flag is ignored by this function. Note that components are not + changed if they are not checkable. The dataChanged() and checkStateChanged() signals are emitted. */ -void ComponentModel::appendRootComponents(QList rootComponents) +void ComponentModel::setCheckedState(QInstaller::ComponentModel::ModelStateFlag state) { - beginResetModel(); - - m_indexByNameCache.clear(); + QSet changed; + switch (state) { + case AllChecked: + changed = updateCheckedState(m_currentCheckedState[Qt::Unchecked], Qt::Checked); + break; + case AllUnchecked: + changed = updateCheckedState(m_currentCheckedState[Qt::Checked], Qt::Unchecked); + break; + case DefaultChecked: + // record all changes, to be able to update the UI properly + changed = updateCheckedState(m_currentCheckedState[Qt::Checked], Qt::Unchecked); + changed += updateCheckedState(m_initialCheckedState[Qt::Checked], Qt::Checked); + break; + default: + break; + } - m_rootIndex = m_rootComponentList.count() - 1; - m_rootComponentList += rootComponents; + if (changed.isEmpty()) + return; - endResetModel(); + // notify about changes done to the model + foreach (const QModelIndex &index, changed) { + emit dataChanged(index, index); + emit checkStateChanged(index); + } + updateAndEmitModelState(); // update the internal state } + // -- private slots void ComponentModel::slotModelReset() { - QList components = m_rootComponentList; + ComponentList components = m_rootComponentList; if (!m_core->isUpdater()) { - for (int i = m_rootIndex; i < m_rootComponentList.count(); ++i) - components.append(m_rootComponentList.at(i)->childs()); + foreach (Component *const component, m_rootComponentList) + components += component->childItems(); + } + + ComponentSet checked; + foreach (Component *const component, components) { + if (component->checkState() == Qt::Checked) + checked.insert(component); } - foreach (Component *child, components) { - if (child->checkState() == Qt::Checked && !child->isTristate()) - m_initialCheckedSet.insert(child->name()); + updateCheckedState(checked, Qt::Checked); + foreach (Component *const component, components) { + if (!component->isCheckable()) + m_uncheckable.insert(component); + m_initialCheckedState[component->checkState()].insert(component); + } + + m_currentCheckedState = m_initialCheckedState; + updateAndEmitModelState(); // update the internal state +} + + +// -- private + +void ComponentModel::updateAndEmitModelState() +{ + m_modelState = ComponentModel::DefaultChecked; + if (m_initialCheckedState != m_currentCheckedState) + m_modelState = ComponentModel::PartiallyChecked; + + if (checked().count() == 0 && partially().count() == 0) { + m_modelState |= ComponentModel::AllUnchecked; + m_modelState &= ~ComponentModel::PartiallyChecked; } - m_currentCheckedSet += m_initialCheckedSet; - if (!m_core->isUpdater()) - select(Qt::Unchecked); + if (unchecked().count() == 0 && partially().count() == 0) { + m_modelState |= ComponentModel::AllChecked; + m_modelState &= ~ComponentModel::PartiallyChecked; + } - foreach (const QString &name, m_currentCheckedSet) - setData(indexFromComponentName(name), Qt::Checked, Qt::CheckStateRole); + emit checkStateChanged(m_modelState); +} +void ComponentModel::collectComponents(Component *const component, const QModelIndex &parent) const +{ + m_indexByNameCache.insert(component->name(), parent); + for (int i = 0; i < component->childCount(); ++i) + collectComponents(component->childAt(i), index(i, 0, parent)); } +namespace ComponentModelPrivate { + +struct NameGreaterThan +{ + bool operator() (const Component *lhs, const Component *rhs) const + { + return lhs->name() > rhs->name(); + } +}; + static Qt::CheckState verifyPartiallyChecked(Component *component) { int checked = 0; int unchecked = 0; - int virtualChilds = 0; const int count = component->childCount(); for (int i = 0; i < count; ++i) { - Component *const child = component->childAt(i); - if (!child->isVirtual()) { - switch (component->childAt(i)->checkState()) { - case Qt::Checked: { - ++checked; - } break; - case Qt::Unchecked: { - ++unchecked; - } break; - default: - break; - } - } else { - ++virtualChilds; + switch (component->childAt(i)->checkState()) { + case Qt::Checked: { + ++checked; + } break; + case Qt::Unchecked: { + ++unchecked; + } break; + default: + break; } } - if ((checked + virtualChilds) == count) + if (checked == count) return Qt::Checked; - if ((unchecked + virtualChilds) == count) + if (unchecked == count) return Qt::Unchecked; return Qt::PartiallyChecked; } -void ComponentModel::slotCheckStateChanged(const QModelIndex &index) -{ - Component *component = componentFromIndex(index); - if (!component) - return; - - if (component->checkState() == Qt::Checked && !component->isTristate()) - m_currentCheckedSet.insert(component->name()); - else if (component->checkState() == Qt::Unchecked && !component->isTristate()) - m_currentCheckedSet.remove(component->name()); - emit defaultCheckStateChanged(m_initialCheckedSet != m_currentCheckedSet); - - if (component->isVirtual()) - return; - - const Qt::CheckState state = component->checkState(); - if (component->isTristate()) { - if (state == Qt::PartiallyChecked) { - component->setCheckState(verifyPartiallyChecked(component)); - return; - } - - QModelIndexList notCheckable; - foreach (Component *child, component->childs()) { - const QModelIndex &idx = indexFromComponentName(child->name()); - if (child->isCheckable()) { - if (child->checkState() != state && !child->isVirtual()) - setData(idx, state, Qt::CheckStateRole); - } else { - notCheckable.append(idx); - } - } - - if (state == Qt::Unchecked && !notCheckable.isEmpty()) { - foreach (const QModelIndex &idx, notCheckable) - setData(idx, idx.data(Qt::CheckStateRole), Qt::CheckStateRole); - } - } else { - QList parents; - while (0 != component->parentComponent()) { - parents.append(component->parentComponent()); - component = parents.last(); - } - - foreach (Component *parent, parents) { - if (parent->isCheckable()) { - const QModelIndex &idx = indexFromComponentName(parent->name()); - if (parent->checkState() == Qt::PartiallyChecked) { - setData(idx, verifyPartiallyChecked(parent), Qt::CheckStateRole); - } else { - setData(idx, Qt::PartiallyChecked, Qt::CheckStateRole); - } - } - } - } -} - -// -- private +} // namespace ComponentModelPrivate -QSet ComponentModel::select(Qt::CheckState state) +QSet ComponentModel::updateCheckedState(const ComponentSet &components, Qt::CheckState state) { - QSet changed; - for (int i = 0; i < m_rootComponentList.count(); ++i) { - QSet tmp; - QList children = m_rootComponentList.at(i)->childs(); - children.prepend(m_rootComponentList.at(i)); // we need to take the root item into account as well - foreach (Component *child, children) { - if (child->isCheckable() && !child->isTristate() && child->checkState() != state) { - tmp.insert(child->name()); - child->setCheckState(state); + // get all parent nodes for the components we're going to update + ComponentSet nodes = components; + foreach (Component *const component, components) { + if (Component *parent = component->parentComponent()) { + nodes.insert(parent); + while (parent->parentComponent() != 0) { + parent = parent->parentComponent(); + nodes.insert(parent); } } - if (!tmp.isEmpty()) { - changed += tmp; - setData(index(i, 0, QModelIndex()), state, Qt::CheckStateRole); - } } - return changed; -} -void ComponentModel::updateCache(const QModelIndex &parent) const -{ - const QModelIndexList &list = collectComponents(parent); - foreach (const QModelIndex &index, list) { - if (Component *component = componentFromIndex(index)) - m_indexByNameCache.insert(component->name(), index); - } - m_indexByNameCache.insert((static_cast (parent.internalPointer()))->name(), parent); -} - -QModelIndexList ComponentModel::collectComponents(const QModelIndex &parent) const -{ - QModelIndexList list; - for (int i = 0; i < rowCount(parent) ; ++i) { - const QModelIndex &next = index(i, 0, parent); - if (Component *component = componentFromIndex(next)) { - if (component->childCount() > 0) - list += collectComponents(next); + QSet changed; + // sort the nodes, so we can start in descending order to check node and tri-state nodes properly + ComponentList sortedNodes = nodes.toList(); + std::sort(sortedNodes.begin(), sortedNodes.end(), ComponentModelPrivate::NameGreaterThan()); + foreach (Component *const node, sortedNodes) { + if (!node->isCheckable()) + continue; + + Qt::CheckState newState = state; + const Qt::CheckState recentState = node->checkState(); + if (node->isTristate()) + newState = ComponentModelPrivate::verifyPartiallyChecked(node); + if (recentState == newState) + continue; + + node->setCheckState(newState); + changed.insert(indexFromComponentName(node->name())); + + m_currentCheckedState[Qt::Checked].remove(node); + m_currentCheckedState[Qt::Unchecked].remove(node); + m_currentCheckedState[Qt::PartiallyChecked].remove(node); + + switch (newState) { + case Qt::Checked: + m_currentCheckedState[Qt::Checked].insert(node); + break; + case Qt::Unchecked: + m_currentCheckedState[Qt::Unchecked].insert(node); + break; + case Qt::PartiallyChecked: + m_currentCheckedState[Qt::PartiallyChecked].insert(node); + break; } - list.append(next); } - return list; + return changed; } } // namespace QInstaller diff --git a/src/libs/installer/componentmodel.h b/src/libs/installer/componentmodel.h index b43bfc75a..a691906a4 100644 --- a/src/libs/installer/componentmodel.h +++ b/src/libs/installer/componentmodel.h @@ -57,11 +57,23 @@ class PackageManagerCore; class INSTALLER_EXPORT ComponentModel : public QAbstractItemModel { Q_OBJECT + typedef QSet ComponentSet; + typedef QList ComponentList; public: + enum ModelStateFlag { + AllChecked = 0x01, + AllUnchecked = 0x02, + DefaultChecked = 0x04, + PartiallyChecked = 0x08 + }; + Q_DECLARE_FLAGS(ModelState, ModelStateFlag); + explicit ComponentModel(int columns, PackageManagerCore *core = 0); ~ComponentModel(); + Qt::ItemFlags flags(const QModelIndex &index) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; int columnCount(const QModelIndex &parent = QModelIndex()) const; @@ -75,49 +87,50 @@ public: bool setHeaderData(int section, Qt::Orientation orientation, const QVariant &value, int role = Qt::EditRole); - Qt::ItemFlags flags(const QModelIndex &index) const; - PackageManagerCore *packageManagerCore() const; + QSet checked() const; + QSet partially() const; + QSet unchecked() const; + QSet uncheckable() const; - bool defaultCheckState() const; - bool hasCheckedComponents() const; - QList checkedComponents() const; + PackageManagerCore *core() const; + ComponentModel::ModelState checkedState() const; QModelIndex indexFromComponentName(const QString &name) const; Component* componentFromIndex(const QModelIndex &index) const; public Q_SLOTS: - void selectAll(); - void deselectAll(); - void selectDefault(); - void setRootComponents(QList rootComponents); - void appendRootComponents(QList rootComponents); + void setCheckedState(QInstaller::ComponentModel::ModelStateFlag state); Q_SIGNALS: - void defaultCheckStateChanged(bool changed); void checkStateChanged(const QModelIndex &index); + void checkStateChanged(QInstaller::ComponentModel::ModelState state); private Q_SLOTS: void slotModelReset(); - void slotCheckStateChanged(const QModelIndex &index); private: - QSet select(Qt::CheckState state); - void updateCache(const QModelIndex &parent) const; - QModelIndexList collectComponents(const QModelIndex &parent) const; + void updateAndEmitModelState(); + void collectComponents(Component *const component, const QModelIndex &parent) const; + QSet updateCheckedState(const ComponentSet &components, Qt::CheckState state); private: PackageManagerCore *m_core; - int m_rootIndex; + ModelState m_modelState; + ComponentSet m_uncheckable; QVector m_headerData; - QSet m_initialCheckedSet; - QSet m_currentCheckedSet; - QList m_rootComponentList; + ComponentList m_rootComponentList; + QHash m_initialCheckedState; + QHash m_currentCheckedState; mutable QHash m_indexByNameCache; }; +Q_DECLARE_OPERATORS_FOR_FLAGS(ComponentModel::ModelState); } // namespace QInstaller +Q_DECLARE_METATYPE(QInstaller::ComponentModel::ModelState); +Q_DECLARE_METATYPE(QInstaller::ComponentModel::ModelStateFlag); + #endif // COMPONENTMODEL_H diff --git a/src/libs/installer/packagemanagercore.cpp b/src/libs/installer/packagemanagercore.cpp index 5b75aa0fb..376b178c3 100644 --- a/src/libs/installer/packagemanagercore.cpp +++ b/src/libs/installer/packagemanagercore.cpp @@ -1970,7 +1970,8 @@ ComponentModel *PackageManagerCore::componentModel(PackageManagerCore *core, con ComponentModel::tr("Size")); connect(this, SIGNAL(setRootComponents(QList)), model, SLOT(setRootComponents(QList))); - connect(model, SIGNAL(defaultCheckStateChanged(bool)), this, SLOT(componentsToInstallNeedsRecalculation())); + connect(model, SIGNAL(checkStateChanged(QInstaller::ComponentModel::ModelState)), this, + SLOT(componentsToInstallNeedsRecalculation())); return model; } diff --git a/src/libs/installer/packagemanagergui.cpp b/src/libs/installer/packagemanagergui.cpp index c68891145..d0d67aa7a 100644 --- a/src/libs/installer/packagemanagergui.cpp +++ b/src/libs/installer/packagemanagergui.cpp @@ -1071,8 +1071,10 @@ public: { m_treeView->setObjectName(QLatin1String("ComponentsTreeView")); - connect(m_allModel, SIGNAL(defaultCheckStateChanged(bool)), q, SLOT(setModified(bool))); - connect(m_updaterModel, SIGNAL(defaultCheckStateChanged(bool)), q, SLOT(setModified(bool))); + connect(m_allModel, SIGNAL(checkStateChanged(QInstaller::ComponentModel::ModelState)), this, + SLOT(onCheckStateChanged(QInstaller::ComponentModel::ModelState))); + connect(m_updaterModel, SIGNAL(checkStateChanged(QInstaller::ComponentModel::ModelState)), this, + SLOT(onCheckStateChanged(QInstaller::ComponentModel::ModelState))); QHBoxLayout *hlayout = new QHBoxLayout; hlayout->addWidget(m_treeView, 3); @@ -1098,7 +1100,6 @@ public: m_checkDefault = new QPushButton; connect(m_checkDefault, SIGNAL(clicked()), this, SLOT(selectDefault())); - connect(m_allModel, SIGNAL(defaultCheckStateChanged(bool)), m_checkDefault, SLOT(setEnabled(bool))); const QVariantHash hash = q->elementsForPage(QLatin1String("ComponentSelectionPage")); if (m_core->isInstaller()) { m_checkDefault->setObjectName(QLatin1String("SelectDefaultComponentsButton")); @@ -1175,8 +1176,8 @@ public: public slots: void currentChanged(const QModelIndex ¤t) { - // if there is not selection or the current selected node didn't change, return - if (!current.isValid() || current != m_treeView->selectionModel()->currentIndex()) + // if there is no selection, return + if (!current.isValid()) return; m_descriptionLabel->setText(m_currentModel->data(m_currentModel->index(current.row(), @@ -1184,42 +1185,51 @@ public slots: m_sizeLabel->clear(); if (!m_core->isUninstaller()) { - Component *component = m_currentModel->componentFromIndex(current); - if (component && component->updateUncompressedSize() > 0) { - const QVariantHash hash = q->elementsForPage(QLatin1String("ComponentSelectionPage")); - m_sizeLabel->setText(hash.value(QLatin1String("ComponentSizeLabel"), - ComponentSelectionPage::tr("This component will occupy approximately %1 on your hard disk drive.")).toString() - .arg(m_currentModel->data(m_currentModel->index(current.row(), - ComponentModelHelper::UncompressedSizeColumn, current.parent())).toString())); + const QModelIndex currentSelected = m_treeView->selectionModel()->currentIndex(); + if (!currentSelected.isValid()) + return; + + Component *component = m_currentModel->componentFromIndex(currentSelected); + if (component == 0) + return; + + if (component->updateUncompressedSize() > 0) { + m_sizeLabel->setText(q->elementsForPage(QLatin1String("ComponentSelectionPage")) + .value(QLatin1String("ComponentSizeLabel"), ComponentSelectionPage::tr("This component " + "will occupy approximately %1 on your hard disk drive.")).toString() + .arg(humanReadableSize(component->value(scUncompressedSizeSum).toLongLong()))); } } } - // TODO: all *select* function ignore the fact that components can be selected inside the tree view as - // well, which will result in e.g. a disabled button state as long as "ALL" components not - // unchecked again. void selectAll() { - m_currentModel->selectAll(); - - m_checkAll->setEnabled(false); - m_uncheckAll->setEnabled(true); + m_currentModel->setCheckedState(ComponentModel::AllChecked); } void deselectAll() { - m_currentModel->deselectAll(); - - m_checkAll->setEnabled(true); - m_uncheckAll->setEnabled(false); + m_currentModel->setCheckedState(ComponentModel::AllUnchecked); } void selectDefault() { - m_currentModel->selectDefault(); + m_currentModel->setCheckedState(ComponentModel::DefaultChecked); + } + + void onCheckStateChanged(QInstaller::ComponentModel::ModelState state) + { + q->setModified(state != ComponentModel::DefaultChecked); + + // If all components in the checked list are only checkable when run without forced installation, set + // ComponentModel::AllUnchecked as well, as we cannot uncheck anything. Helps to keep the UI correct. + if ((!m_core->noForceInstallation()) && (m_currentModel->checked() == m_currentModel->uncheckable())) + state |= ComponentModel::AllUnchecked; - m_checkAll->setEnabled(true); - m_uncheckAll->setEnabled(true); + // enable the button if the corresponding flag is not set + m_checkAll->setEnabled(state.testFlag(ComponentModel::AllChecked) == false); + m_uncheckAll->setEnabled(state.testFlag(ComponentModel::AllUnchecked) == false); + m_checkDefault->setEnabled(state.testFlag(ComponentModel::DefaultChecked) == false); } public: @@ -1330,8 +1340,8 @@ void ComponentSelectionPage::setModified(bool modified) bool ComponentSelectionPage::isComplete() const { if (packageManagerCore()->isInstaller() || packageManagerCore()->isUpdater()) - return d->m_currentModel->hasCheckedComponents(); - return !d->m_currentModel->defaultCheckState(); + return d->m_currentModel->checked().count(); + return d->m_currentModel->checkedState() != ComponentModel::DefaultChecked; } -- cgit v1.2.3