Qt: Speed up ProtoTreeModel with lots of items

When creating a ProtoNode, count the (non-hidden) children and put
them in a QVector. This saves time having to iterate through all
of a node's children (or the parent's children) each time the
model or view wants to get the row or index number. Create and
delete the needed ProtoNodes when the root node is changed, instead
of recreating them on demand from the proto_nodes (since they're no
longer a thin wrapper.)

Fix #18625
This commit is contained in:
John Thacker 2022-11-12 22:27:42 -05:00
parent 52382b2592
commit b7ed46288a
8 changed files with 161 additions and 113 deletions

View File

@ -23,9 +23,15 @@
// - Add ProtoTreeModel to CaptureFile
ProtoTreeModel::ProtoTreeModel(QObject * parent) :
QAbstractItemModel(parent),
root_node_(0)
{}
QAbstractItemModel(parent)
{
root_node_ = new ProtoNode(NULL);
}
ProtoTreeModel::~ProtoTreeModel()
{
delete root_node_;
}
Qt::ItemFlags ProtoTreeModel::flags(const QModelIndex &index) const
{
@ -39,44 +45,39 @@ Qt::ItemFlags ProtoTreeModel::flags(const QModelIndex &index) const
QModelIndex ProtoTreeModel::index(int row, int, const QModelIndex &parent) const
{
ProtoNode parent_node(root_node_);
ProtoNode *parent_node = root_node_;
if (parent.isValid()) {
// index is not a top level item.
parent_node = protoNodeFromIndex(parent);
}
if (! parent_node.isValid())
if (! parent_node->isValid())
return QModelIndex();
int cur_row = 0;
ProtoNode::ChildIterator kids = parent_node.children();
while (kids.element().isValid())
{
if (cur_row == row)
break;
cur_row++;
kids.next();
}
if (! kids.element().isValid()) {
ProtoNode *child = parent_node->child(row);
if (! child) {
return QModelIndex();
}
return createIndex(row, 0, static_cast<void *>(kids.element().protoNode()));
return createIndex(row, 0, static_cast<void *>(child));
}
QModelIndex ProtoTreeModel::parent(const QModelIndex &index) const
{
ProtoNode parent_node = protoNodeFromIndex(index).parentNode();
if (!index.isValid())
return QModelIndex();
ProtoNode *parent_node = protoNodeFromIndex(index)->parentNode();
return indexFromProtoNode(parent_node);
}
int ProtoTreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return protoNodeFromIndex(parent).childrenCount();
return protoNodeFromIndex(parent)->childrenCount();
}
return ProtoNode(root_node_).childrenCount();
return root_node_->childrenCount();
}
// The QItemDelegate documentation says
@ -87,15 +88,18 @@ int ProtoTreeModel::rowCount(const QModelIndex &parent) const
// We might want to move this to a delegate regardless.
QVariant ProtoTreeModel::data(const QModelIndex &index, int role) const
{
ProtoNode index_node = protoNodeFromIndex(index);
FieldInformation finfo(index_node.protoNode());
if (!index.isValid())
return QVariant();
ProtoNode *index_node = protoNodeFromIndex(index);
FieldInformation finfo(index_node);
if (!finfo.isValid()) {
return QVariant();
}
switch (role) {
case Qt::DisplayRole:
return index_node.labelText();
return index_node->labelText();
case Qt::BackgroundRole:
{
switch(finfo.flag(PI_SEVERITY_MASK)) {
@ -148,45 +152,55 @@ QVariant ProtoTreeModel::data(const QModelIndex &index, int role) const
void ProtoTreeModel::setRootNode(proto_node *root_node)
{
beginResetModel();
root_node_ = root_node;
delete root_node_;
root_node_ = new ProtoNode(root_node);
endResetModel();
if (!root_node) return;
int row_count = ProtoNode(root_node_).childrenCount();
int row_count = root_node_->childrenCount();
if (row_count < 1) return;
beginInsertRows(QModelIndex(), 0, row_count - 1);
endInsertRows();
}
ProtoNode ProtoTreeModel::protoNodeFromIndex(const QModelIndex &index) const
ProtoNode* ProtoTreeModel::protoNodeFromIndex(const QModelIndex &index) const
{
return ProtoNode(static_cast<proto_node*>(index.internalPointer()));
return static_cast<ProtoNode*>(index.internalPointer());
}
QModelIndex ProtoTreeModel::indexFromProtoNode(ProtoNode &index_node) const
QModelIndex ProtoTreeModel::indexFromProtoNode(ProtoNode *index_node) const
{
int row = index_node.row();
if (!index_node.isValid() || row < 0) {
if (!index_node) {
return QModelIndex();
}
return createIndex(row, 0, static_cast<void *>(index_node.protoNode()));
int row = index_node->row();
if (!index_node->isValid() || row < 0) {
return QModelIndex();
}
return createIndex(row, 0, static_cast<void *>(index_node));
}
struct find_hfid_ {
int hfid;
ProtoNode node;
ProtoNode *node;
};
void ProtoTreeModel::foreachFindHfid(proto_node *node, gpointer find_hfid_ptr)
bool ProtoTreeModel::foreachFindHfid(ProtoNode *node, gpointer find_hfid_ptr)
{
struct find_hfid_ *find_hfid = (struct find_hfid_ *) find_hfid_ptr;
if (PNODE_FINFO(node)->hfinfo->id == find_hfid->hfid) {
find_hfid->node = ProtoNode(node);
return;
if (PNODE_FINFO(node->protoNode())->hfinfo->id == find_hfid->hfid) {
find_hfid->node = node;
return true;
}
proto_tree_children_foreach(node, foreachFindHfid, find_hfid);
for (int i = 0; i < node->childrenCount(); i++) {
if (foreachFindHfid(node->child(i), &find_hfid)) {
return true;
}
}
return false;
}
QModelIndex ProtoTreeModel::findFirstHfid(int hf_id)
@ -196,9 +210,7 @@ QModelIndex ProtoTreeModel::findFirstHfid(int hf_id)
struct find_hfid_ find_hfid;
find_hfid.hfid = hf_id;
proto_tree_children_foreach(root_node_, foreachFindHfid, &find_hfid);
if (find_hfid.node.isValid()) {
if (foreachFindHfid(root_node_, &find_hfid) && find_hfid.node->isValid()) {
return indexFromProtoNode(find_hfid.node);
}
return QModelIndex();
@ -206,17 +218,22 @@ QModelIndex ProtoTreeModel::findFirstHfid(int hf_id)
struct find_field_info_ {
field_info *fi;
ProtoNode node;
ProtoNode *node;
};
void ProtoTreeModel::foreachFindField(proto_node *node, gpointer find_finfo_ptr)
bool ProtoTreeModel::foreachFindField(ProtoNode *node, gpointer find_finfo_ptr)
{
struct find_field_info_ *find_finfo = (struct find_field_info_ *) find_finfo_ptr;
if (PNODE_FINFO(node) == find_finfo->fi) {
find_finfo->node = ProtoNode(node);
return;
if (PNODE_FINFO(node->protoNode()) == find_finfo->fi) {
find_finfo->node = node;
return true;
}
proto_tree_children_foreach(node, foreachFindField, find_finfo);
for (int i = 0; i < node->childrenCount(); i++) {
if (foreachFindField(node->child(i), &find_finfo)) {
return true;
}
}
return false;
}
QModelIndex ProtoTreeModel::findFieldInformation(FieldInformation *finfo)
@ -228,8 +245,7 @@ QModelIndex ProtoTreeModel::findFieldInformation(FieldInformation *finfo)
struct find_field_info_ find_finfo;
find_finfo.fi = fi;
proto_tree_children_foreach(root_node_, foreachFindField, &find_finfo);
if (find_finfo.node.isValid()) {
if (foreachFindField(root_node_, &find_finfo) && find_finfo.node->isValid()) {
return indexFromProtoNode(find_finfo.node);
}
return QModelIndex();

View File

@ -10,6 +10,7 @@
#ifndef PROTO_TREE_MODEL_H
#define PROTO_TREE_MODEL_H
#include <ui/qt/utils/field_information.h>
#include <ui/qt/utils/proto_node.h>
#include <QAbstractItemModel>
@ -21,6 +22,7 @@ class ProtoTreeModel : public QAbstractItemModel
public:
explicit ProtoTreeModel(QObject * parent = 0);
~ProtoTreeModel();
virtual Qt::ItemFlags flags(const QModelIndex &index) const;
QModelIndex index(int row, int, const QModelIndex &parent = QModelIndex()) const;
@ -31,16 +33,16 @@ public:
// root_node can be NULL.
void setRootNode(proto_node *root_node);
ProtoNode protoNodeFromIndex(const QModelIndex &index) const;
QModelIndex indexFromProtoNode(ProtoNode &index_node) const;
ProtoNode* protoNodeFromIndex(const QModelIndex &index) const;
QModelIndex indexFromProtoNode(ProtoNode *index_node) const;
QModelIndex findFirstHfid(int hf_id);
QModelIndex findFieldInformation(FieldInformation *finfo);
private:
proto_node* root_node_;
static void foreachFindHfid(proto_node *node, gpointer find_hfid_ptr);
static void foreachFindField(proto_node *node, gpointer find_finfo_ptr);
ProtoNode *root_node_;
static bool foreachFindHfid(ProtoNode *node, gpointer find_hfid_ptr);
static bool foreachFindField(ProtoNode *node, gpointer find_finfo_ptr);
};
#endif // PROTO_TREE_MODEL_H

View File

@ -134,7 +134,7 @@ void ProtoTree::ctxCopyVisibleItems()
void ProtoTree::ctxCopyAsFilter()
{
QModelIndex idx = selectionModel()->selectedIndexes().first();
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx));
if (finfo.isValid())
{
epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
@ -156,7 +156,7 @@ void ProtoTree::ctxCopySelectedInfo()
val = send->property("field_type").toInt();
QModelIndex idx = selectionModel()->selectedIndexes().first();
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx));
if (! finfo.isValid())
return;
@ -194,7 +194,7 @@ void ProtoTree::ctxOpenUrlWiki()
if (send && send->property("field_reference").isValid())
is_field_reference = send->property("field_reference").toBool();
QModelIndex idx = selectionModel()->selectedIndexes().first();
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx));
int field_id = finfo.headerInfo().id;
if (!proto_registrar_is_protocol(field_id) && (field_id != hf_text_only)) {
@ -250,7 +250,7 @@ void ProtoTree::contextMenuEvent(QContextMenuEvent *event)
QAction *action;
bool have_subtree = false;
FieldInformation *finfo = new FieldInformation(proto_tree_model_->protoNodeFromIndex(index).protoNode(), ctx_menu);
FieldInformation *finfo = new FieldInformation(proto_tree_model_->protoNodeFromIndex(index), ctx_menu);
field_info * fi = finfo->fieldInfo();
bool is_selected = false;
epan_dissect_t *edt = cap_file_ ? cap_file_->edt : edt_;
@ -376,9 +376,10 @@ void ProtoTree::contextMenuEvent(QContextMenuEvent *event)
// The "text only" header field will not give preferences for the selected protocol.
// Use parent in this case.
proto_node *node = proto_tree_model_->protoNodeFromIndex(index).protoNode();
while (node && node->finfo && node->finfo->hfinfo && node->finfo->hfinfo->id == hf_text_only)
node = node->parent;
ProtoNode *node = proto_tree_model_->protoNodeFromIndex(index);
while (node && node->isValid() && node->protoNode()->finfo && node->protoNode()->finfo->hfinfo && node->protoNode()->finfo->hfinfo->id == hf_text_only) {
node = node->parentNode();
}
FieldInformation pref_finfo(node);
proto_prefs_menu_.setModule(pref_finfo.moduleName());
@ -438,13 +439,8 @@ void ProtoTree::foreachTreeNode(proto_node *node, gpointer proto_tree_ptr)
return;
}
// Expanded state
if (tree_expanded(node->finfo->tree_type)) {
ProtoNode expand_node = ProtoNode(node);
tree_view->expand(model->indexFromProtoNode(expand_node));
}
// Related frames
// Related frames - there might be hidden FT_FRAMENUM nodes, so do this
// for each proto_node and not just the ProtoNodes in the model
if (node->finfo->hfinfo->type == FT_FRAMENUM) {
ft_framenum_type_t framenum_type = (ft_framenum_type_t)GPOINTER_TO_INT(node->finfo->hfinfo->strings);
tree_view->emitRelatedFrame(node->finfo->value.value.uinteger, framenum_type);
@ -453,6 +449,23 @@ void ProtoTree::foreachTreeNode(proto_node *node, gpointer proto_tree_ptr)
proto_tree_children_foreach(node, foreachTreeNode, proto_tree_ptr);
}
void ProtoTree::foreachExpand(const QModelIndex &index = QModelIndex()) {
// Restore expanded state. (Note QModelIndex() refers to the root node)
int children = proto_tree_model_->rowCount(index);
QModelIndex childIndex;
for (int child = 0; child < children; child++) {
childIndex = proto_tree_model_->index(child, 0, index);
if (childIndex.isValid()) {
ProtoNode *node = proto_tree_model_->protoNodeFromIndex(childIndex);
if (node && node->isValid() && tree_expanded(node->protoNode()->finfo->tree_type)) {
expand(childIndex);
}
foreachExpand(childIndex);
}
}
}
// setRootNode sets the new contents for the protocol tree and subsequently
// restores the previously expanded state.
void ProtoTree::setRootNode(proto_node *root_node) {
@ -463,6 +476,7 @@ void ProtoTree::setRootNode(proto_node *root_node) {
disconnect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(syncExpanded(QModelIndex)));
proto_tree_children_foreach(root_node, foreachTreeNode, this);
foreachExpand();
connect(this, SIGNAL(expanded(QModelIndex)), this, SLOT(syncExpanded(QModelIndex)));
updateContentWidth();
@ -505,14 +519,14 @@ void ProtoTree::selectionChanged(const QItemSelection &selected, const QItemSele
// Find and highlight the protocol bytes. select above won't call
// selectionChanged if the current and selected indexes are the same
// so we do this here.
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode(), this);
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index), this);
if (finfo.isValid()) {
QModelIndex parent = index;
while (parent.isValid() && parent.parent().isValid()) {
parent = parent.parent();
}
if (parent.isValid()) {
FieldInformation parent_finfo(proto_tree_model_->protoNodeFromIndex(parent).protoNode());
FieldInformation parent_finfo(proto_tree_model_->protoNodeFromIndex(parent));
finfo.setParentField(parent_finfo.fieldInfo());
}
emit fieldSelected(&finfo);
@ -520,7 +534,7 @@ void ProtoTree::selectionChanged(const QItemSelection &selected, const QItemSele
}
void ProtoTree::syncExpanded(const QModelIndex &index) {
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index));
if (!finfo.isValid()) return;
/*
@ -533,7 +547,7 @@ void ProtoTree::syncExpanded(const QModelIndex &index) {
}
void ProtoTree::syncCollapsed(const QModelIndex &index) {
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index));
if (!finfo.isValid()) return;
/*
@ -612,7 +626,7 @@ void ProtoTree::itemClicked(const QModelIndex &index)
if (selectionModel()->selectedIndexes().isEmpty()) {
emit fieldSelected(0);
} else if (index == selectionModel()->selectedIndexes().first()) {
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index));
if (finfo.isValid()) {
emit fieldSelected(&finfo);
@ -622,7 +636,7 @@ void ProtoTree::itemClicked(const QModelIndex &index)
void ProtoTree::itemDoubleClicked(const QModelIndex &index)
{
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(index));
if (!finfo.isValid()) return;
if (finfo.headerInfo().type == FT_FRAMENUM) {
@ -679,7 +693,7 @@ void ProtoTree::saveSelectedField(QModelIndex &index)
selected_hfid_path_.clear();
QModelIndex save_index = index;
while (save_index.isValid()) {
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(save_index).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(save_index));
if (!finfo.isValid()) break;
selected_hfid_path_.prepend(QPair<int,int>(save_index.row(), finfo.headerInfo().id));
save_index = save_index.parent();
@ -697,7 +711,7 @@ void ProtoTree::restoreSelectedField()
int row = path_entry.first;
int hf_id = path_entry.second;
cur_index = proto_tree_model_->index(row, 0, cur_index);
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(cur_index).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(cur_index));
if (!finfo.isValid() || finfo.headerInfo().id != hf_id) {
// Did not find the selected hfid path in the selected packet
cur_index = QModelIndex();
@ -780,7 +794,7 @@ bool ProtoTree::eventFilter(QObject * obj, QEvent * event)
> QApplication::startDragDistance())
{
QModelIndex idx = indexAt(drag_start_position_);
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx).protoNode());
FieldInformation finfo(proto_tree_model_->protoNodeFromIndex(idx));
if (finfo.isValid())
{
/* Hack to prevent QItemSelection taking the item which has been dragged over at start

View File

@ -71,6 +71,7 @@ private:
void saveSelectedField(QModelIndex &index);
static void foreachTreeNode(proto_node *node, gpointer proto_tree_ptr);
void foreachExpand(const QModelIndex &index);
signals:
void fieldSelected(FieldInformation *);

View File

@ -18,12 +18,12 @@ FieldInformation::FieldInformation(field_info *fi, QObject * parent)
parent_fi_ = NULL;
}
FieldInformation::FieldInformation(proto_node *node, QObject * parent)
FieldInformation::FieldInformation(ProtoNode *node, QObject * parent)
:QObject(parent)
{
fi_ = NULL;
if (node) {
fi_ = node->finfo;
if (node && node->isValid()) {
fi_ = node->protoNode()->finfo;
}
parent_fi_ = NULL;
}

View File

@ -14,6 +14,7 @@
#include <epan/proto.h>
#include <ui/qt/utils/proto_node.h>
#include "data_printer.h"
#include <QObject>
@ -43,7 +44,7 @@ public:
};
explicit FieldInformation(field_info * fi, QObject * parent = Q_NULLPTR);
explicit FieldInformation(proto_node * node, QObject * parent = Q_NULLPTR);
explicit FieldInformation(ProtoNode * node, QObject * parent = Q_NULLPTR);
bool isValid() const;
bool isLink() const ;

View File

@ -8,12 +8,35 @@
*/
#include <ui/qt/utils/proto_node.h>
#include <ui/qt/utils/field_information.h>
#include <epan/prefs.h>
ProtoNode::ProtoNode(proto_node *node) :
node_(node)
ProtoNode::ProtoNode(proto_node *node, ProtoNode *parent) :
node_(node), parent_(parent)
{
if (node_) {
int num_children = 0;
for (proto_node *child = node_->first_child; child; child = child->next) {
if (!isHidden(child)) {
num_children++;
}
}
m_children.reserve(num_children);
for (proto_node *child = node_->first_child; child; child = child->next) {
if (!isHidden(child)) {
m_children.append(new ProtoNode(child, this));
}
}
}
}
ProtoNode::~ProtoNode()
{
qDeleteAll(m_children);
}
bool ProtoNode::isValid() const
@ -26,12 +49,9 @@ bool ProtoNode::isChild() const
return node_ && node_->parent;
}
ProtoNode ProtoNode::parentNode()
ProtoNode* ProtoNode::parentNode()
{
if (node_) {
return ProtoNode(node_->parent);
}
return ProtoNode(NULL);
return parent_;
}
QString ProtoNode::labelText() const
@ -71,15 +91,7 @@ int ProtoNode::childrenCount() const
{
if (!node_) return 0;
int row_count = 0;
ChildIterator kids = children();
while (kids.element().isValid())
{
row_count++;
kids.next();
}
return row_count;
return (int)m_children.count();
}
int ProtoNode::row()
@ -88,20 +100,7 @@ int ProtoNode::row()
return -1;
}
int cur_row = 0;
ProtoNode::ChildIterator kids = parentNode().children();
while (kids.element().isValid())
{
if (kids.element().protoNode() == node_) {
break;
}
cur_row++;
kids.next();
}
if (! kids.element().isValid()) {
return -1;
}
return cur_row;
return (int)parent_->m_children.indexOf(const_cast<ProtoNode*>(this));
}
bool ProtoNode::isExpanded() const
@ -117,8 +116,17 @@ proto_node * ProtoNode::protoNode() const
return node_;
}
ProtoNode* ProtoNode::child(int row)
{
if (row < 0 || row >= m_children.size())
return nullptr;
return m_children.at(row);
}
ProtoNode::ChildIterator ProtoNode::children() const
{
/* XXX: Iterate over m_children instead?
* Somewhat faster as m_children already excludes any hidden items. */
proto_node *child = node_->first_child;
while (child && isHidden(child)) {
child = child->next;

View File

@ -12,7 +12,9 @@
#include <config.h>
#include <ui/qt/utils/field_information.h>
#include <epan/proto.h>
#include <QObject>
class ProtoNode
{
@ -32,16 +34,18 @@ public:
NodePtr node;
};
explicit ProtoNode(proto_node * node = NULL);
explicit ProtoNode(proto_node * node = NULL, ProtoNode *parent = nullptr);
~ProtoNode();
bool isValid() const;
bool isChild() const;
bool isExpanded() const;
proto_node *protoNode() const;
ProtoNode *child(int row);
int childrenCount() const;
int row();
ProtoNode parentNode();
ProtoNode *parentNode();
QString labelText() const;
@ -49,6 +53,8 @@ public:
private:
proto_node * node_;
QVector<ProtoNode*>m_children;
ProtoNode *parent_;
static bool isHidden(proto_node * node);
};