summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/libs/installer/component_p.cpp53
-rw-r--r--src/libs/installer/component_p.h4
-rw-r--r--src/libs/installer/componentmodel.cpp448
-rw-r--r--src/libs/installer/componentmodel.h51
-rw-r--r--src/libs/installer/packagemanagercore.cpp3
-rw-r--r--src/libs/installer/packagemanagergui.cpp66
-rw-r--r--tests/auto/installer/componentmodel/componentmodel.pro8
-rw-r--r--tests/auto/installer/componentmodel/components.qrc5
-rw-r--r--tests/auto/installer/componentmodel/data/updates.xml132
-rw-r--r--tests/auto/installer/componentmodel/tst_componentmodel.cpp479
-rw-r--r--tests/auto/installer/installer.pro1
11 files changed, 938 insertions, 312 deletions
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<Component*> ComponentModelHelper::childs() const
+QList<Component*> ComponentModelHelper::childItems() const
{
QList<Component*> *components = &m_componentPrivate->m_childComponents;
if (m_componentPrivate->m_core->virtualComponentsVisible())
components = &m_componentPrivate->m_allChildComponents;
QList<Component*> 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<Component*> childs() const;
Component* childAt(int index) const;
+ QList<Component*> 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<QModelIndex> 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<Component *> 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<Component *> 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<Component *> 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<Component *> 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<Component*> ComponentModel::checkedComponents() const
+ComponentModel::ModelState ComponentModel::checkedState() const
{
- QList<Component*> 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<QInstaller::Component*> 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<QInstaller::Component*> rootComponents)
+void ComponentModel::setCheckedState(QInstaller::ComponentModel::ModelStateFlag state)
{
- beginResetModel();
-
- m_indexByNameCache.clear();
+ QSet<QModelIndex> 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<QInstaller::Component*> 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<Component*> 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<QString> ComponentModel::select(Qt::CheckState state)
+QSet<QModelIndex> ComponentModel::updateCheckedState(const ComponentSet &components, Qt::CheckState state)
{
- QSet<QString> changed;
- for (int i = 0; i < m_rootComponentList.count(); ++i) {
- QSet<QString> tmp;
- QList<Component*> 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<Component*> (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<QModelIndex> 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<Component *> ComponentSet;
+ typedef QList<Component *> 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<Component *> checked() const;
+ QSet<Component *> partially() const;
+ QSet<Component *> unchecked() const;
+ QSet<Component *> uncheckable() const;
- bool defaultCheckState() const;
- bool hasCheckedComponents() const;
- QList<Component*> 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<QInstaller::Component*> rootComponents);
- void appendRootComponents(QList<QInstaller::Component*> 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<QString> 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<QModelIndex> updateCheckedState(const ComponentSet &components, Qt::CheckState state);
private:
PackageManagerCore *m_core;
- int m_rootIndex;
+ ModelState m_modelState;
+ ComponentSet m_uncheckable;
QVector<QVariant> m_headerData;
- QSet<QString> m_initialCheckedSet;
- QSet<QString> m_currentCheckedSet;
- QList<Component*> m_rootComponentList;
+ ComponentList m_rootComponentList;
+ QHash<Qt::CheckState, ComponentSet> m_initialCheckedState;
+ QHash<Qt::CheckState, ComponentSet> m_currentCheckedState;
mutable QHash<QString, QPersistentModelIndex> 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<QInstaller::Component*>)), model,
SLOT(setRootComponents(QList<QInstaller::Component*>)));
- 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 &current)
{
- // 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;
}
diff --git a/tests/auto/installer/componentmodel/componentmodel.pro b/tests/auto/installer/componentmodel/componentmodel.pro
new file mode 100644
index 000000000..bbd00924b
--- /dev/null
+++ b/tests/auto/installer/componentmodel/componentmodel.pro
@@ -0,0 +1,8 @@
+include(../../qttest.pri)
+
+QT -= gui
+QT += network
+
+SOURCES += tst_componentmodel.cpp
+
+RESOURCES += components.qrc
diff --git a/tests/auto/installer/componentmodel/components.qrc b/tests/auto/installer/componentmodel/components.qrc
new file mode 100644
index 000000000..a2b530f13
--- /dev/null
+++ b/tests/auto/installer/componentmodel/components.qrc
@@ -0,0 +1,5 @@
+<RCC>
+ <qresource prefix="/">
+ <file>data/updates.xml</file>
+ </qresource>
+</RCC>
diff --git a/tests/auto/installer/componentmodel/data/updates.xml b/tests/auto/installer/componentmodel/data/updates.xml
new file mode 100644
index 000000000..e1f72de10
--- /dev/null
+++ b/tests/auto/installer/componentmodel/data/updates.xml
@@ -0,0 +1,132 @@
+<Updates>
+ <ApplicationName>Your application</ApplicationName>
+ <ApplicationVersion>1.2.3</ApplicationVersion>
+ <Checksum>true</Checksum>
+ <PackageUpdate>
+ <Name>com.vendor.product</Name>
+ <DisplayName>The root component</DisplayName>
+ <Description>Install this example.</Description>
+ <Version>0.1.0-1</Version>
+ <ReleaseDate>2010-09-21</ReleaseDate>
+ <Default>true</Default>
+ <Script>installscript.qs</Script>
+ <SortingPriority>1</SortingPriority>
+ <ForcedInstallation>true</ForcedInstallation>
+ <UpdateFile UncompressedSize="61"
+ CompressedSize="61"/>
+ <Licenses>
+ <License name="Beer Public License Agreement"
+ file="license.txt"/>
+ </Licenses>
+ </PackageUpdate>
+ <PackageUpdate>
+ <Name>com.vendor.second.product</Name>
+ <DisplayName>The second root component</DisplayName>
+ <Description>Install this example.</Description>
+ <Version>0.1.0-1</Version>
+ <ReleaseDate>2010-09-21</ReleaseDate>
+ <Default>false</Default>
+ <Script>installscript.qs</Script>
+ <SortingPriority>1</SortingPriority>
+ <UpdateFile UncompressedSize="61"
+ CompressedSize="61"/>
+ <Licenses>
+ <License name="Beer Public License Agreement"
+ file="license.txt"/>
+ </Licenses>
+ </PackageUpdate>
+ <PackageUpdate>
+ <Name>com.vendor.second.product.subnode</Name>
+ <DisplayName>A sub node component for the second root</DisplayName>
+ <Description>Install this example.</Description>
+ <Version>0.1.0-1</Version>
+ <ReleaseDate>2010-09-21</ReleaseDate>
+ <Default>false</Default>
+ <Script>installscript.qs</Script>
+ <SortingPriority>1</SortingPriority>
+ <UpdateFile UncompressedSize="61"
+ CompressedSize="61"/>
+ <Licenses>
+ <License name="Beer Public License Agreement"
+ file="license.txt"/>
+ </Licenses>
+ </PackageUpdate>
+ <PackageUpdate>
+ <Name>com.vendor.second.product.subnode.sub</Name>
+ <DisplayName>A subcomponent for the second sub node of the second root</DisplayName>
+ <Description>Install this example.</Description>
+ <Version>0.1.0-1</Version>
+ <ReleaseDate>2010-09-21</ReleaseDate>
+ <Default>false</Default>
+ <Script>installscript.qs</Script>
+ <SortingPriority>1</SortingPriority>
+ <UpdateFile UncompressedSize="61"
+ CompressedSize="61"/>
+ <Licenses>
+ <License name="Beer Public License Agreement"
+ file="license.txt"/>
+ </Licenses>
+ </PackageUpdate>
+ <PackageUpdate>
+ <Name>com.vendor.second.product.sub</Name>
+ <DisplayName>A subcomponent for the second root</DisplayName>
+ <Description>Install this example.</Description>
+ <Version>0.1.0-1</Version>
+ <ReleaseDate>2010-09-21</ReleaseDate>
+ <Script>installscript.qs</Script>
+ <Default>true</Default>
+ <UpdateFile UncompressedSize="61"
+ CompressedSize="61"/>
+ <Licenses>
+ <License name="Beer Public License Agreement"
+ file="license.txt"/>
+ </Licenses>
+ </PackageUpdate>
+ <PackageUpdate>
+ <Name>com.vendor.second.product.sub1</Name>
+ <DisplayName>A subcomponent for the second root</DisplayName>
+ <Description>Install this example.</Description>
+ <Version>0.1.0-1</Version>
+ <ReleaseDate>2010-09-21</ReleaseDate>
+ <Default>false</Default>
+ <Script>installscript.qs</Script>
+ <UpdateFile UncompressedSize="61"
+ CompressedSize="61"/>
+ <Licenses>
+ <License name="Beer Public License Agreement"
+ file="license.txt"/>
+ </Licenses>
+ </PackageUpdate>
+ <PackageUpdate>
+ <Name>com.vendor.second.product.virtual</Name>
+ <DisplayName>A virtual subcomponent for the second root</DisplayName>
+ <Description>Install this example.</Description>
+ <Version>0.1.0-1</Version>
+ <ReleaseDate>2010-09-21</ReleaseDate>
+ <Script>installscript.qs</Script>
+ <Virtual>true</Virtual>
+ <UpdateFile UncompressedSize="61"
+ CompressedSize="61"/>
+ <Licenses>
+ <License name="Beer Public License Agreement"
+ file="license.txt"/>
+ </Licenses>
+ </PackageUpdate>
+ <PackageUpdate>
+ <Name>com.vendor.third.product.virtual</Name>
+ <DisplayName>A virtual root component</DisplayName>
+ <Description>Install this example.</Description>
+ <Version>0.1.0-1</Version>
+ <ReleaseDate>2010-09-21</ReleaseDate>
+ <Default>true</Default>
+ <Script>installscript.qs</Script>
+ <SortingPriority>0</SortingPriority>
+ <Virtual>true</Virtual>
+ <UpdateFile UncompressedSize="61"
+ CompressedSize="61"/>
+ <Licenses>
+ <License name="Beer Public License Agreement"
+ file="license.txt"/>
+ </Licenses>
+ </PackageUpdate>
+</Updates>
diff --git a/tests/auto/installer/componentmodel/tst_componentmodel.cpp b/tests/auto/installer/componentmodel/tst_componentmodel.cpp
new file mode 100644
index 000000000..cfb83a27e
--- /dev/null
+++ b/tests/auto/installer/componentmodel/tst_componentmodel.cpp
@@ -0,0 +1,479 @@
+#include "component.h"
+#include "componentmodel.h"
+
+#include "kdupdaterupdatesinfo_p.h"
+#include "kdupdaterupdatesinfo.cpp"
+#include "kdupdaterupdatesourcesinfo.h"
+
+#include "packagemanagercore.h"
+
+#include <QTest>
+
+using namespace KDUpdater;
+using namespace QInstaller;
+
+#define EXPECTED_COUNT_VIRTUALS_VISIBLE 8
+#define EXPECTED_COUNT_VIRTUALS_INVISIBLE 7
+
+static const char vendorProduct[] = "com.vendor.product";
+static const char vendorSecondProduct[] = "com.vendor.second.product";
+static const char vendorSecondProductSub[] = "com.vendor.second.product.sub";
+static const char vendorSecondProductSub1[] = "com.vendor.second.product.sub1";
+static const char vendorSecondProductVirtual[] = "com.vendor.second.product.virtual";
+static const char vendorSecondProductSubnode[] = "com.vendor.second.product.subnode";
+static const char vendorSecondProductSubnodeSub[] = "com.vendor.second.product.subnode.sub";
+static const char vendorThirdProductVirtual[] = "com.vendor.third.product.virtual";
+
+class tst_ComponentModel : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum Option {
+ NoFlags = 0x00,
+ VirtualsVisible = 0x01,
+ NoForcedInstallation = 0x02
+ };
+ Q_DECLARE_FLAGS(Options, Option);
+
+private slots:
+ void initTestCase()
+ {
+ m_defaultChecked << vendorProduct << vendorSecondProductSub;
+ m_defaultPartially << vendorSecondProduct;
+ m_defaultUnchecked << vendorSecondProductSub1 << vendorSecondProductSubnode
+ << vendorSecondProductSubnodeSub;
+ }
+
+ void testNameToIndexAndIndexToName()
+ {
+ setPackageManagerOptions(NoFlags);
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+
+ // all names should be resolvable, virtual components are not indexed if they are not visible
+ QStringList all;
+ all << m_defaultChecked << m_defaultPartially << m_defaultUnchecked;
+ foreach (const QString &name, all) {
+ QVERIFY(model.indexFromComponentName(name).isValid());
+ QVERIFY(model.componentFromIndex(model.indexFromComponentName(name)) != 0);
+ QCOMPARE(model.componentFromIndex(model.indexFromComponentName(name))->name(), name);
+ }
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testNameToIndexAndIndexToNameVirtualsVisible()
+ {
+ setPackageManagerOptions(VirtualsVisible);
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+
+ // all names should be resolvable, including virtual components
+ QStringList all;
+ all << m_defaultChecked << m_defaultPartially << m_defaultUnchecked << vendorSecondProductVirtual
+ << vendorThirdProductVirtual;
+ foreach (const QString &name, all) {
+ QVERIFY(model.indexFromComponentName(name).isValid());
+ QVERIFY(model.componentFromIndex(model.indexFromComponentName(name)) != 0);
+ QCOMPARE(model.componentFromIndex(model.indexFromComponentName(name))->name(), name);
+ }
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testDefault()
+ {
+ setPackageManagerOptions(NoFlags);
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+ testDefaultInheritedModelBehavior(&model, 1);
+
+ QCOMPARE(model.core(), &m_core);
+ QCOMPARE(model.checkedState(), ComponentModel::DefaultChecked);
+ testModelState(&model, m_defaultChecked, m_defaultPartially, m_defaultUnchecked);
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testVirtualsVisible()
+ {
+ setPackageManagerOptions(VirtualsVisible);
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+ testDefaultInheritedModelBehavior(&model, 1);
+
+ QCOMPARE(model.checkedState(), ComponentModel::DefaultChecked);
+ // the virtual components are not checked
+ testModelState(&model, m_defaultChecked, m_defaultPartially, m_defaultUnchecked
+ + QStringList(vendorSecondProductVirtual) << vendorThirdProductVirtual);
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testNoForcedInstallation()
+ {
+ setPackageManagerOptions(NoForcedInstallation);
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+ testDefaultInheritedModelBehavior(&model, 1);
+
+ QCOMPARE(model.checkedState(), ComponentModel::DefaultChecked);
+ testModelState(&model, m_defaultChecked, m_defaultPartially, m_defaultUnchecked);
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testVirtualsVisibleNoForcedInstallation()
+ {
+ setPackageManagerOptions(Options(VirtualsVisible | NoForcedInstallation));
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+ testDefaultInheritedModelBehavior(&model, 1);
+
+ QCOMPARE(model.checkedState(), ComponentModel::DefaultChecked);
+ // the virtual components are not checked
+ testModelState(&model, m_defaultChecked, m_defaultPartially, m_defaultUnchecked
+ + QStringList(vendorSecondProductVirtual) << vendorThirdProductVirtual);
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testSelect()
+ {
+ setPackageManagerOptions(NoFlags);
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+ testDefaultInheritedModelBehavior(&model, 1);
+
+ // select all possible components
+ model.setCheckedState(ComponentModel::AllChecked);
+ QCOMPARE(model.checkedState(), ComponentModel::AllChecked);
+ testModelState(&model, m_defaultChecked + m_defaultPartially + m_defaultUnchecked, QStringList()
+ , QStringList());
+
+ // deselect all possible components
+ // as the first root is a forced install, should result in partially checked state
+ model.setCheckedState(ComponentModel::AllUnchecked);
+ QCOMPARE(model.checkedState(), ComponentModel::PartiallyChecked);
+ testModelState(&model, QStringList() << vendorProduct, QStringList(), m_defaultPartially
+ + m_defaultUnchecked + QStringList(vendorSecondProductSub));
+
+ // reset all possible components
+ model.setCheckedState(ComponentModel::DefaultChecked);
+ QCOMPARE(model.checkedState(), ComponentModel::DefaultChecked);
+ testModelState(&model, m_defaultChecked, m_defaultPartially, m_defaultUnchecked);
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testSelectVirtualsVisible()
+ {
+ setPackageManagerOptions(VirtualsVisible);
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+ testDefaultInheritedModelBehavior(&model, 1);
+
+ // select all possible components
+ model.setCheckedState(ComponentModel::AllChecked);
+ QCOMPARE(model.checkedState(), ComponentModel::AllChecked);
+ testModelState(&model, m_defaultChecked + m_defaultPartially + m_defaultUnchecked
+ + QStringList(vendorSecondProductVirtual) << vendorThirdProductVirtual, QStringList(),
+ QStringList());
+
+ // deselect all possible components
+ // as the first root is a forced install, should result in partially checked state
+ model.setCheckedState(ComponentModel::AllUnchecked);
+ QCOMPARE(model.checkedState(), ComponentModel::PartiallyChecked);
+ testModelState(&model, QStringList() << vendorProduct, QStringList(), m_defaultPartially
+ + m_defaultUnchecked + QStringList(vendorSecondProductSub) << vendorSecondProductVirtual
+ << vendorThirdProductVirtual);
+
+ // reset all possible components
+ model.setCheckedState(ComponentModel::DefaultChecked);
+ QCOMPARE(model.checkedState(), ComponentModel::DefaultChecked);
+ testModelState(&model, m_defaultChecked, m_defaultPartially, m_defaultUnchecked
+ + QStringList(vendorSecondProductVirtual) << vendorThirdProductVirtual);
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testSelectNoForcedInstallation()
+ {
+ setPackageManagerOptions(NoForcedInstallation);
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+ testDefaultInheritedModelBehavior(&model, 1);
+
+ // select all possible components
+ model.setCheckedState(ComponentModel::AllChecked);
+ QCOMPARE(model.checkedState(), ComponentModel::AllChecked);
+ testModelState(&model, m_defaultChecked + m_defaultPartially + m_defaultUnchecked, QStringList()
+ , QStringList());
+
+ // deselect all possible components
+ model.setCheckedState(ComponentModel::AllUnchecked);
+ QCOMPARE(model.checkedState(), ComponentModel::AllUnchecked);
+ testModelState(&model, QStringList(), QStringList(), m_defaultPartially + m_defaultUnchecked
+ + QStringList(vendorSecondProductSub) << vendorProduct);
+
+ // reset all possible components
+ model.setCheckedState(ComponentModel::DefaultChecked);
+ QCOMPARE(model.checkedState(), ComponentModel::DefaultChecked);
+ testModelState(&model, m_defaultChecked, m_defaultPartially, m_defaultUnchecked);
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+ void testSelectVirtualsVisibleNoForcedInstallation()
+ {
+ setPackageManagerOptions(Options(VirtualsVisible | NoForcedInstallation));
+
+ QList<Component*> rootComponents = loadComponents();
+ testComponentsLoaded(rootComponents);
+
+ // setup the model with 1 column
+ ComponentModel model(1, &m_core);
+ model.setRootComponents(rootComponents);
+ testDefaultInheritedModelBehavior(&model, 1);
+
+ // select all possible components
+ model.setCheckedState(ComponentModel::AllChecked);
+ QCOMPARE(model.checkedState(), ComponentModel::AllChecked);
+ testModelState(&model, m_defaultChecked + m_defaultPartially + m_defaultUnchecked
+ + QStringList(vendorSecondProductVirtual) << vendorThirdProductVirtual, QStringList(),
+ QStringList());
+
+ // deselect all possible components
+ model.setCheckedState(ComponentModel::AllUnchecked);
+ QCOMPARE(model.checkedState(), ComponentModel::AllUnchecked);
+ testModelState(&model, QStringList(), QStringList(), m_defaultPartially + m_defaultUnchecked
+ + QStringList(vendorSecondProductSub) << vendorSecondProductVirtual << vendorProduct
+ << vendorThirdProductVirtual);
+
+ // reset all possible components
+ model.setCheckedState(ComponentModel::DefaultChecked);
+ QCOMPARE(model.checkedState(), ComponentModel::DefaultChecked);
+ testModelState(&model, m_defaultChecked, m_defaultPartially, m_defaultUnchecked
+ + QStringList(vendorSecondProductVirtual) << vendorThirdProductVirtual);
+
+ foreach (Component *const component, rootComponents)
+ delete component;
+ }
+
+private:
+ void setPackageManagerOptions(Options flags) const
+ {
+ m_core.setNoForceInstallation(flags.testFlag(NoForcedInstallation));
+ m_core.setVirtualComponentsVisible(flags.testFlag(VirtualsVisible));
+ }
+
+ void testComponentsLoaded(const QList<Component *> &rootComponents) const
+ {
+ // we need to have three root components
+ QCOMPARE(rootComponents.count(), 3);
+
+ QList<Component*> components = rootComponents;
+ foreach (Component *const component, rootComponents)
+ components.append(component->childItems());
+
+ // will differ between 6 and 7 components, (2 root nodes, 1 sub node, 3 non virtual and 1 virtual)
+ QCOMPARE(components.count(), m_core.virtualComponentsVisible() ? EXPECTED_COUNT_VIRTUALS_VISIBLE
+ : EXPECTED_COUNT_VIRTUALS_INVISIBLE);
+ }
+
+ void testDefaultInheritedModelBehavior(ComponentModel *model, int columnCount) const
+ {
+ // row count with invalid model index should return:
+ if (m_core.virtualComponentsVisible())
+ QCOMPARE(model->rowCount(), 3); // 3 (2 non virtual and 1 virtual root component)
+ else
+ QCOMPARE(model->rowCount(), 2); // 2 (the 2 non virtual root components)
+ QCOMPARE(model->columnCount(), columnCount);
+
+ const QModelIndex firstParent = model->indexFromComponentName(vendorProduct);
+ const QModelIndex secondParent = model->indexFromComponentName(vendorSecondProduct);
+ const QModelIndex thirdParent = model->indexFromComponentName(vendorThirdProductVirtual);
+
+ // return invalid indexes, as they are the root components
+ QCOMPARE(model->parent(firstParent), QModelIndex());
+ QCOMPARE(model->parent(secondParent), QModelIndex());
+ QCOMPARE(model->parent(thirdParent), QModelIndex());
+
+ // test valid indexes
+ QVERIFY(firstParent.isValid());
+ QVERIFY(secondParent.isValid());
+ QVERIFY(model->indexFromComponentName(vendorSecondProductSub).isValid());
+ QVERIFY(model->indexFromComponentName(vendorSecondProductSub1).isValid());
+ QVERIFY(model->indexFromComponentName(vendorSecondProductSubnode).isValid());
+ QVERIFY(model->indexFromComponentName(vendorSecondProductSubnodeSub).isValid());
+
+ // they should have the same parent
+ QCOMPARE(model->parent(model->indexFromComponentName(vendorSecondProductSub)), secondParent);
+ QCOMPARE(model->parent(model->indexFromComponentName(vendorSecondProductSub1)), secondParent);
+ QCOMPARE(model->parent(model->indexFromComponentName(vendorSecondProductSubnode)), secondParent);
+
+ const QModelIndex forthParent = model->indexFromComponentName(vendorSecondProductSubnode);
+ QCOMPARE(model->parent(model->indexFromComponentName(vendorSecondProductSubnodeSub)), forthParent);
+
+ // row count should be 0, as they have no children
+ QCOMPARE(model->rowCount(firstParent), 0);
+ QCOMPARE(model->rowCount(model->indexFromComponentName(vendorSecondProductSub)), 0);
+ QCOMPARE(model->rowCount(model->indexFromComponentName(vendorSecondProductSub1)), 0);
+ QCOMPARE(model->rowCount(model->indexFromComponentName(vendorSecondProductSubnodeSub)), 0);
+
+ if (m_core.virtualComponentsVisible()) {
+ // test valid index
+ QVERIFY(thirdParent.isValid());
+ QCOMPARE(model->rowCount(thirdParent), 0);
+
+ QVERIFY(model->indexFromComponentName(vendorSecondProductVirtual).isValid());
+ QCOMPARE(model->rowCount(model->indexFromComponentName(vendorSecondProductVirtual)), 0);
+ QCOMPARE(model->parent(model->indexFromComponentName(vendorSecondProductVirtual)), secondParent);
+ // row count should be 4, (2 standard, 1 virtual, 1 sub node)
+ QCOMPARE(model->rowCount(secondParent), 4);
+ } else {
+ // test invalid index
+ QVERIFY(!thirdParent.isValid());
+
+ // row count should be 3, (2 standard, 1 sub node, omit the virtual)
+ QCOMPARE(model->rowCount(secondParent), 3);
+ }
+ // row count should be 1, 1 sub node
+ QCOMPARE(model->rowCount(model->indexFromComponentName(vendorSecondProductSubnode)), 1);
+ }
+
+ void testModelState(ComponentModel *model, const QStringList &checked, const QStringList &partially,
+ const QStringList &unchecked) const
+ {
+ QCOMPARE(model->checked().count(), checked.count());
+ QCOMPARE(model->partially().count(), partially.count());
+ QCOMPARE(model->unchecked().count(), unchecked.count());
+
+ // these components should have checked state
+ foreach (Component *const component, model->checked())
+ QVERIFY(checked.contains(component->name()));
+
+ // these components should not have partially checked state
+ foreach (Component *const component, model->partially())
+ QVERIFY(partially.contains(component->name()));
+
+ // these components should not have checked state
+ foreach (Component *const component, model->unchecked())
+ QVERIFY(unchecked.contains(component->name()));
+ }
+
+ QList<Component*> loadComponents() const
+ {
+ UpdatesInfo updatesInfo;
+ updatesInfo.setFileName(":///data/updates.xml");
+ const QList<UpdateInfo> updateInfos = updatesInfo.updatesInfo(PackageUpdate);
+
+ QHash<QString, Component*> components;
+ foreach (const UpdateInfo &info, updateInfos) {
+ Component *component = new Component(const_cast<PackageManagerCore *>(&m_core));
+
+ // we need at least these to be able to test the model
+ component->setValue("Name", info.data.value("Name").toString());
+ component->setValue("Default", info.data.value("Default").toString());
+ component->setValue("Virtual", info.data.value("Virtual").toString());
+ component->setValue("DisplayName", info.data.value("DisplayName").toString());
+
+ QString forced = info.data.value("ForcedInstallation", scFalse).toString().toLower();
+ if (m_core.noForceInstallation())
+ forced = scFalse;
+ component->setValue("ForcedInstallation", forced);
+ if (forced == scTrue) {
+ component->setEnabled(false);
+ component->setCheckable(false);
+ component->setCheckState(Qt::Checked);
+ }
+ components.insert(component->name(), component);
+ }
+
+ QList <Component*> rootComponents;
+ foreach (QString id, components.keys()) {
+ QInstaller::Component *component = components.value(id);
+ while (!id.isEmpty() && component->parentComponent() == 0) {
+ id = id.section(QLatin1Char('.'), 0, -2);
+ if (components.contains(id))
+ components[id]->appendComponent(component);
+ }
+ }
+
+ foreach (QInstaller::Component *component, components) {
+ if (component->parentComponent() == 0)
+ rootComponents.append(component);
+
+ if (component->isCheckable() && component->isDefault() && (!component->isTristate()))
+ component->setCheckState(Qt::Checked);
+ }
+ return rootComponents;
+ }
+
+private:
+ PackageManagerCore m_core;
+
+ QStringList m_defaultChecked;
+ QStringList m_defaultPartially;
+ QStringList m_defaultUnchecked;
+};
+Q_DECLARE_OPERATORS_FOR_FLAGS(tst_ComponentModel::Options)
+
+QTEST_MAIN(tst_ComponentModel)
+
+#include "tst_componentmodel.moc"
diff --git a/tests/auto/installer/installer.pro b/tests/auto/installer/installer.pro
index 911e1ad75..67a454db7 100644
--- a/tests/auto/installer/installer.pro
+++ b/tests/auto/installer/installer.pro
@@ -3,4 +3,5 @@ TEMPLATE = subdirs
SUBDIRS += \
settings \
repository \
+ componentmodel \
fakestopprocessforupdateoperation