Commit 7a80f2f0 authored by hjk's avatar hjk

TreeModel: Take responsibility for some of the casting

This adds a templated layer on top of TreeModel that can specify
item types for the top three layers in the model, relieving user
code from some of the previously necessary type casting.

Two common setups get an extra layer with convenience functions
on top: TwoLevelTreeModel for two-level model with a first level
of static headers and a uniform second level, and UniformTreeModel
where all non-root nodes are the same.

"Untyped" plain TreeModels are still possible.

The walkTree() feature and untyped iteration in the base
TreeItem and TreeModel is retained for now to ease transition
in downstream modules, but is planned to be removed soon.

Change-Id: I67d75a1a4e18e8f254dbfb458db03510d8990d8b
Reviewed-by: Eike Ziller's avatarEike Ziller <eike.ziller@qt.io>
parent ded0b1c4
......@@ -315,8 +315,8 @@ PluginView::PluginView(QWidget *parent)
m_categoryView->setSelectionMode(QAbstractItemView::SingleSelection);
m_categoryView->setSelectionBehavior(QAbstractItemView::SelectRows);
m_model = new TreeModel(this);
m_model->setHeader(QStringList() << tr("Name") << tr("Load") << tr("Version") << tr("Vendor"));
m_model = new LeveledTreeModel<CollectionItem, PluginItem>(this);
m_model->setHeader({ tr("Name"), tr("Load"), tr("Version"), tr("Vendor") });
m_sortModel = new CategorySortFilterModel(this);
m_sortModel->setSourceModel(m_model);
......@@ -369,7 +369,7 @@ void PluginView::setFilter(const QString &filter)
PluginSpec *PluginView::pluginForIndex(const QModelIndex &index) const
{
const QModelIndex &sourceIndex = m_sortModel->mapToSource(index);
auto item = dynamic_cast<PluginItem *>(m_model->itemForIndex(sourceIndex));
PluginItem *item = m_model->secondLevelItemForIndex(sourceIndex);
return item ? item->m_spec: 0;
}
......@@ -456,7 +456,7 @@ bool PluginView::setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enabl
QSet<PluginSpec *> affectedPlugins = plugins + additionalPlugins;
foreach (PluginSpec *spec, affectedPlugins) {
PluginItem *item = m_model->findItemAtLevel<PluginItem *>(2, [spec](PluginItem *item) {
PluginItem *item = m_model->findSecondLevelItem([spec](PluginItem *item) {
return item->m_spec == spec;
});
QTC_ASSERT(item, continue);
......
......@@ -27,6 +27,8 @@
#include "extensionsystem_global.h"
#include <utils/treemodel.h>
#include <QWidget>
#include <QSet>
#include <QHash>
......@@ -35,11 +37,7 @@ QT_BEGIN_NAMESPACE
class QSortFilterProxyModel;
QT_END_NAMESPACE
namespace Utils {
class TreeItem;
class TreeModel;
class TreeView;
} // namespace Utils
namespace Utils { class TreeView; }
namespace ExtensionSystem {
......@@ -73,7 +71,7 @@ private:
bool setPluginsEnabled(const QSet<PluginSpec *> &plugins, bool enable);
Utils::TreeView *m_categoryView;
Utils::TreeModel *m_model;
Utils::LeveledTreeModel<Internal::CollectionItem, Internal::PluginItem> *m_model;
QSortFilterProxyModel *m_sortModel;
friend class Internal::CollectionItem;
......
......@@ -94,37 +94,91 @@ public:
void walkTree(TreeItemVisitor *visitor);
void walkTree(std::function<void(TreeItem *)> f);
template <class T, class Predicate>
void forSelectedChildren(const Predicate &pred) const {
foreach (TreeItem *item, m_children) {
if (pred(static_cast<T>(item)))
item->forSelectedChildren<T, Predicate>(pred);
}
}
// Levels are 1-based: Child at Level 1 is an immediate child.
template <class T, typename Function>
void forEachChildAtLevel(int n, Function func) {
foreach (auto item, m_children) {
if (n == 1)
func(static_cast<T>(item));
else
item->forEachChildAtLevel<T>(n - 1, func);
template <class T, typename Predicate>
void forAllChildren(const Predicate &pred) const {
foreach (TreeItem *item, m_children) {
pred(static_cast<T>(item));
item->forAllChildren<T, Predicate>(pred);
}
}
template <class T, typename Function>
void forEachChild(Function func) const {
forEachChildAtLevel<T>(1, func);
// Levels are 1-based: Child at Level 1 is an immediate child.
template <class T, typename Predicate>
void forFirstLevelChildren(Predicate pred) {
foreach (TreeItem *item, m_children)
pred(static_cast<T>(item));
}
template <class T, typename Predicate>
void forSecondLevelChildren(Predicate pred) {
foreach (TreeItem *item1, m_children)
foreach (TreeItem *item2, item1->m_children)
pred(static_cast<T>(item2));
}
template <class T, typename Predicate>
T findFirstLevelChild(Predicate pred) const {
foreach (TreeItem *item, m_children)
if (pred(static_cast<T>(item)))
return static_cast<T>(item);
return 0;
}
template <class T, typename Predicate>
T findSecondLevelChild(Predicate pred) const {
foreach (TreeItem *item1, m_children)
foreach (TreeItem *item2, item1->children())
if (pred(static_cast<T>(item2)))
return static_cast<T>(item2);
return 0;
}
// FIXME: Remove. Should only be present in LevelModels
template <class T, typename Predicate>
T findChildAtLevel(int n, Predicate func) const {
if (n == 1) {
foreach (auto item, m_children)
foreach (TreeItem *item, m_children)
if (func(static_cast<T>(item)))
return static_cast<T>(item);
} else {
foreach (auto item, m_children)
foreach (TreeItem *item, m_children)
if (T found = item->findChildAtLevel<T>(n - 1, func))
return found;
}
return 0;
}
// Levels are 1-based: Child at Level 1 is an immediate child.
// FIXME: Remove. Should only be present in LevelModels
template <class T, typename Function>
void forEachChildAtLevel(int n, Function func) {
foreach (TreeItem *item, m_children) {
if (n == 1)
func(static_cast<T>(item));
else
item->forEachChildAtLevel<T>(n - 1, func);
}
}
template <class T, typename Predicate>
T findAnyChild(Predicate pred) const {
foreach (TreeItem *item, m_children) {
if (pred(static_cast<T>(item)))
return static_cast<T>(item);
if (T found = item->findAnyChild<T>(pred))
return found;
}
return 0;
}
private:
TreeItem(const TreeItem &) Q_DECL_EQ_DELETE;
void operator=(const TreeItem &) Q_DECL_EQ_DELETE;
......@@ -141,6 +195,8 @@ private:
friend class TreeModel;
};
// A general purpose multi-level model where each item can have its
// own (TreeItem-derived) type.
class QTCREATOR_UTILS_EXPORT TreeModel : public QAbstractItemModel
{
Q_OBJECT
......@@ -174,22 +230,24 @@ public:
bool canFetchMore(const QModelIndex &idx) const override;
void fetchMore(const QModelIndex &idx) override;
template <class T, typename Function>
void forEachItemAtLevel(int n, Function func) const {
m_root->forEachChildAtLevel<T>(n, func);
}
TreeItem *takeItem(TreeItem *item); // item is not destroyed.
// FIXME: Remove. Should only be uses in LeveledTreeModel
template <class T, typename Predicate>
T findItemAtLevel(int n, Predicate func) const {
return m_root->findChildAtLevel<T>(n, func);
}
// FIXME: Remove. Should only be uses in LeveledTreeModel
template <class T, typename Function>
void forEachItemAtLevel(int n, Function func) const {
m_root->forEachChildAtLevel<T>(n, func);
}
TreeItem *takeItem(TreeItem *item); // item is not destroyed.
signals:
void requestExpansion(QModelIndex);
private:
protected:
friend class TreeItem;
TreeItem *m_root; // Owned.
......@@ -198,4 +256,102 @@ private:
int m_columnCount;
};
// A multi-level model with uniform types per level.
// All items below second level have to have identitical types.
template <class FirstLevelItem,
class SecondLevelItem = FirstLevelItem,
class RootItem = TreeItem>
class LeveledTreeModel : public TreeModel
{
public:
explicit LeveledTreeModel(QObject *parent = 0) : TreeModel(parent) {}
explicit LeveledTreeModel(RootItem *root, QObject *parent = 0) : TreeModel(root, parent) {}
template <class Predicate>
void forFirstLevelItems(const Predicate &pred) const {
m_root->forFirstLevelChildren<FirstLevelItem *>(pred);
}
template <class Predicate>
void forSecondLevelItems(const Predicate &pred) const {
m_root->forSecondLevelChildren<SecondLevelItem *>(pred);
}
template <class Predicate>
FirstLevelItem *findFirstLevelItem(const Predicate &pred) const {
return m_root->findFirstLevelChild<FirstLevelItem *>(pred);
}
template <class Predicate>
SecondLevelItem *findSecondLevelItem(const Predicate &pred) const {
return m_root->findSecondLevelChild<SecondLevelItem *>(pred);
}
RootItem *rootItem() const {
return static_cast<RootItem *>(TreeModel::rootItem());
}
FirstLevelItem *firstLevelItemForIndex(const QModelIndex &idx) const {
TreeItem *item = TreeModel::itemForIndex(idx);
return item && item->level() == 1 ? static_cast<FirstLevelItem *>(item) : 0;
}
SecondLevelItem *secondLevelItemForIndex(const QModelIndex &idx) const {
TreeItem *item = TreeModel::itemForIndex(idx);
return item && item->level() == 2 ? static_cast<SecondLevelItem *>(item) : 0;
}
};
// A two-level model with a first level of static headers and a uniform second level.
template <class SecondLevelItemType>
class TwoLevelTreeModel : public LeveledTreeModel<TreeItem, SecondLevelItemType>
{
public:
using FirstLevelItem = TreeItem;
using SecondLevelItem = SecondLevelItemType;
using BaseType = LeveledTreeModel<FirstLevelItem, SecondLevelItem>;
explicit TwoLevelTreeModel(QObject *parent = 0) : BaseType(parent) {}
FirstLevelItem *appendFirstLevelItem(const QStringList &display) {
auto item = new FirstLevelItem(display);
this->rootItem()->appendChild(item);
return item;
}
};
// A model where all non-root nodes are the same.
template <class ItemType>
class UniformTreeModel : public LeveledTreeModel<ItemType, ItemType, ItemType>
{
public:
using BaseType = LeveledTreeModel<ItemType, ItemType, ItemType>;
explicit UniformTreeModel(QObject *parent = 0) : BaseType(parent) {}
ItemType *nonRootItemForIndex(const QModelIndex &idx) const {
TreeItem *item = TreeModel::itemForIndex(idx);
return item && item->parent() ? static_cast<ItemType *>(item) : 0;
}
template <class Predicate>
ItemType *findNonRooItem(const Predicate &pred) const {
TreeItem *root = this->rootItem();
return root->findAnyChild<ItemType *>(pred);
}
template <class Predicate>
void forSelectedItems(const Predicate &pred) const {
TreeItem *root = this->rootItem();
root->forSelectedChildren<ItemType *, Predicate>(pred);
}
template <class Predicate>
void forAllItems(const Predicate &pred) const {
TreeItem *root = this->rootItem();
root->forAllChildren<ItemType *, Predicate>(pred);
}
};
} // namespace Utils
......@@ -99,7 +99,7 @@ public:
// DebuggerItemModel
// --------------------------------------------------------------------------
class DebuggerItemModel : public TreeModel
class DebuggerItemModel : public TwoLevelTreeModel<DebuggerTreeItem>
{
Q_DECLARE_TR_FUNCTIONS(Debugger::DebuggerOptionsPage)
......@@ -124,10 +124,9 @@ private:
DebuggerItemModel::DebuggerItemModel()
: m_currentTreeItem(0)
{
setHeader(QStringList() << tr("Name") << tr("Location") << tr("Type"));
rootItem()->appendChild(new TreeItem(QStringList() << tr("Auto-detected") << QString() << QString()));
rootItem()->appendChild(new TreeItem(QStringList() << tr("Manual") << QString() << QString()));
setHeader({ tr("Name"), tr("Location"), tr("Type") });
appendFirstLevelItem({ tr("Auto-detected") });
appendFirstLevelItem({ tr("Manual") });
foreach (const DebuggerItem &item, DebuggerItemManager::debuggers())
addDebugger(item, false);
}
......@@ -141,7 +140,7 @@ void DebuggerItemModel::addDebugger(const DebuggerItem &item, bool changed)
void DebuggerItemModel::updateDebugger(const DebuggerItem &item)
{
auto matcher = [item](DebuggerTreeItem *n) { return n->m_item.m_id == item.id(); };
DebuggerTreeItem *treeItem = findItemAtLevel<DebuggerTreeItem *>(2, matcher);
DebuggerTreeItem *treeItem = findSecondLevelItem(matcher);
QTC_ASSERT(treeItem, return);
TreeItem *parent = treeItem->parent();
......@@ -180,7 +179,7 @@ void DebuggerItemModel::apply()
foreach (const QVariant &id, m_removedItems)
DebuggerItemManager::deregisterDebugger(id);
forEachItemAtLevel<DebuggerTreeItem *>(2, [](DebuggerTreeItem *item) {
forSecondLevelItems([](DebuggerTreeItem *item) {
item->m_changed = false;
DebuggerItemManager::updateOrAddDebugger(item->m_item);
});
......
......@@ -231,16 +231,14 @@ ToolTipWatchItem::ToolTipWatchItem(TreeItem *item)
//
/////////////////////////////////////////////////////////////////////////
class ToolTipModel : public TreeModel
class ToolTipModel : public UniformTreeModel<ToolTipWatchItem>
{
public:
ToolTipModel()
{
QStringList headers;
headers.append(DebuggerToolTipManager::tr("Name"));
headers.append(DebuggerToolTipManager::tr("Value"));
headers.append(DebuggerToolTipManager::tr("Type"));
setHeader(headers);
setHeader({ DebuggerToolTipManager::tr("Name"),
DebuggerToolTipManager::tr("Value"),
DebuggerToolTipManager::tr("Type") });
m_enabled = true;
auto item = new ToolTipWatchItem;
item->expandable = true;
......@@ -538,10 +536,9 @@ DebuggerToolTipWidget::DebuggerToolTipWidget()
connect(copyButton, &QAbstractButton::clicked, [this] {
QString text;
QTextStream str(&text);
model.rootItem()->walkTree([&str](TreeItem *item) {
auto titem = static_cast<ToolTipWatchItem *>(item);
model.forAllItems([&str](ToolTipWatchItem *item) {
str << QString(item->level(), QLatin1Char('\t'))
<< titem->name << '\t' << titem->value << '\t' << titem->type << '\n';
<< item->name << '\t' << item->value << '\t' << item->type << '\n';
});
QClipboard *clipboard = QApplication::clipboard();
clipboard->setText(text, QClipboard::Selection);
......@@ -926,7 +923,7 @@ void DebuggerToolTipHolder::saveSessionData(QXmlStreamWriter &w) const
w.writeAttributes(attributes);
w.writeStartElement(QLatin1String(treeElementC));
widget->model.rootItem()->walkTree([&w](TreeItem *item) {
widget->model.forAllItems([&w](ToolTipWatchItem *item) {
const QString modelItemElement = QLatin1String(modelItemElementC);
for (int i = 0; i < 3; ++i) {
const QString value = item->data(i, Qt::DisplayRole).toString();
......
......@@ -240,7 +240,6 @@ ThreadsHandler::ThreadsHandler()
{
m_resetLocationScheduled = false;
setObjectName(QLatin1String("ThreadsModel"));
setRootItem(new ThreadItem(this));
setHeader({
QLatin1String(" ") + tr("ID") + QLatin1String(" "),
tr("Address"), tr("Function"), tr("File"), tr("Line"), tr("State"),
......@@ -251,7 +250,7 @@ ThreadsHandler::ThreadsHandler()
static ThreadItem *itemForThreadId(const ThreadsHandler *handler, ThreadId threadId)
{
const auto matcher = [threadId](ThreadItem *item) { return item->threadData.id == threadId; };
return handler->findItemAtLevel<ThreadItem *>(1, matcher);
return handler->findFirstLevelItem(matcher);
}
static int indexForThreadId(const ThreadsHandler *handler, ThreadId threadId)
......@@ -321,11 +320,6 @@ void ThreadsHandler::notifyGroupCreated(const QByteArray &groupId, const QByteAr
m_pidForGroupId[groupId] = pid;
}
void ThreadsHandler::foreachThread(const std::function<void (ThreadItem *)> &func)
{
forEachItemAtLevel<ThreadItem *>(1, func);
}
void ThreadsHandler::updateThread(const ThreadData &threadData)
{
if (ThreadItem *item = itemForThreadId(this, threadData.id))
......@@ -354,7 +348,7 @@ void ThreadsHandler::setThreads(const Threads &threads)
void ThreadsHandler::updateThreadBox()
{
QStringList list;
foreachThread([&list](ThreadItem *item) {
forFirstLevelItems([&list](ThreadItem *item) {
list.append(QString::fromLatin1("#%1 %2").arg(item->threadData.id.raw()).arg(item->threadData.name));
});
Internal::setThreadBoxContents(list, indexForThreadId(this, m_currentId));
......@@ -375,7 +369,7 @@ void ThreadsHandler::removeAll()
bool ThreadsHandler::notifyGroupExited(const QByteArray &groupId)
{
QList<ThreadItem *> list;
foreachThread([&list, groupId](ThreadItem *item) {
forFirstLevelItems([&list, groupId](ThreadItem *item) {
if (item->threadData.groupId == groupId)
list.append(item);
});
......@@ -402,7 +396,7 @@ void ThreadsHandler::notifyRunning(const QByteArray &data)
void ThreadsHandler::notifyAllRunning()
{
foreachThread([](ThreadItem *item) { item->notifyRunning(); });
forFirstLevelItems([](ThreadItem *item) { item->notifyRunning(); });
}
void ThreadsHandler::notifyRunning(ThreadId threadId)
......@@ -427,7 +421,7 @@ void ThreadsHandler::notifyStopped(const QByteArray &data)
void ThreadsHandler::notifyAllStopped()
{
foreachThread([](ThreadItem *item) { item->notifyStopped(); });
forFirstLevelItems([](ThreadItem *item) { item->notifyStopped(); });
}
void ThreadsHandler::notifyStopped(ThreadId threadId)
......
......@@ -41,7 +41,7 @@ namespace Internal {
class GdbMi;
class ThreadItem;
class ThreadsHandler : public Utils::TreeModel
class ThreadsHandler : public Utils::LeveledTreeModel<ThreadItem>
{
Q_OBJECT
......@@ -78,8 +78,6 @@ public:
void resetLocation();
void scheduleResetLocation();
void foreachThread(const std::function<void(ThreadItem *item)> &func);
private:
void updateThreadBox();
......
......@@ -657,18 +657,6 @@ QString WatchItem::expression() const
return name;
}
WatchItem *WatchItem::findItem(const QByteArray &iname)
{
if (internalName() == iname)
return this;
foreach (TreeItem *child, children()) {
auto witem = static_cast<WatchItem *>(child);
if (WatchItem *result = witem->findItem(iname))
return result;
}
return 0;
}
} // namespace Internal
} // namespace Debugger
......@@ -59,7 +59,6 @@ public:
QVariant editValue() const;
int editType() const;
WatchItem *findItem(const QByteArray &iname);
WatchItem *parentItem() const;
enum State
......
......@@ -351,7 +351,6 @@ public:
void reinitialize(bool includeInspectData = false);
WatchItem *findItem(const QByteArray &iname) const;
const WatchItem *watchItem(const QModelIndex &idx) const;
void reexpandItems();
......@@ -368,7 +367,6 @@ public:
bool m_contentsValid;
bool m_resetLocationScheduled;
WatchItem *root() const { return static_cast<WatchItem *>(rootItem()); }
WatchItem *m_localsRoot; // Not owned.
WatchItem *m_inspectorRoot; // Not owned.
WatchItem *m_watchRoot; // Not owned.
......@@ -394,8 +392,7 @@ WatchModel::WatchModel(WatchHandler *handler, DebuggerEngine *engine)
m_contentsValid = true; // FIXME
m_resetLocationScheduled = false;
setHeader(QStringList() << tr("Name") << tr("Value") << tr("Type"));
auto root = new WatchItem;
setHeader({ tr("Name"), tr("Value"), tr("Type") });
m_localsRoot = new WatchItem;
m_localsRoot->iname = "local";
m_localsRoot->name = tr("Locals");
......@@ -411,12 +408,12 @@ WatchModel::WatchModel(WatchHandler *handler, DebuggerEngine *engine)
m_tooltipRoot = new WatchItem;
m_tooltipRoot->iname = "tooltip";
m_tooltipRoot->name = tr("Tooltip");
auto root = rootItem();
root->appendChild(m_localsRoot);
root->appendChild(m_inspectorRoot);
root->appendChild(m_watchRoot);
root->appendChild(m_returnRoot);
root->appendChild(m_tooltipRoot);
setRootItem(root);
m_requestUpdateTimer.setSingleShot(true);
connect(&m_requestUpdateTimer, &QTimer::timeout,
......@@ -444,12 +441,7 @@ void WatchModel::reinitialize(bool includeInspectData)
WatchItem *WatchModel::findItem(const QByteArray &iname) const
{
return root()->findItem(iname);
}
const WatchItem *WatchModel::watchItem(const QModelIndex &idx) const
{
return static_cast<WatchItem *>(itemForIndex(idx));
return findNonRooItem([iname](WatchItem *item) { return item->iname == iname; });
}
static QByteArray parentName(const QByteArray &iname)
......@@ -912,7 +904,7 @@ QVariant WatchModel::data(const QModelIndex &idx, int role) const
l.append(indexForItem(item));
return QVariant::fromValue(l);
}
const WatchItem *item = watchItem(idx);
const WatchItem *item = nonRootItemForIndex(idx);
if (!item)
return QVariant();
......@@ -1074,7 +1066,7 @@ Qt::ItemFlags WatchModel::flags(const QModelIndex &idx) const
if (!idx.isValid())
return 0;
const WatchItem *item = watchItem(idx);
const WatchItem *item = nonRootItemForIndex(idx);
if (!item)
return Qt::ItemIsEnabled|Qt::ItemIsSelectable;
......@@ -1140,7 +1132,7 @@ bool WatchModel::canFetchMore(const QModelIndex &idx) const
return false;
// See "hasChildren" below.
const WatchItem *item = watchItem(idx);
const WatchItem *item = nonRootItemForIndex(idx);
if (!item)
return false;
if (!item->wantsChildren)
......@@ -1155,7 +1147,7 @@ void WatchModel::fetchMore(const QModelIndex &idx)
if (!idx.isValid())
return;
WatchItem *item = static_cast<WatchItem *>(itemForIndex(idx));
WatchItem *item = nonRootItemForIndex(idx);
if (item) {
m_expandedINames.insert(item->iname);
if (item->children().isEmpty()) {
......@@ -1167,7 +1159,7 @@ void WatchModel::fetchMore(const QModelIndex &idx)
bool WatchModel::hasChildren(const QModelIndex &idx) const
{
const WatchItem *item = watchItem(idx);
const WatchItem *item = nonRootItemForIndex(idx);
if (!item)
return true;
if (item->rowCount() > 0)
......@@ -1310,7 +1302,7 @@ bool WatchHandler::insertItem(WatchItem *item)
item->update();
item->walkTree([this](TreeItem *sub) { m_model->showEditValue(static_cast<WatchItem *>(sub)); });
item->forAllChildren<WatchItem *>([this](WatchItem *sub) { m_model->showEditValue(sub); });
return !found;
}
......@@ -1344,10 +1336,8 @@ void WatchHandler::removeAllData(bool includeInspectData)
void WatchHandler::resetValueCache()
{
m_model->m_valueCache.clear();
TreeItem *root = m_model->rootItem();
root->walkTree([this, root](TreeItem *item) {
auto watchItem = static_cast<WatchItem *>(item);
m_model->m_valueCache[watchItem->iname] = watchItem->value;
m_model->forAllItems([this](WatchItem *item) {
m_model->m_valueCache[item->iname] = item->value;
});
}
......@@ -1361,13 +1351,13 @@ void WatchHandler::notifyUpdateStarted(const QList<QByteArray> &inames)
auto marker = [](TreeItem *it) { static_cast<WatchItem *>(it)->outdated = true; };
if (inames.isEmpty()) {
m_model->forEachItemAtLevel<WatchItem *>(2, [marker](WatchItem *item) {
item->walkTree(marker);
m_model->forSecondLevelItems([marker](WatchItem *item) {
item->forAllChildren<