summaryrefslogtreecommitdiffstats
path: root/ui/qt/models
diff options
context:
space:
mode:
Diffstat (limited to 'ui/qt/models')
-rw-r--r--ui/qt/models/astringlist_list_model.cpp305
-rw-r--r--ui/qt/models/astringlist_list_model.h107
-rw-r--r--ui/qt/models/atap_data_model.cpp850
-rw-r--r--ui/qt/models/atap_data_model.h329
-rw-r--r--ui/qt/models/cache_proxy_model.cpp100
-rw-r--r--ui/qt/models/cache_proxy_model.h47
-rw-r--r--ui/qt/models/coloring_rules_delegate.cpp123
-rw-r--r--ui/qt/models/coloring_rules_delegate.h43
-rw-r--r--ui/qt/models/coloring_rules_model.cpp569
-rw-r--r--ui/qt/models/coloring_rules_model.h102
-rw-r--r--ui/qt/models/column_list_model.cpp518
-rw-r--r--ui/qt/models/column_list_model.h96
-rw-r--r--ui/qt/models/credentials_model.cpp155
-rw-r--r--ui/qt/models/credentials_model.h53
-rw-r--r--ui/qt/models/decode_as_delegate.cpp390
-rw-r--r--ui/qt/models/decode_as_delegate.h59
-rw-r--r--ui/qt/models/decode_as_model.cpp849
-rw-r--r--ui/qt/models/decode_as_model.h120
-rw-r--r--ui/qt/models/dissector_tables_model.cpp434
-rw-r--r--ui/qt/models/dissector_tables_model.h89
-rw-r--r--ui/qt/models/enabled_protocols_model.cpp520
-rw-r--r--ui/qt/models/enabled_protocols_model.h143
-rw-r--r--ui/qt/models/expert_info_model.cpp424
-rw-r--r--ui/qt/models/expert_info_model.h128
-rw-r--r--ui/qt/models/expert_info_proxy_model.cpp290
-rw-r--r--ui/qt/models/expert_info_proxy_model.h61
-rw-r--r--ui/qt/models/export_objects_model.cpp316
-rw-r--r--ui/qt/models/export_objects_model.h91
-rw-r--r--ui/qt/models/fileset_entry_model.cpp155
-rw-r--r--ui/qt/models/fileset_entry_model.h52
-rw-r--r--ui/qt/models/filter_list_model.cpp314
-rw-r--r--ui/qt/models/filter_list_model.h71
-rw-r--r--ui/qt/models/info_proxy_model.cpp119
-rw-r--r--ui/qt/models/info_proxy_model.h47
-rw-r--r--ui/qt/models/interface_sort_filter_model.cpp396
-rw-r--r--ui/qt/models/interface_sort_filter_model.h87
-rw-r--r--ui/qt/models/interface_tree_cache_model.cpp584
-rw-r--r--ui/qt/models/interface_tree_cache_model.h66
-rw-r--r--ui/qt/models/interface_tree_model.cpp545
-rw-r--r--ui/qt/models/interface_tree_model.h99
-rw-r--r--ui/qt/models/manuf_table_model.cpp220
-rw-r--r--ui/qt/models/manuf_table_model.h90
-rw-r--r--ui/qt/models/numeric_value_chooser_delegate.cpp93
-rw-r--r--ui/qt/models/numeric_value_chooser_delegate.h45
-rw-r--r--ui/qt/models/packet_list_model.cpp1014
-rw-r--r--ui/qt/models/packet_list_model.h130
-rw-r--r--ui/qt/models/packet_list_record.cpp246
-rw-r--r--ui/qt/models/packet_list_record.h84
-rw-r--r--ui/qt/models/path_selection_delegate.cpp63
-rw-r--r--ui/qt/models/path_selection_delegate.h35
-rw-r--r--ui/qt/models/percent_bar_delegate.cpp91
-rw-r--r--ui/qt/models/percent_bar_delegate.h52
-rw-r--r--ui/qt/models/pref_delegate.cpp85
-rw-r--r--ui/qt/models/pref_delegate.h37
-rw-r--r--ui/qt/models/pref_models.cpp774
-rw-r--r--ui/qt/models/pref_models.h165
-rw-r--r--ui/qt/models/profile_model.cpp1299
-rw-r--r--ui/qt/models/profile_model.h165
-rw-r--r--ui/qt/models/proto_tree_model.cpp252
-rw-r--r--ui/qt/models/proto_tree_model.h48
-rw-r--r--ui/qt/models/related_packet_delegate.cpp373
-rw-r--r--ui/qt/models/related_packet_delegate.h51
-rw-r--r--ui/qt/models/resolved_addresses_models.cpp210
-rw-r--r--ui/qt/models/resolved_addresses_models.h48
-rw-r--r--ui/qt/models/sparkline_delegate.cpp109
-rw-r--r--ui/qt/models/sparkline_delegate.h35
-rw-r--r--ui/qt/models/supported_protocols_model.cpp261
-rw-r--r--ui/qt/models/supported_protocols_model.h97
-rw-r--r--ui/qt/models/timeline_delegate.cpp128
-rw-r--r--ui/qt/models/timeline_delegate.h66
-rw-r--r--ui/qt/models/tree_model_helpers.h89
-rw-r--r--ui/qt/models/uat_delegate.cpp221
-rw-r--r--ui/qt/models/uat_delegate.h42
-rw-r--r--ui/qt/models/uat_model.cpp545
-rw-r--r--ui/qt/models/uat_model.h82
-rw-r--r--ui/qt/models/url_link_delegate.cpp51
-rw-r--r--ui/qt/models/url_link_delegate.h36
-rw-r--r--ui/qt/models/voip_calls_info_model.cpp249
-rw-r--r--ui/qt/models/voip_calls_info_model.h71
79 files changed, 17598 insertions, 0 deletions
diff --git a/ui/qt/models/astringlist_list_model.cpp b/ui/qt/models/astringlist_list_model.cpp
new file mode 100644
index 00000000..a0b5fc38
--- /dev/null
+++ b/ui/qt/models/astringlist_list_model.cpp
@@ -0,0 +1,305 @@
+/* astringlist_list_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <QSortFilterProxyModel>
+#include <QStringList>
+#include <QPalette>
+#include <QApplication>
+#include <QBrush>
+
+#include <ui/qt/models/astringlist_list_model.h>
+
+#include <ui/qt/utils/color_utils.h>
+
+AStringListListModel::AStringListListModel(QObject * parent):
+QAbstractTableModel(parent)
+{}
+
+AStringListListModel::~AStringListListModel() { display_data_.clear(); }
+
+void AStringListListModel::appendRow(const QStringList & display_strings, const QString & row_tooltip, const QModelIndex &parent)
+{
+ QStringList columns = headerColumns();
+ if (display_strings.count() != columns.count())
+ return;
+
+ emit beginInsertRows(parent, rowCount(), rowCount());
+ display_data_ << display_strings;
+ tooltip_data_ << row_tooltip;
+ emit endInsertRows();
+}
+
+int AStringListListModel::rowCount(const QModelIndex &) const
+{
+ return static_cast<int>(display_data_.count());
+}
+
+int AStringListListModel::columnCount(const QModelIndex &parent) const
+{
+ if (rowCount(parent) == 0)
+ return 0;
+
+ return static_cast<int>(headerColumns().count());
+}
+
+QVariant AStringListListModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Vertical)
+ return QVariant();
+
+ QStringList columns = headerColumns();
+ if (role == Qt::DisplayRole && section < columns.count())
+ return QVariant::fromValue(columns[section]);
+
+ return QVariant();
+}
+
+QVariant AStringListListModel::data(const QModelIndex &index, int role) const
+{
+ if (! index.isValid() || index.row() >= rowCount())
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ {
+ QStringList data = display_data_.at(index.row());
+
+ if (index.column() < columnCount())
+ return QVariant::fromValue(data.at(index.column()));
+ }
+ else if (role == Qt::ToolTipRole)
+ {
+ QString tooltip = tooltip_data_.at(index.row());
+ if (!tooltip.isEmpty()) {
+ return tooltip;
+ }
+ }
+
+ return QVariant();
+}
+
+AStringListListSortFilterProxyModel::AStringListListSortFilterProxyModel(QObject * parent)
+: QSortFilterProxyModel(parent)
+{
+ filter_ = QString();
+ types_[-1] = FilterByContains;
+}
+
+bool AStringListListSortFilterProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ QString leftData = left.data().toString();
+ QString rightData = right.data().toString();
+
+ if (numericColumns_.contains(left.column()) || numericColumns_.contains(right.column()) )
+ {
+ float leftD = leftData.toFloat();
+ float rightD = rightData.toFloat();
+
+ return leftD < rightD;
+ }
+
+ return leftData.compare(rightData, sortCaseSensitivity()) < 0;
+}
+
+void AStringListListSortFilterProxyModel::setFilter(const QString & filter)
+{
+ filter_ = filter;
+ invalidateFilter();
+}
+
+static bool AContainsB(const QVariant &a, const QVariant &b, Qt::CaseSensitivity cs)
+{
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ if (! a.canConvert<QString>() || ! b.canConvert<QString>())
+#else
+ if (! a.canConvert(QVariant::String) || ! b.canConvert(QVariant::String))
+#endif
+ return false;
+ return a.toString().contains(b.toString(), cs);
+}
+
+static bool AStartsWithB(const QVariant &a, const QVariant &b, Qt::CaseSensitivity cs)
+{
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ if (! a.canConvert<QString>() || ! b.canConvert<QString>())
+#else
+ if (! a.canConvert(QVariant::String) || ! b.canConvert(QVariant::String))
+#endif
+ return false;
+ return a.toString().startsWith(b.toString(), cs);
+}
+
+static bool AIsEquivalentToB(const QVariant &a, const QVariant &b, Qt::CaseSensitivity)
+{
+ return a == b;
+}
+
+bool AStringListListSortFilterProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ if (columnsToFilter_.count() == 0)
+ return true;
+
+ foreach(int column, columnsToFilter_)
+ {
+ if (column >= columnCount())
+ continue;
+
+ QModelIndex chkIdx = sourceModel()->index(sourceRow, column, sourceParent);
+ QString dataString = chkIdx.data().toString();
+
+ /* Default is filter by string a contains string b */
+ bool (*compareFunc)(const QVariant&, const QVariant&, Qt::CaseSensitivity) = AContainsB;
+ if (types_.keys().contains(column))
+ {
+ switch (types_.value(column, FilterByContains))
+ {
+ case FilterByStart:
+ compareFunc = AStartsWithB;
+ break;
+ case FilterByEquivalent:
+ compareFunc = AIsEquivalentToB;
+ break;
+ case FilterNone:
+ return true;
+ break;
+ default:
+ compareFunc = AContainsB;
+ break;
+ }
+ }
+
+ if (compareFunc(dataString, filter_, filterCaseSensitivity()))
+ return true;
+ }
+
+ return false;
+}
+
+void AStringListListSortFilterProxyModel::setFilterType(AStringListListFilterType type, int column)
+{
+ if (column >= -1 && column < columnCount())
+ {
+ if (! types_.keys().contains(column))
+ {
+ types_.insert(column, type);
+ invalidateFilter();
+ }
+ else if (types_.keys().contains(column) && type != types_[column])
+ {
+ types_[column] = type;
+ invalidateFilter();
+ }
+ }
+}
+
+void AStringListListSortFilterProxyModel::setColumnToFilter(int column)
+{
+ if (column < columnCount() && ! columnsToFilter_.contains(column))
+ {
+ columnsToFilter_.append(column);
+ invalidateFilter();
+ }
+}
+
+void AStringListListSortFilterProxyModel::setColumnsToFilter(QList<int> columns)
+{
+ bool hasBeenAdded = false;
+
+ foreach (int column, columns) {
+ if (column < columnCount() && ! columnsToFilter_.contains(column)) {
+ columnsToFilter_.append(column);
+ hasBeenAdded = true;
+ }
+ }
+
+ if (hasBeenAdded)
+ invalidateFilter();
+}
+
+void AStringListListSortFilterProxyModel::clearColumnsToFilter()
+{
+ columnsToFilter_.clear();
+ invalidateFilter();
+}
+
+void AStringListListSortFilterProxyModel::clearHiddenColumns()
+{
+ hiddenColumns_.clear();
+ invalidateFilter();
+}
+
+void AStringListListSortFilterProxyModel::setColumnToHide(int col)
+{
+ if (! hiddenColumns_.contains(col) && col > -1 && sourceModel() && sourceModel()->columnCount() > col)
+ {
+ hiddenColumns_ << col;
+ invalidateFilter();
+ }
+}
+
+bool AStringListListSortFilterProxyModel::filterAcceptsColumn(int sourceColumn, const QModelIndex &sourceParent) const
+{
+ QModelIndex realIndex = sourceModel()->index(0, sourceColumn, sourceParent);
+
+ if (! realIndex.isValid())
+ return false;
+
+ if (hiddenColumns_.contains(sourceColumn))
+ return false;
+
+ return true;
+}
+
+void AStringListListSortFilterProxyModel::clearNumericColumns()
+{
+ numericColumns_.clear();
+ invalidateFilter();
+}
+
+void AStringListListSortFilterProxyModel::setColumnAsNumeric(int col)
+{
+ if (! numericColumns_.contains(col) && col > -1 && sourceModel() && sourceModel()->columnCount() > col)
+ {
+ numericColumns_ << col;
+ invalidateFilter();
+ }
+}
+
+AStringListListUrlProxyModel::AStringListListUrlProxyModel(QObject * parent):
+ QIdentityProxyModel(parent)
+{}
+
+void AStringListListUrlProxyModel::setUrlColumn(int column)
+{
+ if (column < columnCount() && ! urls_.contains(column))
+ urls_ << column;
+}
+
+bool AStringListListUrlProxyModel::isUrlColumn(int column) const
+{
+ return urls_.contains(column);
+}
+
+QVariant AStringListListUrlProxyModel::data(const QModelIndex &index, int role) const
+{
+ QVariant result = QIdentityProxyModel::data(index, role);
+
+ if (role == Qt::ForegroundRole && urls_.contains(index.column())
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ && result.canConvert<QBrush>())
+#else
+ && result.canConvert(QVariant::Brush))
+#endif
+ {
+ QBrush selected = result.value<QBrush>();
+ selected.setColor(ColorUtils::themeLinkBrush().color());
+ return selected;
+ }
+
+ return result;
+}
diff --git a/ui/qt/models/astringlist_list_model.h b/ui/qt/models/astringlist_list_model.h
new file mode 100644
index 00000000..70acc97b
--- /dev/null
+++ b/ui/qt/models/astringlist_list_model.h
@@ -0,0 +1,107 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef ASTRINGLIST_LIST_MODEL_H
+#define ASTRINGLIST_LIST_MODEL_H
+
+#include <config.h>
+
+#include <QAbstractTableModel>
+#include <QModelIndex>
+#include <QList>
+#include <QStringList>
+#include <QSortFilterProxyModel>
+#include <QIdentityProxyModel>
+
+class AStringListListModel : public QAbstractTableModel
+{
+public:
+ explicit AStringListListModel(QObject * parent = Q_NULLPTR);
+ virtual ~AStringListListModel();
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ //
+ // This is not protected because we may need to invoke it from
+ // a wmem_map_foreach() callback implemented as an extern "C"
+ // static member function of a subclass. wmem_map_foreach() is
+ // passed, as the user data, a pointer to the class instance to
+ // which we want to append rows.
+ //
+ virtual void appendRow(const QStringList &, const QString & row_tooltip = QString(), const QModelIndex &parent = QModelIndex());
+
+protected:
+ virtual QStringList headerColumns() const = 0;
+
+private:
+ QList<QStringList> display_data_;
+ QStringList tooltip_data_;
+};
+
+class AStringListListSortFilterProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+public:
+
+ enum AStringListListFilterType
+ {
+ FilterByContains = 0,
+ FilterByStart,
+ FilterByEquivalent,
+ FilterNone
+ };
+ Q_ENUM(AStringListListFilterType)
+
+ explicit AStringListListSortFilterProxyModel(QObject * parent = Q_NULLPTR);
+
+ virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
+ virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
+ virtual bool filterAcceptsColumn(int column, const QModelIndex &sourceParent) const;
+
+ void setFilterType(AStringListListFilterType type, int column = -1);
+
+ void setColumnToFilter(int);
+ void setColumnsToFilter(QList<int>);
+ void clearColumnsToFilter();
+
+ void clearHiddenColumns();
+ void setColumnToHide(int col);
+
+ void clearNumericColumns();
+ void setColumnAsNumeric(int col);
+
+public slots:
+ void setFilter(const QString&);
+
+private:
+ QString filter_;
+ QMap<int, AStringListListFilterType> types_;
+ QList<int> columnsToFilter_;
+ QList<int> hiddenColumns_;
+ QList<int> numericColumns_;
+};
+
+class AStringListListUrlProxyModel : public QIdentityProxyModel
+{
+public:
+ explicit AStringListListUrlProxyModel(QObject * parent = Q_NULLPTR);
+
+ void setUrlColumn(int);
+ bool isUrlColumn(int) const;
+
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+private:
+ QList<int> urls_;
+};
+
+#endif // ASTRINGLIST_LIST_MODEL_H
diff --git a/ui/qt/models/atap_data_model.cpp b/ui/qt/models/atap_data_model.cpp
new file mode 100644
index 00000000..b7ff508d
--- /dev/null
+++ b/ui/qt/models/atap_data_model.cpp
@@ -0,0 +1,850 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <glib.h>
+
+#include <epan/tap.h>
+#include <epan/conversation.h>
+#include <epan/conversation_table.h>
+#include <epan/maxmind_db.h>
+#include <epan/addr_resolv.h>
+
+#include <wsutil/utf8_entities.h>
+#include <wsutil/nstime.h>
+#include <wsutil/str_util.h>
+
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/utils/variant_pointer.h>
+#include <ui/qt/main_application.h>
+#include <ui/qt/models/atap_data_model.h>
+#include <ui/qt/models/timeline_delegate.h>
+
+#include <QSize>
+#include <QVariant>
+#include <QWidget>
+#include <QDateTime>
+
+static QString formatString(qlonglong value)
+{
+ return QLocale().formattedDataSize(value, 0, QLocale::DataSizeSIFormat);
+}
+
+ATapDataModel::ATapDataModel(dataModelType type, int protoId, QString filter, QObject *parent):
+ QAbstractListModel(parent)
+{
+ hash_.conv_array = nullptr;
+ hash_.hashtable = nullptr;
+ hash_.user_data = this;
+
+ storage_ = nullptr;
+ _resolveNames = false;
+ _absoluteTime = false;
+ _nanoseconds = false;
+
+ _protoId = protoId;
+ _filter = filter;
+
+ _minRelStartTime = 0;
+ _maxRelStopTime = 0;
+
+ _type = type;
+ _disableTap = true;
+
+ QString _tap(proto_get_protocol_filter_name(protoId));
+}
+
+ATapDataModel::~ATapDataModel()
+{
+ /* Only remove the tap if we come from a enabled model */
+ if (!_disableTap)
+ remove_tap_listener(hash());
+
+ if (_type == ATapDataModel::DATAMODEL_ENDPOINT)
+ reset_endpoint_table_data(&hash_);
+ else if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
+ reset_conversation_table_data(&hash_);
+}
+
+int ATapDataModel::protoId() const
+{
+ return _protoId;
+}
+
+QString ATapDataModel::tap() const
+{
+ return proto_get_protocol_filter_name(_protoId);
+}
+
+#ifdef HAVE_MAXMINDDB
+bool ATapDataModel::hasGeoIPData()
+{
+ bool coordsFound = false;
+ int row = 0;
+ int count = rowCount();
+ while (!coordsFound && row < count)
+ {
+ QModelIndex idx = index(row, 0);
+ if (_type == ATapDataModel::DATAMODEL_ENDPOINT)
+ coordsFound = qobject_cast<EndpointDataModel *>(this)->data(idx, ATapDataModel::GEODATA_AVAILABLE).toBool();
+ else if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
+ coordsFound = qobject_cast<ConversationDataModel *>(this)->data(idx, ATapDataModel::GEODATA_AVAILABLE).toBool();
+ row++;
+ }
+
+ return coordsFound;
+}
+#endif
+
+bool ATapDataModel::enableTap()
+{
+ /* We can't reenable a tap, so just return */
+ if (! _disableTap)
+ return true;
+
+ _disableTap = false;
+
+ /* The errorString is ignored. If this is not working, there is nothing really the user may do about
+ * it, so the error is only interesting to the developer.*/
+ GString * errorString = register_tap_listener(tap().toUtf8().constData(), hash(), _filter.toUtf8().constData(),
+ TL_IGNORE_DISPLAY_FILTER, &ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr);
+ if (errorString && errorString->len > 0) {
+ g_string_free(errorString, TRUE);
+ _disableTap = true;
+ emit tapListenerChanged(false);
+ return false;
+ }
+
+ if (errorString)
+ g_string_free(errorString, TRUE);
+
+ emit tapListenerChanged(true);
+
+ return true;
+}
+
+void ATapDataModel::disableTap()
+{
+ /* Only remove the tap if we come from a enabled model */
+ if (!_disableTap)
+ remove_tap_listener(hash());
+ _disableTap = true;
+ emit tapListenerChanged(false);
+}
+
+int ATapDataModel::rowCount(const QModelIndex &parent) const
+{
+ return (storage_ && !parent.isValid()) ? (int) storage_->len : 0;
+}
+
+void ATapDataModel::tapReset(void *tapdata) {
+ if (! tapdata)
+ return;
+
+ conv_hash_t *hash = (conv_hash_t*)tapdata;
+ ATapDataModel * dataModel = qobject_cast<ATapDataModel *>((ATapDataModel *)hash->user_data);
+
+ dataModel->resetData();
+}
+
+void ATapDataModel::tapDraw(void *tapdata)
+{
+ if (! tapdata)
+ return;
+
+ conv_hash_t *hash = (conv_hash_t*)tapdata;
+ ATapDataModel * dataModel = qobject_cast<ATapDataModel *>((ATapDataModel *)hash->user_data);
+
+ dataModel->updateData(hash->conv_array);
+}
+
+conv_hash_t * ATapDataModel::hash()
+{
+ return &hash_;
+}
+
+register_ct_t * ATapDataModel::registerTable() const
+{
+ if (_protoId > -1)
+ return get_conversation_by_proto_id(_protoId);
+
+ return nullptr;
+}
+
+tap_packet_cb ATapDataModel::conversationPacketHandler()
+{
+ register_ct_t* table = registerTable();
+ if (table) {
+ if (_type == ATapDataModel::DATAMODEL_ENDPOINT)
+ return get_endpoint_packet_func(table);
+ else if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
+ return get_conversation_packet_func(table);
+ }
+
+ return nullptr;
+}
+
+void ATapDataModel::resetData()
+{
+ if (_disableTap)
+ return;
+
+ beginResetModel();
+ storage_ = nullptr;
+ if (_type == ATapDataModel::DATAMODEL_ENDPOINT)
+ reset_endpoint_table_data(&hash_);
+ else if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
+ reset_conversation_table_data(&hash_);
+
+ _minRelStartTime = 0;
+ _maxRelStopTime = 0;
+
+ endResetModel();
+}
+
+void ATapDataModel::updateData(GArray * newData)
+{
+ if (_disableTap)
+ return;
+
+ beginResetModel();
+ storage_ = newData;
+ endResetModel();
+
+ if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
+ ((ConversationDataModel *)(this))->doDataUpdate();
+}
+
+bool ATapDataModel::resolveNames() const
+{
+ return _resolveNames;
+}
+
+void ATapDataModel::setResolveNames(bool resolve)
+{
+ if (_resolveNames == resolve)
+ return;
+
+ beginResetModel();
+ _resolveNames = resolve;
+ endResetModel();
+}
+
+bool ATapDataModel::allowsNameResolution() const
+{
+ if (_protoId < 0)
+ return false;
+
+ QStringList mac_protos = QStringList() << "bluetooth" << "eth" << "fddi"
+ << "sll" << "tr" << "wlan";
+ QStringList net_protos = QStringList() << "dccp" << "ip" << "ipv6"
+ << "jxta" << "mptcp" << "ncp"
+ << "rsvp" << "sctp" << "sll"
+ << "tcp" << "udp";
+ QStringList transport_protos = QStringList() << "dccp" << "mptcp" << "sctp"
+ << "tcp" << "udp";
+
+ QString table_proto = proto_get_protocol_filter_name(_protoId);
+
+ if (mac_protos.contains(table_proto) && gbl_resolv_flags.mac_name)
+ return true;
+ if (net_protos.contains(table_proto) && gbl_resolv_flags.network_name)
+ return true;
+ if (transport_protos.contains(table_proto) && gbl_resolv_flags.transport_name)
+ return true;
+
+ return false;
+}
+
+void ATapDataModel::useAbsoluteTime(bool absolute)
+{
+ if (absolute == _absoluteTime)
+ return;
+
+ beginResetModel();
+ _absoluteTime = absolute;
+ endResetModel();
+}
+
+void ATapDataModel::useNanosecondTimestamps(bool nanoseconds)
+{
+ if (_nanoseconds == nanoseconds)
+ return;
+
+ beginResetModel();
+ _nanoseconds = nanoseconds;
+ endResetModel();
+}
+
+void ATapDataModel::setFilter(QString filter)
+{
+ if (_disableTap)
+ return;
+
+ _filter = filter;
+ GString * errorString = set_tap_dfilter(&hash_, !_filter.isEmpty() ? _filter.toUtf8().constData() : nullptr);
+ if (errorString && errorString->len > 0) {
+ /* If this fails, chances are that the main system failed as well. Silently exiting as the
+ * user cannot react to it */
+ disableTap();
+ }
+
+ if (errorString)
+ g_string_free(errorString, TRUE);
+}
+
+QString ATapDataModel::filter() const
+{
+ return _filter;
+}
+
+ATapDataModel::dataModelType ATapDataModel::modelType() const
+{
+ return _type;
+}
+
+bool ATapDataModel::portsAreHidden() const
+{
+ return (get_conversation_hide_ports(registerTable()));
+}
+
+bool ATapDataModel::showTotalColumn() const
+{
+ /* Implemented to ensure future changes may be done more easily */
+ return _filter.length() > 0;
+}
+
+EndpointDataModel::EndpointDataModel(int protoId, QString filter, QObject *parent) :
+ ATapDataModel(ATapDataModel::DATAMODEL_ENDPOINT, protoId, filter, parent)
+{}
+
+int EndpointDataModel::columnCount(const QModelIndex &) const
+{
+#ifdef HAVE_MAXMINDDB
+ int proto_ipv4 = proto_get_id_by_filter_name("ip");
+ int proto_ipv6 = proto_get_id_by_filter_name("ipv6");
+ if (protoId() == proto_ipv4 || protoId() == proto_ipv6) {
+ return ENDP_NUM_GEO_COLUMNS;
+ }
+#endif
+ return ENDP_NUM_COLUMNS;
+}
+
+QVariant EndpointDataModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Vertical)
+ return QVariant();
+
+ if (role == Qt::DisplayRole) {
+ switch (section) {
+ case ENDP_COLUMN_ADDR:
+ return tr("Address"); break;
+ case ENDP_COLUMN_PORT:
+ return tr("Port"); break;
+ case ENDP_COLUMN_PACKETS:
+ return tr("Packets"); break;
+ case ENDP_COLUMN_BYTES:
+ return tr("Bytes"); break;
+ case ENDP_COLUMN_PACKETS_TOTAL:
+ return tr("Total Packets"); break;
+ case ENDP_COLUMN_BYTES_TOTAL:
+ return tr("Percent Filtered"); break;
+ case ENDP_COLUMN_PKT_AB:
+ return tr("Tx Packets"); break;
+ case ENDP_COLUMN_BYTES_AB:
+ return tr("Tx Bytes"); break;
+ case ENDP_COLUMN_PKT_BA:
+ return tr("Rx Packets"); break;
+ case ENDP_COLUMN_BYTES_BA:
+ return tr("Rx Bytes"); break;
+ case ENDP_COLUMN_GEO_COUNTRY:
+ return tr("Country"); break;
+ case ENDP_COLUMN_GEO_CITY:
+ return tr("City"); break;
+ case ENDP_COLUMN_GEO_LATITUDE:
+ return tr("Latitude"); break;
+ case ENDP_COLUMN_GEO_LONGITUDE:
+ return tr("Longitude"); break;
+ case ENDP_COLUMN_GEO_AS_NUM:
+ return tr("AS Number"); break;
+ case ENDP_COLUMN_GEO_AS_ORG:
+ return tr("AS Organization"); break;
+ }
+ } else if (role == Qt::TextAlignmentRole) {
+ switch (section) {
+ case ENDP_COLUMN_ADDR:
+ case ENDP_COLUMN_GEO_COUNTRY:
+ case ENDP_COLUMN_GEO_CITY:
+ case ENDP_COLUMN_GEO_AS_ORG:
+ return Qt::AlignLeft;
+ default:
+ break;
+ }
+ return Qt::AlignRight;
+ }
+
+ return QVariant();
+}
+
+QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
+{
+ if (! idx.isValid())
+ return QVariant();
+
+ // Column text cooked representation.
+ endpoint_item_t *item = &g_array_index(storage_, endpoint_item_t, idx.row());
+ const mmdb_lookup_t *mmdb_lookup = nullptr;
+#ifdef HAVE_MAXMINDDB
+ char addr[WS_INET6_ADDRSTRLEN];
+ if (item->myaddress.type == AT_IPv4) {
+ const ws_in4_addr * ip4 = (const ws_in4_addr *) item->myaddress.data;
+ mmdb_lookup = maxmind_db_lookup_ipv4(ip4);
+ ws_inet_ntop4(ip4, addr, sizeof(addr));
+ } else if (item->myaddress.type == AT_IPv6) {
+ const ws_in6_addr * ip6 = (const ws_in6_addr *) item->myaddress.data;
+ mmdb_lookup = maxmind_db_lookup_ipv6(ip6);
+ ws_inet_ntop6(ip6, addr, sizeof(addr));
+ } else {
+ addr[0] = '\0';
+ }
+ QString ipAddress(addr);
+#endif
+
+ if (role == Qt::DisplayRole || role == ATapDataModel::UNFORMATTED_DISPLAYDATA) {
+ switch (idx.column()) {
+ case ENDP_COLUMN_ADDR: {
+ char* addr_str = get_conversation_address(NULL, &item->myaddress, _resolveNames);
+ QString q_addr_str(addr_str);
+ wmem_free(NULL, addr_str);
+ return q_addr_str;
+ }
+ case ENDP_COLUMN_PORT:
+ if (_resolveNames) {
+ char* port_str = get_endpoint_port(NULL, item, _resolveNames);
+ QString q_port_str(port_str);
+ wmem_free(NULL, port_str);
+ return q_port_str;
+ } else {
+ return quint32(item->port);
+ }
+ case ENDP_COLUMN_PACKETS:
+ {
+ qlonglong packets = (qlonglong)(item->tx_frames + item->rx_frames);
+ return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets;
+ }
+ case ENDP_COLUMN_BYTES:
+ return role == Qt::DisplayRole ? formatString((qlonglong)(item->tx_bytes + item->rx_bytes)) :
+ QVariant((qlonglong)(item->tx_bytes + item->rx_bytes));
+ case ENDP_COLUMN_PACKETS_TOTAL:
+ {
+ qlonglong packets = 0;
+ if (showTotalColumn())
+ packets = item->tx_frames_total + item->rx_frames_total;
+ return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets;
+ }
+ case ENDP_COLUMN_BYTES_TOTAL:
+ {
+ double percent = 0;
+ if (showTotalColumn()) {
+ qlonglong totalPackets = (qlonglong)(item->tx_frames_total + item->rx_frames_total);
+ qlonglong packets = (qlonglong)(item->tx_frames + item->rx_frames);
+ percent = totalPackets == 0 ? 0 : (double) packets * 100 / (double) totalPackets;
+ }
+ QString rounded = QString::number(percent, 'f', 2);
+ /* Qt guarantees that this roundtrip conversion compares equally,
+ * so filtering with equality will work as expected.
+ * Perhaps the UNFORMATTED_DISPLAYDATA role shoud be split
+ * into one used for raw data export, and one used for comparisons.
+ */
+ return role == Qt::DisplayRole ? rounded + "%" : QVariant(rounded.toDouble());
+ }
+ case ENDP_COLUMN_PKT_AB:
+ return role == Qt::DisplayRole ? QString("%L1").arg((qlonglong)item->tx_frames) : QVariant((qlonglong) item->tx_frames);
+ case ENDP_COLUMN_BYTES_AB:
+ return role == Qt::DisplayRole ? formatString((qlonglong)item->tx_bytes) : QVariant((qlonglong)item->tx_bytes);
+ case ENDP_COLUMN_PKT_BA:
+ return role == Qt::DisplayRole ? QString("%L1").arg((qlonglong)item->rx_frames) : QVariant((qlonglong) item->rx_frames);
+ case ENDP_COLUMN_BYTES_BA:
+ return role == Qt::DisplayRole ? formatString((qlonglong)item->rx_bytes) : QVariant((qlonglong)item->rx_bytes);
+ case ENDP_COLUMN_GEO_COUNTRY:
+ if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->country) {
+ return QVariant(mmdb_lookup->country);
+ }
+ return QVariant();
+ case ENDP_COLUMN_GEO_CITY:
+ if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->city) {
+ return QVariant(mmdb_lookup->city);
+ }
+ return QVariant();
+ case ENDP_COLUMN_GEO_LATITUDE:
+ if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->latitude >= -90.0 && mmdb_lookup->latitude <= 90.0) {
+ return role == Qt::DisplayRole ? QString("%L1" UTF8_DEGREE_SIGN).arg(mmdb_lookup->latitude) : QVariant(mmdb_lookup->latitude);
+ }
+ return QVariant();
+ case ENDP_COLUMN_GEO_LONGITUDE:
+ if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->longitude >= -180.0 && mmdb_lookup->longitude <= 180.0) {
+ return role == Qt::DisplayRole ? QString("%L1" UTF8_DEGREE_SIGN).arg(mmdb_lookup->longitude) : QVariant(mmdb_lookup->longitude);
+ }
+ return QVariant();
+ case ENDP_COLUMN_GEO_AS_NUM:
+ if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->as_number) {
+ return QVariant(mmdb_lookup->as_number);
+ }
+ return QVariant();
+ case ENDP_COLUMN_GEO_AS_ORG:
+ if (mmdb_lookup && mmdb_lookup->found && mmdb_lookup->as_org) {
+ return QVariant(mmdb_lookup->as_org);
+ }
+ return QVariant();
+ default:
+ return QVariant();
+ }
+ } else if (role == Qt::TextAlignmentRole) {
+ switch (idx.column()) {
+ case ENDP_COLUMN_ADDR:
+ case ENDP_COLUMN_GEO_COUNTRY:
+ case ENDP_COLUMN_GEO_CITY:
+ case ENDP_COLUMN_GEO_AS_ORG:
+ return Qt::AlignLeft;
+ default:
+ break;
+ }
+ return Qt::AlignRight;
+ } else if (role == ATapDataModel::DISPLAY_FILTER) {
+ return QString(get_endpoint_filter(item));
+ } else if (role == ATapDataModel::ROW_IS_FILTERED) {
+ return (bool)item->filtered && showTotalColumn();
+ }
+#ifdef HAVE_MAXMINDDB
+ else if (role == ATapDataModel::GEODATA_AVAILABLE) {
+ return (bool)(mmdb_lookup && maxmind_db_has_coords(mmdb_lookup));
+ } else if (role == ATapDataModel::GEODATA_LOOKUPTABLE) {
+ return VariantPointer<const mmdb_lookup_t>::asQVariant(mmdb_lookup);
+ } else if (role == ATapDataModel::GEODATA_ADDRESS) {
+ return ipAddress;
+ }
+#endif
+ else if (role == ATapDataModel::PROTO_ID) {
+ return protoId();
+ } else if (role == ATapDataModel::DATA_ADDRESS_TYPE) {
+ if (idx.column() == EndpointDataModel::ENDP_COLUMN_ADDR)
+ return (int)item->myaddress.type;
+ return (int) AT_NONE;
+ } else if (role == ATapDataModel::DATA_IPV4_INTEGER || role == ATapDataModel::DATA_IPV6_LIST) {
+ if (idx.column() == EndpointDataModel::ENDP_COLUMN_ADDR) {
+ if (role == ATapDataModel::DATA_IPV4_INTEGER && item->myaddress.type == AT_IPv4) {
+ const ws_in4_addr * ip4 = (const ws_in4_addr *) item->myaddress.data;
+ return (quint32) GUINT32_FROM_BE(*ip4);
+ }
+ else if (role == ATapDataModel::DATA_IPV6_LIST && item->myaddress.type == AT_IPv6) {
+ const ws_in6_addr * ip6 = (const ws_in6_addr *) item->myaddress.data;
+ QList<quint8> result;
+ result.reserve(16);
+ std::copy(ip6->bytes + 0, ip6->bytes + 16, std::back_inserter(result));
+ return QVariant::fromValue(result);
+ }
+ }
+ }
+
+ return QVariant();
+}
+
+ConversationDataModel::ConversationDataModel(int protoId, QString filter, QObject *parent) :
+ ATapDataModel(ATapDataModel::DATAMODEL_CONVERSATION, protoId, filter, parent)
+{}
+
+void ConversationDataModel::doDataUpdate()
+{
+ _minRelStartTime = 0;
+ _maxRelStopTime = 0;
+
+ for (int row = 0; row < rowCount(); row ++) {
+ conv_item_t *conv_item = &g_array_index(storage_, conv_item_t, row);
+
+ if (row == 0) {
+ _minRelStartTime = nstime_to_sec(&(conv_item->start_time));
+ _maxRelStopTime = nstime_to_sec(&(conv_item->stop_time));
+ } else {
+ double item_rel_start = nstime_to_sec(&(conv_item->start_time));
+ if (item_rel_start < _minRelStartTime) {
+ _minRelStartTime = item_rel_start;
+ }
+
+ double item_rel_stop = nstime_to_sec(&(conv_item->stop_time));
+ if (item_rel_stop > _maxRelStopTime) {
+ _maxRelStopTime = item_rel_stop;
+ }
+ }
+ }
+}
+
+int ConversationDataModel::columnCount(const QModelIndex &) const
+{
+ return CONV_NUM_COLUMNS;
+}
+
+QVariant ConversationDataModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Vertical)
+ return QVariant();
+
+ if (role == Qt::DisplayRole) {
+ switch (section) {
+ case CONV_COLUMN_SRC_ADDR:
+ return tr("Address A"); break;
+ case CONV_COLUMN_SRC_PORT:
+ return tr("Port A"); break;
+ case CONV_COLUMN_DST_ADDR:
+ return tr("Address B"); break;
+ case CONV_COLUMN_DST_PORT:
+ return tr("Port B"); break;
+ case CONV_COLUMN_PACKETS:
+ return tr("Packets"); break;
+ case CONV_COLUMN_BYTES:
+ return tr("Bytes"); break;
+ case CONV_COLUMN_CONV_ID:
+ return tr("Stream ID"); break;
+ case CONV_COLUMN_PACKETS_TOTAL:
+ return tr("Total Packets"); break;
+ case CONV_COLUMN_BYTES_TOTAL:
+ return tr("Percent Filtered"); break;
+ case CONV_COLUMN_PKT_AB:
+ return tr("Packets A " UTF8_RIGHTWARDS_ARROW " B"); break;
+ case CONV_COLUMN_BYTES_AB:
+ return tr("Bytes A " UTF8_RIGHTWARDS_ARROW " B"); break;
+ case CONV_COLUMN_PKT_BA:
+ return tr("Packets B " UTF8_RIGHTWARDS_ARROW " A"); break;
+ case CONV_COLUMN_BYTES_BA:
+ return tr("Bytes B " UTF8_RIGHTWARDS_ARROW " A"); break;
+ case CONV_COLUMN_START:
+ return _absoluteTime ? tr("Abs Start") : tr("Rel Start"); break;
+ case CONV_COLUMN_DURATION:
+ return tr("Duration"); break;
+ case CONV_COLUMN_BPS_AB:
+ return tr("Bits/s A " UTF8_RIGHTWARDS_ARROW " B"); break;
+ case CONV_COLUMN_BPS_BA:
+ return tr("Bits/s B " UTF8_RIGHTWARDS_ARROW " A"); break;
+ }
+ } else if (role == Qt::TextAlignmentRole) {
+ if (section == CONV_COLUMN_SRC_ADDR || section == CONV_COLUMN_DST_ADDR)
+ return Qt::AlignLeft;
+ return Qt::AlignRight;
+ }
+
+ return QVariant();
+}
+
+static const double min_bw_calc_duration_ = 5 / 1000.0; // seconds
+
+QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
+{
+ if (! idx.isValid())
+ return QVariant();
+
+ // Column text cooked representation.
+ conv_item_t *conv_item = (conv_item_t *)&g_array_index(storage_, conv_item_t,idx.row());
+
+ double duration = nstime_to_sec(&conv_item->stop_time) - nstime_to_sec(&conv_item->start_time);
+ double bps_ab = 0, bps_ba = 0;
+ bool bpsCalculated = false;
+ if (duration > min_bw_calc_duration_) {
+ bps_ab = conv_item->tx_bytes * 8 / duration;
+ bps_ba = conv_item->rx_bytes * 8 / duration;
+ bpsCalculated = true;
+ }
+
+ if (role == Qt::DisplayRole || role == ATapDataModel::UNFORMATTED_DISPLAYDATA) {
+ switch(idx.column()) {
+ case CONV_COLUMN_SRC_ADDR:
+ {
+ char* addr_str = get_conversation_address(NULL, &conv_item->src_address, _resolveNames);
+ QString q_addr_str(addr_str);
+ wmem_free(NULL, addr_str);
+ return q_addr_str;
+ }
+ case CONV_COLUMN_SRC_PORT:
+ if (_resolveNames) {
+ char* port_str = get_conversation_port(NULL, conv_item->src_port, conv_item->ctype, _resolveNames);
+ QString q_port_str(port_str);
+ wmem_free(NULL, port_str);
+ return q_port_str;
+ } else {
+ return quint32(conv_item->src_port);
+ }
+ case CONV_COLUMN_DST_ADDR:
+ {
+ char* addr_str = get_conversation_address(NULL, &conv_item->dst_address, _resolveNames);
+ QString q_addr_str(addr_str);
+ wmem_free(NULL, addr_str);
+ return q_addr_str;
+ }
+ case CONV_COLUMN_DST_PORT:
+ if (_resolveNames) {
+ char* port_str = get_conversation_port(NULL, conv_item->dst_port, conv_item->ctype, _resolveNames);
+ QString q_port_str(port_str);
+ wmem_free(NULL, port_str);
+ return q_port_str;
+ } else {
+ return quint32(conv_item->dst_port);
+ }
+ case CONV_COLUMN_PACKETS:
+ {
+ qlonglong packets = conv_item->tx_frames + conv_item->rx_frames;
+ return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets;
+ }
+ case CONV_COLUMN_BYTES:
+ return role == Qt::DisplayRole ? formatString((qlonglong)conv_item->tx_bytes + conv_item->rx_bytes) :
+ QVariant((qlonglong)conv_item->tx_bytes + conv_item->rx_bytes);
+ case CONV_COLUMN_CONV_ID:
+ return (int) conv_item->conv_id;
+ case CONV_COLUMN_PACKETS_TOTAL:
+ {
+ qlonglong packets = 0;
+ if (showTotalColumn())
+ packets = conv_item->tx_frames_total + conv_item->rx_frames_total;
+
+ return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets;
+ }
+ case CONV_COLUMN_BYTES_TOTAL:
+ {
+ double percent = 0;
+ if (showTotalColumn()) {
+ qlonglong totalPackets = (qlonglong)(conv_item->tx_frames_total + conv_item->rx_frames_total);
+ qlonglong packets = (qlonglong)(conv_item->tx_frames + conv_item->rx_frames);
+ percent = totalPackets == 0 ? 0 : (double) packets * 100 / (double) totalPackets;
+ }
+ QString rounded = QString::number(percent, 'f', 2);
+ /* Qt guarantees that this roundtrip conversion compares equally,
+ * so filtering with equality will work as expected.
+ * XXX: Perhaps the UNFORMATTED_DISPLAYDATA role shoud be split
+ * into one used for raw data export and comparisions with each
+ * other, and another for comparing with filters?
+ */
+ return role == Qt::DisplayRole ? rounded + "%" : QVariant(rounded.toDouble());
+ }
+ case CONV_COLUMN_PKT_AB:
+ {
+ qlonglong packets = conv_item->tx_frames;
+ return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets;
+ }
+ case CONV_COLUMN_BYTES_AB:
+ return role == Qt::DisplayRole ? formatString((qlonglong)conv_item->tx_bytes) : QVariant((qlonglong)conv_item->tx_bytes);
+ case CONV_COLUMN_PKT_BA:
+ {
+ qlonglong packets = conv_item->rx_frames;
+ return role == Qt::DisplayRole ? QString("%L1").arg(packets) : (QVariant)packets;
+ }
+ case CONV_COLUMN_BYTES_BA:
+ return role == Qt::DisplayRole ? formatString((qlonglong)conv_item->rx_bytes) : QVariant((qlonglong)conv_item->rx_bytes);
+ case CONV_COLUMN_START:
+ {
+ int width = _nanoseconds ? 9 : 6;
+
+ if (_absoluteTime) {
+ nstime_t *abs_time = &conv_item->start_abs_time;
+ /* XXX: QDateTime only supports millisecond resolution,
+ * and we have microseconds or nanoseconds.
+ * Should we use something else, particularly for exporting
+ * raw data? GDateTime handles microseconds.
+ */
+ QDateTime abs_dt = QDateTime::fromMSecsSinceEpoch(nstime_to_msec(abs_time));
+ if (role == Qt::DisplayRole) {
+ if (_maxRelStopTime >= 24*60*60) {
+ return abs_dt.toString(Qt::ISODateWithMs);
+ } else {
+ return abs_dt.time().toString(Qt::ISODateWithMs);
+ }
+ } else {
+ return QVariant(abs_dt);
+ }
+ } else {
+ return role == Qt::DisplayRole ?
+ QString::number(nstime_to_sec(&conv_item->start_time), 'f', width) :
+ (QVariant)((double) nstime_to_sec(&conv_item->start_time));
+ }
+ }
+ case CONV_COLUMN_DURATION:
+ {
+ int width = _nanoseconds ? 6 : 4;
+ return role == Qt::DisplayRole ? QString::number(duration, 'f', width) : (QVariant)duration;
+ }
+ case CONV_COLUMN_BPS_AB:
+ return bpsCalculated ? (role == Qt::DisplayRole ? gchar_free_to_qstring(format_size((int64_t)bps_ab, FORMAT_SIZE_UNIT_BITS_S, FORMAT_SIZE_PREFIX_SI)) : QVariant((qlonglong)bps_ab)): QVariant();
+ case CONV_COLUMN_BPS_BA:
+ return bpsCalculated ? (role == Qt::DisplayRole ? gchar_free_to_qstring(format_size((int64_t)bps_ba, FORMAT_SIZE_UNIT_BITS_S, FORMAT_SIZE_PREFIX_SI)) : QVariant((qlonglong)bps_ba)): QVariant();
+ }
+ } else if (role == Qt::ToolTipRole) {
+ if (idx.column() == CONV_COLUMN_START || idx.column() == CONV_COLUMN_DURATION)
+ return QObject::tr("Bars show the relative timeline for each conversation.");
+ } else if (role == Qt::TextAlignmentRole) {
+ if (idx.column() == CONV_COLUMN_SRC_ADDR || idx.column() == CONV_COLUMN_DST_ADDR)
+ return Qt::AlignLeft;
+ return Qt::AlignRight;
+ } else if (role == ATapDataModel::TIMELINE_DATA) {
+ struct timeline_span span_data;
+ span_data.minRelTime = _minRelStartTime;
+ span_data.maxRelTime = _maxRelStopTime;
+ span_data.startTime = nstime_to_sec(&conv_item->start_time);
+ span_data.stopTime = nstime_to_sec(&conv_item->stop_time);
+ span_data.colStart = CONV_COLUMN_START;
+ span_data.colDuration = CONV_COLUMN_DURATION;
+
+ if ((_maxRelStopTime - _minRelStartTime) > 0) {
+ return QVariant::fromValue(span_data);
+ }
+ } else if (role == ATapDataModel::ENDPOINT_DATATYPE) {
+ return (int)(conv_item->ctype);
+ } else if (role == ATapDataModel::PROTO_ID) {
+ return protoId();
+ } else if (role == ATapDataModel::CONVERSATION_ID) {
+ return (int)(conv_item->conv_id);
+ } else if (role == ATapDataModel::ROW_IS_FILTERED) {
+ return (bool)conv_item->filtered && showTotalColumn();
+ } else if (role == ATapDataModel::DATA_ADDRESS_TYPE) {
+ if (idx.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR || idx.column() == ConversationDataModel::CONV_COLUMN_DST_ADDR) {
+ address tst_address = idx.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR ? conv_item->src_address : conv_item->dst_address;
+ return (int)tst_address.type;
+ }
+ return (int) AT_NONE;
+ } else if (role == ATapDataModel::DATA_IPV4_INTEGER || role == ATapDataModel::DATA_IPV6_LIST) {
+ if (idx.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR || idx.column() == ConversationDataModel::CONV_COLUMN_DST_ADDR) {
+ address tst_address = idx.column() == ConversationDataModel::CONV_COLUMN_SRC_ADDR ? conv_item->src_address : conv_item->dst_address;
+ if (role == ATapDataModel::DATA_IPV4_INTEGER && tst_address.type == AT_IPv4) {
+ const ws_in4_addr * ip4 = (const ws_in4_addr *) tst_address.data;
+ return (quint32) GUINT32_FROM_BE(*ip4);
+ }
+ else if (role == ATapDataModel::DATA_IPV6_LIST && tst_address.type == AT_IPv6) {
+ const ws_in6_addr * ip6 = (const ws_in6_addr *) tst_address.data;
+ QList<quint8> result;
+ result.reserve(16);
+ std::copy(ip6->bytes + 0, ip6->bytes + 16, std::back_inserter(result));
+ return QVariant::fromValue(result);
+ }
+ }
+ }
+
+ return QVariant();
+}
+
+conv_item_t * ConversationDataModel::itemForRow(int row)
+{
+ if (row < 0 || row >= rowCount())
+ return nullptr;
+ return (conv_item_t *)&g_array_index(storage_, conv_item_t, row);
+}
+
+bool ConversationDataModel::showConversationId(int row) const
+{
+ if (!storage_ || row < 0 || row >= (int) storage_->len)
+ return false;
+
+ conv_item_t *conv_item = (conv_item_t *)&g_array_index(storage_, conv_item_t, row);
+ if (conv_item && (conv_item->ctype == CONVERSATION_TCP || conv_item->ctype == CONVERSATION_UDP))
+ return true;
+ return false;
+}
diff --git a/ui/qt/models/atap_data_model.h b/ui/qt/models/atap_data_model.h
new file mode 100644
index 00000000..38bdbd2f
--- /dev/null
+++ b/ui/qt/models/atap_data_model.h
@@ -0,0 +1,329 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef ATAP_DATA_MODEL_H
+#define ATAP_DATA_MODEL_H
+
+#include "config.h"
+
+#include "glib.h"
+
+#include <epan/tap.h>
+#include <epan/conversation.h>
+#include <epan/conversation_table.h>
+
+#include <QAbstractListModel>
+
+/**
+ * @brief DataModel for tap user data
+ *
+ * This datamodel provides the management for all tap data for the conversation
+ * and endpoint dialogs. It predominantly is implemented to work with conversation
+ * tap data. The management of displaying and correctly presenting the information
+ * is done in the corresponding type classes
+ *
+ * @see EndpointDataModel
+ * @see ConversationDataModel
+ */
+class ATapDataModel : public QAbstractListModel
+{
+ Q_OBJECT
+public:
+
+ enum {
+ DISPLAY_FILTER = Qt::UserRole,
+ UNFORMATTED_DISPLAYDATA,
+#ifdef HAVE_MAXMINDDB
+ GEODATA_AVAILABLE,
+ GEODATA_LOOKUPTABLE,
+ GEODATA_ADDRESS,
+#endif
+ TIMELINE_DATA,
+ ENDPOINT_DATATYPE,
+ PROTO_ID,
+ CONVERSATION_ID,
+ ROW_IS_FILTERED,
+ DATA_ADDRESS_TYPE,
+ DATA_IPV4_INTEGER,
+ DATA_IPV6_LIST,
+ };
+
+ typedef enum {
+ DATAMODEL_ENDPOINT,
+ DATAMODEL_CONVERSATION,
+ DATAMODEL_UNKNOWN
+ } dataModelType;
+
+ /**
+ * @brief Construct a new ATapDataModel object
+ *
+ * The tap will not be created automatically, but must be enabled by calling enableTap
+ *
+ * @param type an element of dataModelType. Either DATAMODEL_ENDPOINT or DATAMODEL_CONVERSATION are supported
+ * at this time
+ * @param protoId the protocol id for which the tap is created
+ * @param filter a potential filter to be used for the tap
+ * @param parent the parent for the class
+ *
+ * @see enableTap
+ */
+ explicit ATapDataModel(dataModelType type, int protoId, QString filter, QObject *parent = nullptr);
+ virtual ~ATapDataModel();
+
+ /**
+ * @brief Number of rows under the given parent in this model, which
+ * is the total number of rows for the empty QModelIndex, and 0 for
+ * any valid parent index (as no row has children; this is a flat table.)
+ *
+ * @param parent index of parent, QModelIndex() for the root
+ * @return int the number of rows under the parent
+ */
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const = 0;
+ virtual QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const = 0;
+ virtual QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const = 0;
+
+ /**
+ * @brief Returns the name for the tap being used
+ *
+ * @return QString the tap name, normally identical with the protocol name
+ */
+ QString tap() const;
+
+ /**
+ * @brief The protocol id for the tap
+ *
+ * @return int the id given in the constructor
+ */
+ int protoId() const;
+
+ /**
+ * @brief Set the filter string.
+ *
+ * A set filter can be reset by providing an empty string
+ *
+ * @param filter the filter for the tap
+ */
+ void setFilter(QString filter);
+
+ /**
+ * @brief Return a filter set for the model
+ *
+ * @return QString the filter string for the model
+ */
+ QString filter() const;
+
+ /**
+ * @brief Is the model set to resolve names in address and ports columns
+ *
+ * @return true yes, names will be resolved
+ * @return false no they won't
+ */
+ bool resolveNames() const;
+
+ /**
+ * @brief Enable or disable if names should be resolved
+ *
+ * @param resolve true if names should be resolved
+ */
+ void setResolveNames(bool resolve);
+
+ /**
+ * @brief Does the model allow names to be resolved
+ *
+ * @return true yes, names may be resolved (set via setResolveNames)
+ * @return false no, they won't be resolved
+ *
+ * @see setResolveNames
+ * @see resolveNames
+ */
+ bool allowsNameResolution() const;
+
+ /**
+ * @brief Use absolute time for any column supporting it
+ *
+ * @param absolute true to use absolute time values
+ */
+ void useAbsoluteTime(bool absolute);
+
+ /**
+ * @brief Use nanosecond timestamps if requested
+ *
+ * @param nanoseconds use nanosecond timestamps if required and requested
+ */
+ void useNanosecondTimestamps(bool nanoseconds);
+
+ /**
+ * @brief Are ports hidden for this model
+ *
+ * @return true the ports are hidden
+ * @return false the ports are not hidden
+ */
+ bool portsAreHidden() const;
+
+ /**
+ * @brief A total column is filled
+ *
+ * @return true if the column is filled
+ * @return false the column is empty
+ */
+ bool showTotalColumn() const;
+
+ /**
+ * @brief Enable tapping in this model.
+ *
+ * This will register the tap listener with the corresponding packet function.
+ * @note if the tap has not been disabled, this method will do nothing
+ *
+ * @return true the tap has been enabled
+ * @return false the tap has not been enabled
+ */
+ bool enableTap();
+
+ /**
+ * @brief Disable the tapping for this model
+ */
+ void disableTap();
+
+ /**
+ * @brief Return the model type
+ *
+ * @return dataModelType
+ */
+ dataModelType modelType() const;
+
+#ifdef HAVE_MAXMINDDB
+ /**
+ * @brief Does this model have geoip data available
+ *
+ * @return true it has
+ * @return false it has not
+ */
+ bool hasGeoIPData();
+#endif
+
+signals:
+ void tapListenerChanged(bool enable);
+
+protected:
+
+ static void tapReset(void *tapdata);
+ static void tapDraw(void *tap_data);
+
+ virtual tap_packet_cb conversationPacketHandler();
+
+ conv_hash_t * hash();
+
+ void resetData();
+ void updateData(GArray * data);
+
+ dataModelType _type;
+ GArray * storage_;
+ QString _filter;
+
+ bool _absoluteTime;
+ bool _nanoseconds;
+ bool _resolveNames;
+ bool _disableTap;
+
+ double _minRelStartTime;
+ double _maxRelStopTime;
+
+ register_ct_t* registerTable() const;
+
+private:
+ int _protoId;
+
+ conv_hash_t hash_;
+};
+
+class EndpointDataModel : public ATapDataModel
+{
+ Q_OBJECT
+public:
+
+ typedef enum
+ {
+ ENDP_COLUMN_ADDR,
+ ENDP_COLUMN_PORT,
+ ENDP_COLUMN_PACKETS,
+ ENDP_COLUMN_BYTES,
+ ENDP_COLUMN_PACKETS_TOTAL,
+ ENDP_COLUMN_BYTES_TOTAL,
+ ENDP_COLUMN_PKT_AB,
+ ENDP_COLUMN_BYTES_AB,
+ ENDP_COLUMN_PKT_BA,
+ ENDP_COLUMN_BYTES_BA,
+ ENDP_NUM_COLUMNS,
+ ENDP_COLUMN_GEO_COUNTRY = ENDP_NUM_COLUMNS,
+ ENDP_COLUMN_GEO_CITY,
+ ENDP_COLUMN_GEO_LATITUDE,
+ ENDP_COLUMN_GEO_LONGITUDE,
+ ENDP_COLUMN_GEO_AS_NUM,
+ ENDP_COLUMN_GEO_AS_ORG,
+ ENDP_NUM_GEO_COLUMNS
+ } endpoint_column_type_e;
+
+ explicit EndpointDataModel(int protoId, QString filter, QObject *parent = nullptr);
+
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const;
+
+};
+
+class ConversationDataModel : public ATapDataModel
+{
+ Q_OBJECT
+public:
+
+ typedef enum {
+ CONV_COLUMN_SRC_ADDR,
+ CONV_COLUMN_SRC_PORT,
+ CONV_COLUMN_DST_ADDR,
+ CONV_COLUMN_DST_PORT,
+ CONV_COLUMN_PACKETS,
+ CONV_COLUMN_BYTES,
+ CONV_COLUMN_CONV_ID,
+ CONV_COLUMN_PACKETS_TOTAL,
+ CONV_COLUMN_BYTES_TOTAL,
+ CONV_COLUMN_PKT_AB,
+ CONV_COLUMN_BYTES_AB,
+ CONV_COLUMN_PKT_BA,
+ CONV_COLUMN_BYTES_BA,
+ CONV_COLUMN_START,
+ CONV_COLUMN_DURATION,
+ CONV_COLUMN_BPS_AB,
+ CONV_COLUMN_BPS_BA,
+ CONV_NUM_COLUMNS,
+ CONV_INDEX_COLUMN = CONV_NUM_COLUMNS
+ } conversation_column_type_e;
+
+ explicit ConversationDataModel(int protoId, QString filter, QObject *parent = nullptr);
+
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant headerData(int section, Qt::Orientation orientation = Qt::Horizontal, int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const;
+
+ void doDataUpdate();
+
+ conv_item_t * itemForRow(int row);
+
+ /**
+ * @brief Show the conversation id if available
+ *
+ * @return true a conversation id exists
+ * @return false none available
+ */
+ bool showConversationId(int row = 0) const;
+
+};
+
+#endif // ATAP_DATA_MODEL_H
diff --git a/ui/qt/models/cache_proxy_model.cpp b/ui/qt/models/cache_proxy_model.cpp
new file mode 100644
index 00000000..9f136882
--- /dev/null
+++ b/ui/qt/models/cache_proxy_model.cpp
@@ -0,0 +1,100 @@
+/* cache_proxy_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/cache_proxy_model.h>
+
+CacheProxyModel::CacheProxyModel(QObject *parent) : QIdentityProxyModel(parent)
+{
+}
+
+QVariant CacheProxyModel::data(const QModelIndex &index, int role) const
+{
+ QModelIndex dataIndex = cache.index(index.row(), index.column());
+ if (!dataIndex.isValid()) {
+ // index is possibly outside columnCount or rowCount
+ return QVariant();
+ }
+
+ if (hasModel()) {
+ QVariant value = QIdentityProxyModel::data(index, role);
+ cache.setData(dataIndex, value, role);
+ return value;
+ } else {
+ return cache.data(dataIndex, role);
+ }
+}
+
+Qt::ItemFlags CacheProxyModel::flags(const QModelIndex &index) const
+{
+ if (hasModel()) {
+ return QIdentityProxyModel::flags(index);
+ } else {
+ // Override default to prevent editing.
+ return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+ }
+}
+
+QVariant CacheProxyModel::headerData(int section, Qt::Orientation orientation,
+ int role) const
+{
+ if (hasModel()) {
+ QVariant value = QIdentityProxyModel::headerData(section, orientation, role);
+ cache.setHeaderData(section, orientation, value, role);
+ return value;
+ } else {
+ return cache.headerData(section, orientation, role);
+ }
+}
+
+int CacheProxyModel::rowCount(const QModelIndex &parent) const
+{
+ if (hasModel()) {
+ int count = QIdentityProxyModel::rowCount(parent);
+ cache.setRowCount(count);
+ return count;
+ } else {
+ return cache.rowCount(parent);
+ }
+}
+
+int CacheProxyModel::columnCount(const QModelIndex &parent) const
+{
+ if (hasModel()) {
+ int count = QIdentityProxyModel::columnCount(parent);
+ cache.setColumnCount(count);
+ return count;
+ } else {
+ return cache.columnCount(parent);
+ }
+}
+
+/**
+ * Sets the source model from which data must be pulled. If newSourceModel is
+ * NULL, then the cache will be used.
+ */
+void CacheProxyModel::setSourceModel(QAbstractItemModel *newSourceModel)
+{
+ if (newSourceModel) {
+ cache.clear();
+ QIdentityProxyModel::setSourceModel(newSourceModel);
+ connect(newSourceModel, &QAbstractItemModel::modelReset,
+ this, &CacheProxyModel::resetCacheModel);
+ } else {
+ if (sourceModel()) {
+ // Prevent further updates to source model from invalidating cache.
+ disconnect(sourceModel(), &QAbstractItemModel::modelReset,
+ this, &CacheProxyModel::resetCacheModel);
+ }
+ QIdentityProxyModel::setSourceModel(&cache);
+ }
+}
+
+void CacheProxyModel::resetCacheModel() {
+ cache.clear();
+}
diff --git a/ui/qt/models/cache_proxy_model.h b/ui/qt/models/cache_proxy_model.h
new file mode 100644
index 00000000..70954b59
--- /dev/null
+++ b/ui/qt/models/cache_proxy_model.h
@@ -0,0 +1,47 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef CACHE_PROXY_MODEL_H
+#define CACHE_PROXY_MODEL_H
+
+#include <config.h>
+
+#include <QIdentityProxyModel>
+#include <QStandardItemModel>
+
+/**
+ * Caches any data read access to the source model, returning an older copy if
+ * the source model is invalidated.
+ *
+ * Only flat data is supported at the moment, tree models (with parents) are
+ * unsupported.
+ */
+class CacheProxyModel : public QIdentityProxyModel
+{
+ Q_OBJECT
+
+public:
+ CacheProxyModel(QObject *parent = 0);
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ void setSourceModel(QAbstractItemModel *newSourceModel);
+
+private:
+ mutable QStandardItemModel cache;
+
+ bool hasModel() const { return sourceModel() != &cache; }
+
+private slots:
+ void resetCacheModel();
+};
+#endif
diff --git a/ui/qt/models/coloring_rules_delegate.cpp b/ui/qt/models/coloring_rules_delegate.cpp
new file mode 100644
index 00000000..b86ffa97
--- /dev/null
+++ b/ui/qt/models/coloring_rules_delegate.cpp
@@ -0,0 +1,123 @@
+/* coloring_rules_delegate.cpp
+ * Delegates for editing various coloring rule fields.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/coloring_rules_delegate.h>
+#include <ui/qt/models/coloring_rules_model.h>
+#include <ui/qt/widgets/display_filter_edit.h>
+
+ColoringRulesDelegate::ColoringRulesDelegate(QObject *parent) : QStyledItemDelegate(parent)
+{
+}
+
+QWidget* ColoringRulesDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem&,
+ const QModelIndex &index) const
+{
+ switch (index.column())
+ {
+ case ColoringRulesModel::colName:
+ {
+ SyntaxLineEdit *editor = new SyntaxLineEdit(parent);
+ connect(editor, &SyntaxLineEdit::textChanged, this, &ColoringRulesDelegate::ruleNameChanged);
+ return editor;
+ }
+
+ case ColoringRulesModel::colFilter:
+ return new DisplayFilterEdit(parent);
+
+ default:
+ Q_ASSERT(FALSE);
+ return 0;
+ }
+
+ return 0;
+}
+
+void ColoringRulesDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+ switch (index.column())
+ {
+ case ColoringRulesModel::colName:
+ {
+ SyntaxLineEdit *syntaxEdit = static_cast<SyntaxLineEdit*>(editor);
+ syntaxEdit->setText(index.model()->data(index, Qt::EditRole).toString());
+ break;
+ }
+ case ColoringRulesModel::colFilter:
+ {
+ DisplayFilterEdit *displayEdit = static_cast<DisplayFilterEdit*>(editor);
+ displayEdit->setText(index.model()->data(index, Qt::EditRole).toString());
+ break;
+ }
+ default:
+ QStyledItemDelegate::setEditorData(editor, index);
+ break;
+ }
+}
+
+void ColoringRulesDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const
+{
+ switch (index.column())
+ {
+ case ColoringRulesModel::colName:
+ {
+ SyntaxLineEdit *syntaxEdit = static_cast<SyntaxLineEdit*>(editor);
+ model->setData(index, syntaxEdit->text(), Qt::EditRole);
+ if (syntaxEdit->syntaxState() == SyntaxLineEdit::Invalid) {
+ QString error_text = tr("the \"@\" symbol will be ignored.");
+ emit invalidField(index, error_text);
+ }
+ else
+ {
+ emit validField(index);
+ }
+ break;
+ }
+ case ColoringRulesModel::colFilter:
+ {
+ DisplayFilterEdit *displayEdit = static_cast<DisplayFilterEdit*>(editor);
+ model->setData(index, displayEdit->text(), Qt::EditRole);
+ if ((displayEdit->syntaxState() == SyntaxLineEdit::Invalid) &&
+ (model->data(model->index(index.row(), ColoringRulesModel::colName), Qt::CheckStateRole) == Qt::Checked))
+ {
+ model->setData(model->index(index.row(), ColoringRulesModel::colName), Qt::Unchecked, Qt::CheckStateRole);
+ emit invalidField(index, displayEdit->syntaxErrorMessage());
+ }
+ else
+ {
+ emit validField(index);
+ }
+ break;
+ }
+ default:
+ QStyledItemDelegate::setModelData(editor, model, index);
+ break;
+ }
+}
+
+void ColoringRulesDelegate::updateEditorGeometry(QWidget *editor,
+ const QStyleOptionViewItem &option, const QModelIndex&) const
+{
+ editor->setGeometry(option.rect);
+}
+
+void ColoringRulesDelegate::ruleNameChanged(const QString name)
+{
+ SyntaxLineEdit *name_edit = qobject_cast<SyntaxLineEdit*>(QObject::sender());
+ if (!name_edit) return;
+
+ if (name.isEmpty()) {
+ name_edit->setSyntaxState(SyntaxLineEdit::Empty);
+ } else if (name.contains("@")) {
+ name_edit->setSyntaxState(SyntaxLineEdit::Invalid);
+ } else {
+ name_edit->setSyntaxState(SyntaxLineEdit::Valid);
+ }
+}
diff --git a/ui/qt/models/coloring_rules_delegate.h b/ui/qt/models/coloring_rules_delegate.h
new file mode 100644
index 00000000..24ba2f38
--- /dev/null
+++ b/ui/qt/models/coloring_rules_delegate.h
@@ -0,0 +1,43 @@
+/** @file
+ *
+ * Delegates for editing various coloring rule fields.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef COLORING_RULE_DELEGATE_H
+#define COLORING_RULE_DELEGATE_H
+
+#include <config.h>
+
+#include <QStyledItemDelegate>
+#include <QModelIndex>
+
+class ColoringRulesDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ ColoringRulesDelegate(QObject *parent = 0);
+
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+ void setEditorData(QWidget *editor, const QModelIndex &index) const;
+ void setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const;
+
+ void updateEditorGeometry(QWidget *editor,
+ const QStyleOptionViewItem &option, const QModelIndex &index) const;
+
+signals:
+ void invalidField(const QModelIndex &index, const QString& errMessage) const;
+ void validField(const QModelIndex &index) const;
+
+private slots:
+ void ruleNameChanged(const QString name);
+};
+#endif // COLORING_RULE_DELEGATE_H
diff --git a/ui/qt/models/coloring_rules_model.cpp b/ui/qt/models/coloring_rules_model.cpp
new file mode 100644
index 00000000..dced9c37
--- /dev/null
+++ b/ui/qt/models/coloring_rules_model.cpp
@@ -0,0 +1,569 @@
+/* coloring_rules_model.cpp
+ * Data model for coloring rules.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+#include "config.h"
+
+#include "coloring_rules_model.h"
+
+#include <errno.h>
+
+#include "ui/ws_ui_util.h" //for color_filter_add_cb
+
+#include <ui/qt/utils/color_utils.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/utils/variant_pointer.h>
+#include <ui/qt/utils/wireshark_mime_data.h>
+
+#include <QMimeData>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QJsonArray>
+
+ColoringRuleItem::ColoringRuleItem(bool disabled, QString name, QString filter, QColor foreground, QColor background, ColoringRuleItem* parent)
+ : ModelHelperTreeItem<ColoringRuleItem>(parent),
+ disabled_(disabled),
+ name_(name),
+ filter_(filter),
+ foreground_(foreground),
+ background_(background)
+{
+}
+
+ColoringRuleItem::~ColoringRuleItem()
+{
+
+}
+
+ColoringRuleItem::ColoringRuleItem(color_filter_t *colorf, ColoringRuleItem* parent)
+ : ModelHelperTreeItem<ColoringRuleItem>(parent),
+ disabled_(colorf->disabled),
+ name_(colorf->filter_name),
+ filter_(colorf->filter_text),
+ foreground_(ColorUtils::fromColorT(colorf->fg_color)),
+ background_(ColorUtils::fromColorT(colorf->bg_color))
+{
+}
+
+ColoringRuleItem::ColoringRuleItem(const ColoringRuleItem& item)
+ : ModelHelperTreeItem<ColoringRuleItem>(item.parent_),
+ disabled_(item.disabled_),
+ name_(item.name_),
+ filter_(item.filter_),
+ foreground_(item.foreground_),
+ background_(item.background_)
+{
+}
+
+ColoringRuleItem& ColoringRuleItem::operator=(ColoringRuleItem& rhs)
+{
+ disabled_ = rhs.disabled_;
+ name_ = rhs.name_;
+ filter_ = rhs.filter_;
+ foreground_ = rhs.foreground_;
+ background_ = rhs.background_;
+ return *this;
+}
+
+// Callback for color_filters_clone.
+void
+color_filter_add_cb(color_filter_t *colorf, gpointer user_data)
+{
+ ColoringRulesModel *model = (ColoringRulesModel*)user_data;
+
+ if (model == NULL)
+ return;
+
+ model->addColor(colorf);
+}
+
+ColoringRulesModel::ColoringRulesModel(QColor defaultForeground, QColor defaultBackground, QObject *parent) :
+ QAbstractItemModel(parent),
+ root_(new ColoringRuleItem(false, "", "", QColor(), QColor(), NULL)),
+ conversation_colors_(NULL),
+ defaultForeground_(defaultForeground),
+ defaultBackground_(defaultBackground)
+
+{
+ color_filters_clone(this, color_filter_add_cb);
+}
+
+ColoringRulesModel::~ColoringRulesModel()
+{
+ delete root_;
+ color_filter_list_delete(&conversation_colors_);
+}
+
+GSList *ColoringRulesModel::createColorFilterList()
+{
+ GSList *cfl = NULL;
+ for (int row = 0; row < root_->childCount(); row++)
+ {
+ ColoringRuleItem* rule = root_->child(row);
+ if (rule == NULL)
+ continue;
+
+ color_t fg = ColorUtils::toColorT(rule->foreground_);
+ color_t bg = ColorUtils::toColorT(rule->background_);
+ color_filter_t *colorf = color_filter_new(rule->name_.toUtf8().constData(),
+ rule->filter_.toUtf8().constData(),
+ &bg, &fg, rule->disabled_);
+ cfl = g_slist_append(cfl, colorf);
+ }
+
+ return cfl;
+}
+
+void ColoringRulesModel::addColor(color_filter_t* colorf)
+{
+ if (!colorf) return;
+
+ if (strstr(colorf->filter_name, CONVERSATION_COLOR_PREFIX) != NULL) {
+ conversation_colors_ = g_slist_append(conversation_colors_, colorf);
+ } else {
+ int count = root_->childCount();
+
+ beginInsertRows(QModelIndex(), count, count);
+ ColoringRuleItem* item = new ColoringRuleItem(colorf, root_);
+ color_filter_delete(colorf);
+ root_->appendChild(item);
+ endInsertRows();
+ }
+}
+
+void ColoringRulesModel::addColor(bool disabled, QString filter, QColor foreground, QColor background)
+{
+ //add rule to top of the list
+ beginInsertRows(QModelIndex(), 0, 0);
+ ColoringRuleItem* item = new ColoringRuleItem(disabled, tr("New coloring rule"), filter, foreground, background, root_);
+ root_->prependChild(item);
+ endInsertRows();
+}
+
+
+bool ColoringRulesModel::importColors(QString filename, QString& err)
+{
+ bool success = true;
+ gchar* err_msg = NULL;
+ if (!color_filters_import(filename.toUtf8().constData(), this, &err_msg, color_filter_add_cb)) {
+ err = gchar_free_to_qstring(err_msg);
+ success = false;
+ }
+
+ return success;
+}
+
+bool ColoringRulesModel::exportColors(QString filename, QString& err)
+{
+ GSList *cfl = createColorFilterList();
+ bool success = true;
+ gchar* err_msg = NULL;
+ if (!color_filters_export(filename.toUtf8().constData(), cfl, FALSE, &err_msg)) {
+ err = gchar_free_to_qstring(err_msg);
+ success = false;
+ }
+ color_filter_list_delete(&cfl);
+
+ return success;
+}
+
+bool ColoringRulesModel::writeColors(QString& err)
+{
+ GSList *cfl = createColorFilterList();
+ bool success = true;
+ gchar* err_msg = NULL;
+ if (!color_filters_apply(conversation_colors_, cfl, &err_msg)) {
+ err = gchar_free_to_qstring(err_msg);
+ success = false;
+ }
+ if (!color_filters_write(cfl, &err_msg)) {
+ err = QString(tr("Unable to save coloring rules: %1").arg(g_strerror(errno)));
+ success = false;
+ g_free(err_msg);
+ }
+ color_filter_list_delete(&cfl);
+
+ return success;
+}
+
+bool ColoringRulesModel::insertRows(int row, int count, const QModelIndex& parent)
+{
+ // sanity check insertion
+ if (row < 0)
+ return false;
+
+ beginInsertRows(parent, row, row+(count-1));
+
+ for (int i = row; i < row + count; i++)
+ {
+ ColoringRuleItem* item = new ColoringRuleItem(true, tr("New coloring rule"), "", defaultForeground_, defaultBackground_, root_);
+ root_->insertChild(i, item);
+ /* Automatically enable the new coloring rule */
+ setData(index(i, colName, parent), Qt::Checked, Qt::CheckStateRole);
+ }
+
+ endInsertRows();
+ return true;
+}
+
+bool ColoringRulesModel::removeRows(int row, int count, const QModelIndex& parent)
+{
+ if (row < 0)
+ return false;
+
+ beginRemoveRows(parent, row, row+(count-1));
+ for (int i = row; i < row + count; i++)
+ {
+ root_->removeChild(row);
+ }
+ endRemoveRows();
+
+ return true;
+}
+
+bool ColoringRulesModel::copyRow(int dst_row, int src_row)
+{
+ if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) {
+ return false;
+ }
+
+ ColoringRuleItem* src_item = root_->child(src_row);
+ if (src_item == NULL)
+ return false;
+
+ ColoringRuleItem* dst_item = new ColoringRuleItem(*src_item);
+ if (dst_item == NULL)
+ return false;
+
+ beginInsertRows(QModelIndex(), dst_row, dst_row);
+ root_->insertChild(dst_row, dst_item);
+ endInsertRows();
+
+ return true;
+}
+
+Qt::ItemFlags ColoringRulesModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+ switch (index.column())
+ {
+ case colName:
+ flags |= (Qt::ItemIsUserCheckable|Qt::ItemIsEditable);
+ break;
+ case colFilter:
+ flags |= Qt::ItemIsEditable;
+ break;
+ }
+
+ if (index.isValid())
+ flags |= (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled);
+ else
+ flags |= Qt::ItemIsDropEnabled;
+
+ return flags;
+}
+
+
+QVariant ColoringRulesModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ ColoringRuleItem* rule = root_->child(index.row());
+ if (rule == NULL)
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ case Qt::EditRole:
+ switch(index.column())
+ {
+ case colName:
+ return rule->name_;
+ case colFilter:
+ return rule->filter_;
+ }
+ break;
+ case Qt::CheckStateRole:
+ switch(index.column())
+ {
+ case colName:
+ return rule->disabled_ ? Qt::Unchecked : Qt::Checked;
+ }
+ break;
+ case Qt::BackgroundRole:
+ return rule->background_;
+ case Qt::ForegroundRole:
+ return rule->foreground_;
+ }
+ return QVariant();
+}
+
+bool ColoringRulesModel::setData(const QModelIndex &dataIndex, const QVariant &value, int role)
+{
+ if (!dataIndex.isValid())
+ return false;
+
+ if (data(dataIndex, role) == value) {
+ // Data appears unchanged, do not do additional checks.
+ return true;
+ }
+
+ ColoringRuleItem* rule = root_->child(dataIndex.row());
+ if (rule == NULL)
+ return false;
+
+ QModelIndex topLeft = dataIndex,
+ bottomRight = dataIndex;
+
+ switch (role)
+ {
+ case Qt::EditRole:
+ switch (dataIndex.column())
+ {
+ case colName:
+ rule->name_ = value.toString();
+ break;
+ case colFilter:
+ rule->filter_ = value.toString();
+ break;
+ default:
+ return false;
+ }
+ break;
+ case Qt::CheckStateRole:
+ switch (dataIndex.column())
+ {
+ case colName:
+ rule->disabled_ = (value.toInt() == Qt::Checked) ? false : true;
+ break;
+ default:
+ return false;
+ }
+ break;
+ case Qt::BackgroundRole:
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ if (!value.canConvert<QColor>())
+#else
+ if (!value.canConvert(QVariant::Color))
+#endif
+ return false;
+
+ rule->background_ = QColor(value.toString());
+ break;
+ case Qt::ForegroundRole:
+#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
+ if (!value.canConvert<QColor>())
+#else
+ if (!value.canConvert(QVariant::Color))
+#endif
+ return false;
+
+ rule->foreground_ = QColor(value.toString());
+ break;
+ case Qt::UserRole:
+ {
+ ColoringRuleItem* new_rule = VariantPointer<ColoringRuleItem>::asPtr(value);
+ *rule = *new_rule;
+ topLeft = index(dataIndex.row(), colName);
+ bottomRight = index(dataIndex.row(), colFilter);
+ break;
+ }
+ default:
+ return false;
+ }
+
+ QVector<int> roles;
+ roles << role;
+
+ emit dataChanged(topLeft, bottomRight, roles);
+
+ return true;
+
+}
+
+QVariant ColoringRulesModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
+ return QVariant();
+
+ switch ((ColoringRulesColumn)section) {
+ case colName:
+ return tr("Name");
+ case colFilter:
+ return tr("Filter");
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+Qt::DropActions ColoringRulesModel::supportedDropActions() const
+{
+ return Qt::MoveAction | Qt::CopyAction;
+}
+
+QStringList ColoringRulesModel::mimeTypes() const
+{
+ return QStringList() << WiresharkMimeData::ColoringRulesMimeType;
+}
+
+QMimeData* ColoringRulesModel::mimeData(const QModelIndexList &indexes) const
+{
+ //if the list is empty, don't return an empty list
+ if (indexes.count() == 0)
+ return NULL;
+
+ QMimeData *mimeData = new QMimeData();
+
+ QJsonArray data;
+ foreach (const QModelIndex & index, indexes)
+ {
+ if (index.column() == 0)
+ {
+ ColoringRuleItem * item = root_->child(index.row());
+ QJsonObject entry;
+ entry["disabled"] = item->disabled_;
+ entry["name"] = item->name_;
+ entry["filter"] = item->filter_;
+ entry["foreground"] = QVariant::fromValue(item->foreground_).toString();
+ entry["background"] = QVariant::fromValue(item->background_).toString();
+ data.append(entry);
+ }
+ }
+
+ QJsonObject dataSet;
+ dataSet["coloringrules"] = data;
+ QByteArray encodedData = QJsonDocument(dataSet).toJson();
+
+ mimeData->setData(WiresharkMimeData::ColoringRulesMimeType, encodedData);
+ return mimeData;
+}
+
+bool ColoringRulesModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent)
+{
+ //clear any previous dragDrop information
+ dragDropRows_.clear();
+
+ if (action == Qt::IgnoreAction)
+ return true;
+
+ if (!data->hasFormat(WiresharkMimeData::ColoringRulesMimeType) || column > 0)
+ return false;
+
+ int beginRow;
+
+ if (row != -1)
+ beginRow = row;
+ else if (parent.isValid())
+ beginRow = parent.row();
+ else
+ beginRow = rowCount();
+
+ QList<QVariant> rules;
+
+ QJsonDocument encodedData = QJsonDocument::fromJson(data->data(WiresharkMimeData::ColoringRulesMimeType));
+ if (! encodedData.isObject() || ! encodedData.object().contains("coloringrules"))
+ return false;
+
+ QJsonArray dataArray = encodedData.object()["coloringrules"].toArray();
+
+ for (int datarow = 0; datarow < dataArray.count(); datarow++)
+ {
+ QJsonObject entry = dataArray.at(datarow).toObject();
+
+ if (! entry.contains("foreground") || ! entry.contains("background") || ! entry.contains("filter"))
+ continue;
+
+ QColor fgColor = entry["foreground"].toVariant().value<QColor>();
+ QColor bgColor = entry["background"].toVariant().value<QColor>();
+
+ ColoringRuleItem * item = new ColoringRuleItem(
+ entry["disabled"].toVariant().toBool(),
+ entry["name"].toString(),
+ entry["filter"].toString(),
+ fgColor,
+ bgColor,
+ root_);
+ rules.append(VariantPointer<ColoringRuleItem>::asQVariant(item));
+ }
+
+ insertRows(beginRow, static_cast<int>(rules.count()), QModelIndex());
+ for (int i = 0; i < rules.count(); i++) {
+ QModelIndex idx = index(beginRow, 0, QModelIndex());
+ setData(idx, rules[i], Qt::UserRole);
+ beginRow++;
+ }
+
+ return true;
+}
+
+QModelIndex ColoringRulesModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+
+ ColoringRuleItem *parent_item, *child_item;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<ColoringRuleItem*>(parent.internalPointer());
+
+ Q_ASSERT(parent_item);
+
+ child_item = parent_item->child(row);
+ if (child_item) {
+ return createIndex(row, column, child_item);
+ }
+
+ return QModelIndex();
+}
+
+QModelIndex ColoringRulesModel::parent(const QModelIndex& indexItem) const
+{
+ if (!indexItem.isValid())
+ return QModelIndex();
+
+ ColoringRuleItem* item = static_cast<ColoringRuleItem*>(indexItem.internalPointer());
+ if (item != NULL) {
+ ColoringRuleItem* parent_item = item->parentItem();
+ if (parent_item != NULL) {
+ if (parent_item == root_)
+ return QModelIndex();
+
+ return createIndex(parent_item->row(), 0, parent_item);
+ }
+ }
+
+ return QModelIndex();
+}
+
+int ColoringRulesModel::rowCount(const QModelIndex& parent) const
+{
+ ColoringRuleItem *parent_item;
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<ColoringRuleItem*>(parent.internalPointer());
+
+ if (parent_item == NULL)
+ return 0;
+
+ return parent_item->childCount();
+}
+
+int ColoringRulesModel::columnCount(const QModelIndex&) const
+{
+ return colColoringRulesMax;
+}
diff --git a/ui/qt/models/coloring_rules_model.h b/ui/qt/models/coloring_rules_model.h
new file mode 100644
index 00000000..f51a1a30
--- /dev/null
+++ b/ui/qt/models/coloring_rules_model.h
@@ -0,0 +1,102 @@
+/** @file
+ *
+ * Data model for coloring rules.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef COLORING_RULES_MODEL_H
+#define COLORING_RULES_MODEL_H
+
+#include <config.h>
+
+#include <glib.h>
+#include <epan/color_filters.h>
+
+#include <ui/qt/models/tree_model_helpers.h>
+
+#include <QList>
+#include <QColor>
+#include <QAbstractTableModel>
+#include <QSortFilterProxyModel>
+
+class ColoringRuleItem : public ModelHelperTreeItem<ColoringRuleItem>
+{
+public:
+ ColoringRuleItem(bool disabled, QString name, QString filter, QColor foreground, QColor background, ColoringRuleItem* parent);
+ virtual ~ColoringRuleItem();
+
+ ColoringRuleItem(color_filter_t *colorf, ColoringRuleItem* parent);
+ ColoringRuleItem(const ColoringRuleItem& item);
+
+ bool disabled_;
+ QString name_;
+ QString filter_;
+ QColor foreground_;
+ QColor background_;
+
+ ColoringRuleItem& operator=(ColoringRuleItem& rhs);
+
+};
+
+class ColoringRulesModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ ColoringRulesModel(QColor defaultForeground, QColor defaultBackground, QObject *parent);
+ virtual ~ColoringRulesModel();
+
+ enum ColoringRulesColumn {
+ colName = 0,
+ colFilter,
+ colColoringRulesMax
+ };
+
+ void addColor(color_filter_t* colorf);
+ void addColor(bool disabled, QString filter, QColor foreground, QColor background);
+ bool importColors(QString filename, QString& err);
+ bool exportColors(QString filename, QString& err);
+ bool writeColors(QString& err);
+
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ QVariant data(const QModelIndex &index, int role) const;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ QModelIndex index(int row, int column,
+ const QModelIndex & = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &) const;
+
+ //Drag & drop functionality
+ Qt::DropActions supportedDropActions() const;
+ QStringList mimeTypes() const;
+ QMimeData* mimeData(const QModelIndexList &indexes) const;
+ bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ bool copyRow(int dst_row, int src_row);
+
+private:
+ void populate();
+ struct _GSList *createColorFilterList();
+
+ ColoringRuleItem* root_;
+ //Save off the conversation colors, do not include in dialog
+ struct _GSList *conversation_colors_;
+
+ QColor defaultForeground_;
+ QColor defaultBackground_;
+
+ QList<int> dragDropRows_;
+};
+
+#endif // COLORING_RULES_MODEL_H
diff --git a/ui/qt/models/column_list_model.cpp b/ui/qt/models/column_list_model.cpp
new file mode 100644
index 00000000..a0e5c8ba
--- /dev/null
+++ b/ui/qt/models/column_list_model.cpp
@@ -0,0 +1,518 @@
+/* column_list_models.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/column_list_model.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/widgets/field_filter_edit.h>
+#include <ui/qt/widgets/syntax_line_edit.h>
+#include <ui/qt/utils/wireshark_mime_data.h>
+
+#include <glib.h>
+#include <epan/column.h>
+#include <epan/prefs.h>
+#include <epan/proto.h>
+#include <ui/preference_utils.h>
+
+#include <QLineEdit>
+#include <QStringList>
+#include <QComboBox>
+
+struct ListElement
+{
+ QString title;
+ QString customFields;
+ int nr;
+ int type;
+ int originalType;
+ int occurrence;
+ bool displayed;
+ bool resolved;
+};
+
+static QList<ListElement> store_;
+
+ColumnProxyModel::ColumnProxyModel(QObject * parent) :
+ QSortFilterProxyModel(parent),
+ showDisplayedOnly_(false)
+{}
+
+bool ColumnProxyModel::filterAcceptsRow(int source_row, const QModelIndex &/*source_parent*/) const
+{
+ bool displayed = false;
+ if (sourceModel() &&
+ sourceModel()->index(source_row, ColumnListModel::COL_DISPLAYED).data(ColumnListModel::DisplayedState).toBool())
+ displayed = true;
+
+ if (showDisplayedOnly_ && ! displayed)
+ return false;
+
+ return true;
+}
+
+void ColumnProxyModel::setShowDisplayedOnly(bool set)
+{
+ showDisplayedOnly_ = set;
+ invalidateFilter();
+}
+
+ColumnTypeDelegate::ColumnTypeDelegate(QObject * parent) :
+ QStyledItemDelegate(parent)
+{}
+
+QWidget *ColumnTypeDelegate::createEditor(QWidget *parent,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ QWidget *editor = nullptr;
+
+ if (index.column() == ColumnListModel::COL_TYPE)
+ {
+ QComboBox *cb_editor = new QComboBox(parent);
+
+ for (int i = 0; i < NUM_COL_FMTS; i++)
+ {
+ cb_editor->addItem(col_format_desc(i), QVariant(i));
+ if (i == index.data().toInt())
+ cb_editor->setCurrentIndex(i);
+ }
+
+ cb_editor->setFrame(false);
+ editor = cb_editor;
+ }
+ else if (index.column() == ColumnListModel::COL_FIELDS)
+ {
+ FieldFilterEdit * ff_editor = new FieldFilterEdit(parent);
+ connect(ff_editor, &FieldFilterEdit::textChanged, ff_editor, &FieldFilterEdit::checkCustomColumn);
+ ff_editor->setText(index.data().toString());
+ editor = ff_editor;
+ }
+ else if (index.column() == ColumnListModel::COL_OCCURRENCE)
+ {
+ SyntaxLineEdit * sl_editor = new SyntaxLineEdit(parent);
+ connect(sl_editor, &SyntaxLineEdit::textChanged, sl_editor, &SyntaxLineEdit::checkInteger);
+ sl_editor->setText(index.data().toString());
+ editor = sl_editor;
+ }
+
+ if (!editor) {
+ editor = QStyledItemDelegate::createEditor(parent, option, index);
+ }
+ editor->setAutoFillBackground(true);
+ return editor;
+}
+
+void ColumnTypeDelegate::setEditorData(QWidget *editor,
+ const QModelIndex &index) const
+{
+ QVariant data = index.model()->data(index);
+ if (index.column() == ColumnListModel::COL_TYPE)
+ {
+ QComboBox *comboBox = static_cast<QComboBox*>(editor);
+ comboBox->setCurrentText(data.toString());
+ }
+ else if (index.column() == ColumnListModel::COL_FIELDS)
+ {
+ if (qobject_cast<FieldFilterEdit *>(editor))
+ qobject_cast<FieldFilterEdit *>(editor)->setText(data.toString());
+ }
+ else if (index.column() == ColumnListModel::COL_OCCURRENCE)
+ {
+ if (qobject_cast<SyntaxLineEdit *>(editor))
+ qobject_cast<SyntaxLineEdit *>(editor)->setText(data.toString());
+ }
+ else
+ {
+ if (qobject_cast<QLineEdit *>(editor))
+ qobject_cast<QLineEdit *>(editor)->setText(data.toString());
+ }
+}
+
+void ColumnTypeDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const
+{
+ if (index.column() == ColumnListModel::COL_TYPE)
+ {
+ QComboBox *comboBox = static_cast<QComboBox*>(editor);
+ bool ok = false;
+ int value = comboBox->currentData().toInt(&ok);
+
+ if (ok)
+ model->setData(index, value, Qt::EditRole);
+ }
+ else if (index.column() == ColumnListModel::COL_FIELDS)
+ {
+ FieldFilterEdit * ffe = qobject_cast<FieldFilterEdit *>(editor);
+ if (ffe)
+ {
+ if (ffe->syntaxState() == SyntaxLineEdit::Valid) {
+ QModelIndex typeIndex = index.sibling(index.row(), ColumnListModel::COL_TYPE);
+ model->setData(typeIndex, COL_CUSTOM, Qt::EditRole);
+ model->setData(index, ffe->text(), Qt::EditRole);
+ }
+ else
+ {
+ ffe->setText(index.data().toString());
+ }
+ }
+
+ if (index.data().toString().length() == 0)
+ {
+ QModelIndex typeIndex = index.sibling(index.row(), ColumnListModel::COL_TYPE);
+ model->setData(typeIndex, index.data(ColumnListModel::OriginalType).toInt(), Qt::EditRole);
+
+ }
+ }
+ else if (index.column() == ColumnListModel::COL_OCCURRENCE)
+ {
+ SyntaxLineEdit * sle = qobject_cast<SyntaxLineEdit *>(editor);
+ bool ok = false;
+ if (sle)
+ {
+ sle->checkInteger(index.data().toString());
+ if (sle->syntaxState() == SyntaxLineEdit::Valid)
+ ok = true;
+ }
+
+ if (ok)
+ {
+ QModelIndex typeIndex = index.sibling(index.row(), ColumnListModel::COL_TYPE);
+ model->setData(typeIndex, COL_CUSTOM, Qt::EditRole);
+ model->setData(index, sle->text(), Qt::EditRole);
+ }
+ else if (sle)
+ {
+ sle->setText(index.data().toString());
+ }
+
+ if (index.data().toString().length() == 0)
+ {
+ QModelIndex typeIndex = index.sibling(index.row(), ColumnListModel::COL_TYPE);
+ model->setData(typeIndex, index.data(ColumnListModel::OriginalType).toInt(), Qt::EditRole);
+
+ }
+ }
+ else
+ QStyledItemDelegate::setModelData(editor, model, index);
+}
+
+void ColumnTypeDelegate::updateEditorGeometry(QWidget *editor,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &/* index */) const
+{
+ editor->setGeometry(option.rect);
+}
+
+ColumnListModel::ColumnListModel(QObject * parent):
+ QAbstractTableModel(parent)
+{
+ populate();
+}
+
+QVariant ColumnListModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (section > ColumnListModel::COL_RESOLVED || orientation != Qt::Horizontal ||
+ role != Qt::DisplayRole)
+ return QVariant();
+
+ return headerTitle(section);
+}
+
+int ColumnListModel::rowCount(const QModelIndex &/*parent*/) const
+{
+ return static_cast<int>(store_.count());
+}
+
+int ColumnListModel::columnCount(const QModelIndex &/*parent*/) const
+{
+ return ColumnListModel::COL_RESOLVED + 1;
+}
+
+QString ColumnListModel::headerTitle(int section) const
+{
+ switch (section)
+ {
+ case ColumnListModel::COL_DISPLAYED:
+ return tr("Displayed");
+ case ColumnListModel::COL_TITLE:
+ return tr("Title");
+ case ColumnListModel::COL_TYPE:
+ return tr("Type");
+ case ColumnListModel::COL_FIELDS:
+ return tr("Fields");
+ case ColumnListModel::COL_OCCURRENCE:
+ return tr("Field Occurrence");
+ case ColumnListModel::COL_RESOLVED:
+ return tr("Resolved");
+ }
+
+ return QString();
+}
+
+void ColumnListModel::populate()
+{
+ store_.clear();
+
+ int nr = 0;
+
+ for (GList *cur = g_list_first(prefs.col_list); cur != NULL && cur->data != NULL; cur = cur->next) {
+ fmt_data *cfmt = (fmt_data *) cur->data;
+ ListElement ne;
+ ne.nr = nr;
+ ne.displayed = cfmt->visible;
+ ne.title = cfmt->title;
+ ne.type = ne.originalType = cfmt->fmt;
+ ne.customFields = cfmt->custom_fields;
+ ne.occurrence = cfmt->custom_occurrence;
+ ne.resolved = cfmt->resolved;
+
+ nr++;
+ store_ << ne;
+ }
+}
+
+QVariant ColumnListModel::data(const QModelIndex &index, int role) const
+{
+ if (! index.isValid() || index.column() >= store_.count())
+ return QVariant();
+
+ ListElement ne = store_.at(index.row());
+
+ if (role == Qt::DisplayRole)
+ {
+ switch (index.column())
+ {
+ case COL_DISPLAYED:
+ case COL_RESOLVED:
+ return QVariant();
+ case ColumnListModel::COL_TITLE:
+ return ne.title;
+ case ColumnListModel::COL_TYPE:
+ return col_format_desc(ne.type);
+ case ColumnListModel::COL_FIELDS:
+ return ne.customFields;
+ case ColumnListModel::COL_OCCURRENCE:
+ return ne.customFields.length() > 0 ? QVariant::fromValue(ne.occurrence) : QVariant();
+ }
+ }
+ else if (role == Qt::CheckStateRole)
+ {
+ if (index.column() == COL_DISPLAYED)
+ {
+ return ne.displayed ? Qt::Checked : Qt::Unchecked;
+ }
+ else if (index.column() == COL_RESOLVED)
+ {
+ QModelIndex fieldsIndex = index.sibling(index.row(), ColumnListModel::COL_FIELDS);
+ if (column_prefs_custom_resolve(fieldsIndex.data().toString().toUtf8().constData())) {
+ return ne.resolved ? Qt::Checked : Qt::Unchecked;
+ }
+ }
+ }
+ else if (role == Qt::ToolTipRole)
+ {
+ if (index.column() == COL_RESOLVED)
+ {
+ return tr("<html>Show human-readable strings instead of raw values for fields. Only applicable to custom columns with fields that have value strings.</html>");
+ }
+ }
+ else if (role == OriginalType)
+ return QVariant::fromValue(ne.originalType);
+ else if (role == DisplayedState)
+ return QVariant::fromValue(ne.displayed);
+
+ return QVariant();
+}
+
+Qt::ItemFlags ColumnListModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags defaultFlags = QAbstractTableModel::flags(index);
+ if (index.isValid() && index.row() < store_.count())
+ {
+ ListElement ne = store_.at(index.row());
+
+ Qt::ItemFlags flags = Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
+
+ if (index.column() == COL_DISPLAYED || index.column() == COL_RESOLVED) {
+ flags |= Qt::ItemIsUserCheckable;
+ } else {
+ flags |= Qt::ItemIsEditable;
+ }
+
+ return flags;
+ }
+ else
+ return Qt::ItemIsDropEnabled | defaultFlags;
+}
+
+QStringList ColumnListModel::mimeTypes() const
+{
+ return QStringList() << WiresharkMimeData::ColumnListMimeType;
+}
+
+QMimeData *ColumnListModel::mimeData(const QModelIndexList &indexes) const
+{
+ QMimeData *mimeData = new QMimeData;
+
+ int row = -1;
+ if (indexes.count() > 0)
+ row = indexes.at(0).row();
+
+ mimeData->setData(WiresharkMimeData::ColumnListMimeType, QString::number(row).toUtf8());
+ return mimeData;
+}
+
+bool ColumnListModel::canDropMimeData(const QMimeData *data,
+ Qt::DropAction /* action */, int /* row */, int /* column */, const QModelIndex &parent) const
+{
+ if (parent.isValid() || ! data->hasFormat(WiresharkMimeData::ColumnListMimeType))
+ return false;
+
+ return true;
+}
+
+bool ColumnListModel::dropMimeData(const QMimeData *data,
+ Qt::DropAction action, int row, int column, const QModelIndex &parent)
+{
+ int moveTo;
+
+ if (!canDropMimeData(data, action, row, column, parent))
+ return false;
+
+ if (action == Qt::IgnoreAction || parent.isValid())
+ return true;
+
+ if (row != -1)
+ moveTo = row;
+ else
+ moveTo = rowCount(QModelIndex());
+
+ bool ok = false;
+ int moveFrom = QString(data->data(WiresharkMimeData::ColumnListMimeType)).toInt(&ok);
+ if (! ok)
+ return false;
+
+ if (moveFrom < moveTo)
+ moveTo = moveTo - 1;
+
+ if (moveTo >= store_.count())
+ moveTo = static_cast<int>(store_.count()) - 1;
+
+ beginResetModel();
+ store_.move(moveFrom, moveTo);
+ endResetModel();
+
+ return true;
+}
+
+Qt::DropActions ColumnListModel::supportedDropActions() const
+{
+ return Qt::MoveAction;
+}
+
+bool ColumnListModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid() || ! value.isValid())
+ return false;
+
+ bool change = false;
+ if (role == Qt::CheckStateRole && index.column() == ColumnListModel::COL_DISPLAYED)
+ {
+ store_[index.row()].displayed = value.toInt() == Qt::Checked ? true : false;
+ change = true;
+ }
+ else if (index.column() == ColumnListModel::COL_TYPE)
+ {
+ bool ok = false;
+ int val = value.toInt(&ok);
+ if (ok)
+ store_[index.row()].type = val;
+ }
+ else if (index.column() == ColumnListModel::COL_TITLE)
+ {
+ store_[index.row()].title = value.toString();
+ }
+ else if (index.column() == ColumnListModel::COL_FIELDS)
+ {
+ store_[index.row()].customFields = value.toString();
+ }
+ else if (index.column() == ColumnListModel::COL_OCCURRENCE)
+ {
+ bool ok = false;
+ int val = value.toInt(&ok);
+ if (ok)
+ store_[index.row()].occurrence = val;
+ }
+ else if (role == Qt::CheckStateRole && index.column() == ColumnListModel::COL_RESOLVED)
+ {
+ store_[index.row()].resolved = value.toInt() == Qt::Checked ? true : false;
+ }
+
+ if (change)
+ emit dataChanged(index, index);
+
+ return change;
+}
+
+void ColumnListModel::saveColumns()
+{
+ GList *new_col_list = Q_NULLPTR;
+
+ for (int row = 0; row < store_.count(); row++)
+ {
+ fmt_data * cfmt = g_new0(fmt_data, 1);
+ ListElement elem = store_.at(row);
+
+ cfmt->title = qstring_strdup(elem.title);
+ cfmt->visible = elem.displayed;
+ cfmt->fmt = elem.type;
+ cfmt->resolved = TRUE;
+ if (cfmt->fmt == COL_CUSTOM)
+ {
+ cfmt->custom_fields = qstring_strdup(elem.customFields);
+ cfmt->custom_occurrence = elem.occurrence;
+ cfmt->resolved = elem.resolved;
+ }
+
+ new_col_list = g_list_append(new_col_list, cfmt);
+ }
+
+ while (prefs.col_list)
+ column_prefs_remove_link(prefs.col_list);
+
+ prefs.col_list = new_col_list;
+}
+
+void ColumnListModel::addEntry()
+{
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ ListElement elem;
+ elem.nr = rowCount();
+ elem.title = tr("New Column");
+ elem.displayed = true;
+ elem.type = elem.originalType = COL_NUMBER;
+ elem.occurrence = 0;
+ elem.customFields = QString();
+ elem.resolved = true;
+ store_ << elem;
+ endInsertRows();
+}
+
+void ColumnListModel::deleteEntry(int row)
+{
+ beginRemoveRows(QModelIndex(), row, row);
+ store_.removeAt(row);
+ endRemoveRows();
+}
+
+void ColumnListModel::reset()
+{
+ beginResetModel();
+ populate();
+ endResetModel();
+}
diff --git a/ui/qt/models/column_list_model.h b/ui/qt/models/column_list_model.h
new file mode 100644
index 00000000..c4739f96
--- /dev/null
+++ b/ui/qt/models/column_list_model.h
@@ -0,0 +1,96 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef COLUMN_LIST_MODELS_H
+#define COLUMN_LIST_MODELS_H
+
+#include <QAbstractListModel>
+#include <QSortFilterProxyModel>
+#include <QStyledItemDelegate>
+#include <QSortFilterProxyModel>
+#include <QMimeData>
+
+class ColumnProxyModel : public QSortFilterProxyModel
+{
+public:
+ ColumnProxyModel(QObject *parent = Q_NULLPTR);
+
+ void setShowDisplayedOnly(bool set);
+
+protected:
+ virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
+
+private:
+ bool showDisplayedOnly_;
+};
+
+class ColumnTypeDelegate : public QStyledItemDelegate
+{
+public:
+ ColumnTypeDelegate(QObject * parent = Q_NULLPTR);
+
+ QWidget * createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+
+ void setEditorData(QWidget *editor, const QModelIndex &index) const override;
+ void setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const override;
+
+ void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+};
+
+class ColumnListModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ ColumnListModel(QObject * parent = Q_NULLPTR);
+
+ enum {
+ COL_DISPLAYED,
+ COL_TITLE,
+ COL_TYPE,
+ COL_FIELDS,
+ COL_OCCURRENCE,
+ COL_RESOLVED
+ };
+
+ enum {
+ OriginalType = Qt::UserRole,
+ DisplayedState
+ };
+
+ void saveColumns();
+
+ void addEntry();
+ void deleteEntry(int row);
+ void reset();
+
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
+
+ virtual QStringList mimeTypes() const;
+ virtual QMimeData *mimeData(const QModelIndexList &indexes) const;
+ virtual Qt::DropActions supportedDropActions() const;
+ virtual bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const;
+ virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
+
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+private:
+ QString headerTitle(int section) const;
+
+ void populate();
+};
+
+#endif // COLUMN_LIST_MODELS_H
diff --git a/ui/qt/models/credentials_model.cpp b/ui/qt/models/credentials_model.cpp
new file mode 100644
index 00000000..8bc649f1
--- /dev/null
+++ b/ui/qt/models/credentials_model.cpp
@@ -0,0 +1,155 @@
+/*
+ * credentials_model.h
+ *
+ * Copyright 2019 - Dario Lombardo <lomato@gmail.com>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "credentials_model.h"
+
+#include <file.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+
+CredentialsModel::CredentialsModel(QObject *parent)
+ :QAbstractListModel(parent)
+{
+}
+
+CredentialsModel::~CredentialsModel()
+{
+ clear();
+}
+
+int CredentialsModel::rowCount(const QModelIndex &) const
+{
+ return static_cast<int>(credentials_.count());
+}
+
+int CredentialsModel::columnCount(const QModelIndex &) const
+{
+ return COL_INFO + 1;
+}
+
+QVariant CredentialsModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ tap_credential_t * auth = credentials_.at(index.row());
+ if (!auth)
+ return QVariant();
+
+
+ if (role == Qt::DisplayRole) {
+ switch (index.column()) {
+ case COL_NUM:
+ return QVariant::fromValue(auth->num);
+ case COL_PROTO:
+ return QString(auth->proto);
+ case COL_USERNAME:
+ return QString(auth->username);
+ case COL_INFO:
+ return QString(auth->info);
+ default:
+ return QVariant();
+ }
+ }
+
+ if (role == Qt::UserRole) {
+ switch (index.column()) {
+ case COL_NUM:
+ if (auth->num > 0)
+ return QVariant::fromValue(auth->num);
+ break;
+ case COL_USERNAME:
+ if (auth->username_num > 0)
+ return QVariant::fromValue(auth->username_num);
+ break;
+ default:
+ return QVariant();
+ }
+ }
+
+ if (role == CredentialsModel::ColumnHFID)
+ return QVariant::fromValue(auth->password_hf_id);
+
+ if (role == Qt::ToolTipRole) {
+ const QString select_msg(tr("Click to select the packet"));
+ switch (index.column()) {
+ case COL_NUM:
+ if (auth->num > 0)
+ return select_msg;
+ break;
+ case COL_USERNAME:
+ if (auth->username_num > 0) {
+ if (auth->username_num != auth->num)
+ return QString(tr("Click to select the packet with username"));
+ else
+ return select_msg;
+ } else {
+ return QString(tr("Username not available"));
+ }
+ break;
+ default:
+ return QVariant();
+ }
+ }
+
+ return QVariant();
+}
+
+void CredentialsModel::addRecord(const tap_credential_t* auth)
+{
+ emit beginInsertRows(QModelIndex(), rowCount(), rowCount() + 1);
+
+ tap_credential_t* clone = new tap_credential_t;
+ clone->num = auth->num;
+ clone->username_num = auth->username_num;
+ clone->password_hf_id = auth->password_hf_id;
+ clone->username = qstring_strdup(auth->username);
+ clone->proto = auth->proto;
+ clone->info = qstring_strdup(auth->info);
+ credentials_.append(clone);
+
+ emit endInsertRows();
+}
+
+void CredentialsModel::clear()
+{
+ if (!credentials_.isEmpty()) {
+ emit beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
+ for (QList<tap_credential_t*>::iterator itr = credentials_.begin(); itr != credentials_.end(); ++itr) {
+ g_free((*itr)->username);
+ g_free((*itr)->info);
+ delete *itr;
+ }
+ credentials_.clear();
+ emit endRemoveRows();
+ }
+}
+
+QVariant CredentialsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ if (orientation == Qt::Horizontal) {
+ switch (section) {
+ case COL_NUM:
+ return QString(tr("Packet No."));
+ case COL_PROTO:
+ return QString(tr("Protocol"));
+ case COL_USERNAME:
+ return QString(tr("Username"));
+ case COL_INFO:
+ return QString(tr("Additional Info"));
+ }
+ }
+
+ return QVariant();
+}
diff --git a/ui/qt/models/credentials_model.h b/ui/qt/models/credentials_model.h
new file mode 100644
index 00000000..cc0f4d3d
--- /dev/null
+++ b/ui/qt/models/credentials_model.h
@@ -0,0 +1,53 @@
+/** @file
+ *
+ * Copyright 2019 - Dario Lombardo <lomato@gmail.com>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef CREDENTIALS_MODELS_H
+#define CREDENTIALS_MODELS_H
+
+#include <QAbstractListModel>
+#include <QList>
+
+#include <epan/tap.h>
+#include <capture_file.h>
+#include <ui/tap-credentials.h>
+
+class CredentialsModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ CredentialsModel(QObject *parent);
+ ~CredentialsModel();
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const ;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ void addRecord(const tap_credential_t *rec);
+ void clear();
+
+ enum {
+ COL_NUM,
+ COL_PROTO,
+ COL_USERNAME,
+ COL_INFO
+ };
+
+ enum {
+ ColumnHFID = Qt::UserRole + 1
+ };
+
+private:
+ QList<tap_credential_t*> credentials_;
+
+};
+
+#endif // CREDENTIALS_MODELS_H
diff --git a/ui/qt/models/decode_as_delegate.cpp b/ui/qt/models/decode_as_delegate.cpp
new file mode 100644
index 00000000..3c300a79
--- /dev/null
+++ b/ui/qt/models/decode_as_delegate.cpp
@@ -0,0 +1,390 @@
+/* decode_as_delegate.cpp
+ * Delegates for editing various field types in a Decode As record.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "decode_as_delegate.h"
+
+#include "epan/decode_as.h"
+#include "epan/epan_dissect.h"
+
+#include <ui/qt/utils/variant_pointer.h>
+
+#include <QComboBox>
+#include <QEvent>
+#include <QLineEdit>
+#include <QTreeView>
+
+typedef struct _dissector_info_t {
+ QString proto_name;
+ dissector_handle_t dissector_handle;
+} dissector_info_t;
+
+Q_DECLARE_METATYPE(dissector_info_t *)
+
+DecodeAsDelegate::DecodeAsDelegate(QObject *parent, capture_file *cf)
+ : QStyledItemDelegate(parent),
+ cap_file_(cf)
+{
+ cachePacketProtocols();
+}
+
+DecodeAsItem* DecodeAsDelegate::indexToField(const QModelIndex &index) const
+{
+ const QVariant v = index.model()->data(index, Qt::UserRole);
+ return static_cast<DecodeAsItem*>(v.value<void *>());
+}
+
+void DecodeAsDelegate::cachePacketProtocols()
+{
+ //cache the list of potential decode as protocols in the current packet
+ if (cap_file_ && cap_file_->edt) {
+
+ wmem_list_frame_t * protos = wmem_list_head(cap_file_->edt->pi.layers);
+ guint8 curr_layer_num = 1;
+
+ while (protos != NULL) {
+ int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos));
+ const gchar * proto_name = proto_get_protocol_filter_name(proto_id);
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_t *entry = (decode_as_t *) cur->data;
+ if (g_strcmp0(proto_name, entry->name) == 0) {
+ packet_proto_data_t proto_data;
+
+ proto_data.table_ui_name = get_dissector_table_ui_name(entry->table_name);
+ proto_data.proto_name = proto_name;
+ proto_data.curr_layer_num = curr_layer_num;
+
+ packet_proto_list_.append(proto_data);
+ }
+ }
+ protos = wmem_list_frame_next(protos);
+ curr_layer_num++;
+ }
+ }
+}
+
+void DecodeAsDelegate::collectDAProtocols(QSet<QString>& all_protocols, QList<QString>& current_list) const
+{
+ // If a packet is selected group its tables at the top in order
+ // from last-dissected to first.
+
+ //gather the initial list
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_t *entry = (decode_as_t *) cur->data;
+ const char *table_name = get_dissector_table_ui_name(entry->table_name);
+ if (table_name) {
+ all_protocols.insert(get_dissector_table_ui_name(entry->table_name));
+ }
+ }
+
+ //filter out those in selected packet
+ foreach(packet_proto_data_t proto, packet_proto_list_)
+ {
+ current_list.append(proto.table_ui_name);
+ all_protocols.remove(proto.table_ui_name);
+ }
+}
+
+//Determine if there are multiple values in the selector field that would
+//correspond to using a combo box
+bool DecodeAsDelegate::isSelectorCombo(DecodeAsItem* item) const
+{
+ const gchar *proto_name = NULL;
+
+ foreach(packet_proto_data_t proto, packet_proto_list_)
+ {
+ if (g_strcmp0(proto.table_ui_name, item->tableUIName()) == 0) {
+ proto_name = proto.proto_name;
+ break;
+ }
+ }
+
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_t *entry = (decode_as_t *) cur->data;
+ if ((g_strcmp0(proto_name, entry->name) == 0) &&
+ (g_strcmp0(item->tableName(), entry->table_name) == 0) &&
+ (cap_file_ && cap_file_->edt)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void DecodeAsDelegate::decodeAddProtocol(const gchar *, const gchar *proto_name, gpointer value, gpointer user_data)
+{
+ QList<dissector_info_t*>* proto_list = (QList<dissector_info_t*>*)user_data;
+
+ if (!proto_list)
+ return;
+
+ dissector_info_t *dissector_info = new dissector_info_t();
+ dissector_info->proto_name = proto_name;
+ dissector_info->dissector_handle = (dissector_handle_t) value;
+
+ proto_list->append(dissector_info);
+}
+
+QWidget* DecodeAsDelegate::createEditor(QWidget *parentWidget, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ DecodeAsItem* item = indexToField(index);
+ QWidget *editor = nullptr;
+
+ switch(index.column())
+ {
+ case DecodeAsModel::colTable:
+ {
+ QComboBox *cb_editor = new QComboBox(parentWidget);
+ QSet<QString> da_set;
+ QList<QString> packet_list;
+ QString table_ui_name;
+
+ collectDAProtocols(da_set, packet_list);
+
+ cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+
+ //put the protocols from the packet first in the combo box
+ foreach (table_ui_name, packet_list) {
+ cb_editor->addItem(table_ui_name, table_ui_name);
+ }
+ if (packet_list.count() > 0) {
+ cb_editor->insertSeparator(static_cast<int>(packet_list.count()));
+ }
+
+ //put the rest of the protocols in the combo box
+ QList<QString> da_list = da_set.values();
+ std::sort(da_list.begin(), da_list.end());
+
+ foreach (table_ui_name, da_list) {
+ cb_editor->addItem(table_ui_name, table_ui_name);
+ }
+
+ //Make sure the combo box is at least as wide as the column
+ QTreeView* parentTree = (QTreeView*)parent();
+ int protoColWidth = parentTree->columnWidth(index.column());
+ if (protoColWidth > cb_editor->size().width())
+ cb_editor->setFixedWidth(protoColWidth);
+
+ editor = cb_editor;
+ break;
+ }
+ case DecodeAsModel::colSelector:
+ {
+ QComboBox *cb_editor = NULL;
+ const gchar *proto_name = NULL;
+ bool edt_present = cap_file_ && cap_file_->edt;
+ gint8 curr_layer_num_saved = edt_present ? cap_file_->edt->pi.curr_layer_num : 0;
+ QList<guint8> proto_layers;
+
+ foreach(packet_proto_data_t proto, packet_proto_list_)
+ {
+ if (g_strcmp0(proto.table_ui_name, item->tableUIName()) == 0) {
+ if (edt_present) {
+ proto_layers << proto.curr_layer_num;
+ }
+ proto_name = proto.proto_name;
+ }
+ }
+
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_t *entry = (decode_as_t *) cur->data;
+ if ((g_strcmp0(proto_name, entry->name) == 0) &&
+ (g_strcmp0(item->tableName(), entry->table_name) == 0)) {
+ if (edt_present) {
+ //create a combobox to add the entries from the packet
+ cb_editor = new QComboBox(parentWidget);
+
+ //Don't limit user to just what's in combo box
+ cb_editor->setEditable(true);
+
+ cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+
+ //add the current value of the column
+ const QString& current_value = index.model()->data(index, Qt::EditRole).toString();
+ if (!current_value.isEmpty())
+ cb_editor->addItem(current_value);
+
+ //get the value(s) from the packet
+ foreach(guint8 current_layer, proto_layers) {
+ cap_file_->edt->pi.curr_layer_num = current_layer;
+ for (uint ni = 0; ni < entry->num_items; ni++) {
+ if (entry->values[ni].num_values == 1) { // Skip over multi-value ("both") entries
+ QString entryStr = DecodeAsModel::entryString(entry->table_name,
+ entry->values[ni].build_values[0](&cap_file_->edt->pi));
+ //don't duplicate entries
+ if (cb_editor->findText(entryStr) < 0)
+ cb_editor->addItem(entryStr);
+ }
+ }
+ }
+ cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved;
+ cb_editor->setCurrentIndex(entry->default_index_value);
+
+ //Make sure the combo box is at least as wide as the column
+ QTreeView* parentTree = (QTreeView*)parent();
+ int protoColWidth = parentTree->columnWidth(index.column());
+ if (protoColWidth > cb_editor->size().width())
+ cb_editor->setFixedWidth(protoColWidth);
+ }
+ break;
+ }
+ }
+
+ //if there isn't a need for a combobox, just let user have a text box for direct edit
+ if (cb_editor) {
+ editor = cb_editor;
+ } else {
+ editor = QStyledItemDelegate::createEditor(parentWidget, option, index);
+ }
+ break;
+ }
+
+ case DecodeAsModel::colProtocol:
+ {
+ QComboBox *cb_editor = new QComboBox(parentWidget);
+ QList<dissector_info_t*> protocols;
+
+ cb_editor->setSizeAdjustPolicy(QComboBox::AdjustToContents);
+
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_t *entry = (decode_as_t *) cur->data;
+ if (g_strcmp0(item->tableName(), entry->table_name) == 0) {
+ entry->populate_list(entry->table_name, decodeAddProtocol, &protocols);
+ break;
+ }
+ }
+
+ // Sort by description in a human-readable way (case-insensitive, etc)
+ std::sort(protocols.begin(), protocols.end(), [](dissector_info_t* d1, dissector_info_t* d2) {
+ return d1->proto_name.localeAwareCompare(d2->proto_name) < 0;
+ });
+
+ cb_editor->addItem(DECODE_AS_NONE);
+ cb_editor->insertSeparator(cb_editor->count());
+
+ for (dissector_info_t* protocol : protocols)
+ {
+ // Make it easy to reset to the default dissector
+ if (protocol->proto_name == item->defaultDissector()) {
+ cb_editor->insertItem(0, protocol->proto_name, VariantPointer<dissector_info_t>::asQVariant(protocol));
+ } else {
+ cb_editor->addItem(protocol->proto_name, VariantPointer<dissector_info_t>::asQVariant(protocol));
+ }
+ }
+
+ //Make sure the combo box is at least as wide as the column
+ QTreeView* parentTree = (QTreeView*)parent();
+ int protoColWidth = parentTree->columnWidth(index.column());
+ if (protoColWidth > cb_editor->size().width())
+ cb_editor->setFixedWidth(protoColWidth);
+
+ editor = cb_editor;
+ break;
+ }
+ }
+
+ if (editor) {
+ editor->setAutoFillBackground(true);
+ }
+ return editor;
+}
+
+void DecodeAsDelegate::destroyEditor(QWidget *editor, const QModelIndex &index) const
+{
+ if (index.column() == DecodeAsModel::colProtocol) {
+ QComboBox *cb_editor = (QComboBox*)editor;
+ for (int i=0; i < cb_editor->count(); ++i) {
+ delete VariantPointer<dissector_info_t>::asPtr(cb_editor->itemData(i));
+ }
+ }
+ QStyledItemDelegate::destroyEditor(editor, index);
+}
+
+void DecodeAsDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+ DecodeAsItem* item = indexToField(index);
+
+ switch(index.column())
+ {
+ case DecodeAsModel::colTable:
+ case DecodeAsModel::colProtocol:
+ {
+ QComboBox *combobox = static_cast<QComboBox *>(editor);
+ const QString &data = index.model()->data(index, Qt::EditRole).toString();
+ combobox->setCurrentText(data);
+ }
+ break;
+ case DecodeAsModel::colSelector:
+ if (isSelectorCombo(item)) {
+ QComboBox *combobox = static_cast<QComboBox *>(editor);
+ const QString &data = index.model()->data(index, Qt::EditRole).toString();
+ combobox->setCurrentText(data);
+ }
+ else {
+ QStyledItemDelegate::setEditorData(editor, index);
+ }
+ break;
+ default:
+ QStyledItemDelegate::setEditorData(editor, index);
+ break;
+ }
+}
+
+void DecodeAsDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const
+{
+ DecodeAsItem* item = indexToField(index);
+
+ switch(index.column())
+ {
+ case DecodeAsModel::colTable:
+ {
+ QComboBox *combobox = static_cast<QComboBox *>(editor);
+ const QString &data = combobox->currentText();
+ model->setData(index, data, Qt::EditRole);
+ break;
+ }
+ case DecodeAsModel::colSelector:
+ if (isSelectorCombo(item)) {
+ QComboBox *combobox = static_cast<QComboBox *>(editor);
+ const QString &data = combobox->currentText();
+ model->setData(index, data, Qt::EditRole);
+ } else {
+ QStyledItemDelegate::setModelData(editor, model, index);
+ }
+ break;
+ case DecodeAsModel::colProtocol:
+ {
+ QComboBox *combobox = static_cast<QComboBox *>(editor);
+
+ //set the dissector handle
+ QVariant var = combobox->itemData(combobox->currentIndex());
+ dissector_info_t* dissector_info = VariantPointer<dissector_info_t>::asPtr(var);
+ if (dissector_info != NULL) {
+ model->setData(index, VariantPointer<dissector_handle>::asQVariant(dissector_info->dissector_handle), Qt::EditRole);
+ } else {
+ model->setData(index, QVariant(), Qt::EditRole);
+ }
+ break;
+ }
+ default:
+ QStyledItemDelegate::setModelData(editor, model, index);
+ break;
+ }
+}
+
+#if 0
+// Qt docs suggest overriding updateEditorGeometry, but the defaults seem sane.
+void UatDelegate::updateEditorGeometry(QWidget *editor,
+ const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyledItemDelegate::updateEditorGeometry(editor, option, index);
+}
+#endif
diff --git a/ui/qt/models/decode_as_delegate.h b/ui/qt/models/decode_as_delegate.h
new file mode 100644
index 00000000..d0457764
--- /dev/null
+++ b/ui/qt/models/decode_as_delegate.h
@@ -0,0 +1,59 @@
+/** @file
+ *
+ * Delegates for editing various field types in a Decode As record.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef DECODE_AS_DELEGATE_H
+#define DECODE_AS_DELEGATE_H
+
+#include <config.h>
+#include <glib.h>
+
+#include "cfile.h"
+
+#include <QStyledItemDelegate>
+#include <QSet>
+#include <QList>
+#include <ui/qt/models/decode_as_model.h>
+
+typedef struct _packet_proto_data_t {
+ const gchar* proto_name;
+ const gchar* table_ui_name;
+ guint8 curr_layer_num;
+} packet_proto_data_t;
+
+class DecodeAsDelegate : public QStyledItemDelegate
+{
+public:
+ DecodeAsDelegate(QObject *parent = 0, capture_file *cf = NULL);
+
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const override;
+ void destroyEditor(QWidget *editor, const QModelIndex &index) const override;
+ void setEditorData(QWidget *editor, const QModelIndex &index) const override;
+ void setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const override;
+
+#if 0
+ void updateEditorGeometry(QWidget *editor,
+ const QStyleOptionViewItem &option, const QModelIndex &index) const;
+#endif
+
+private:
+ DecodeAsItem *indexToField(const QModelIndex &index) const;
+ void collectDAProtocols(QSet<QString>& all_protocols, QList<QString>& current_list) const;
+ void cachePacketProtocols();
+ bool isSelectorCombo(DecodeAsItem* item) const;
+
+ static void decodeAddProtocol(const gchar *table_name, const gchar *proto_name, gpointer value, gpointer user_data);
+
+ capture_file *cap_file_;
+ QList<packet_proto_data_t> packet_proto_list_;
+};
+#endif // DECODE_AS_DELEGATE_H
diff --git a/ui/qt/models/decode_as_model.cpp b/ui/qt/models/decode_as_model.cpp
new file mode 100644
index 00000000..67e98b75
--- /dev/null
+++ b/ui/qt/models/decode_as_model.cpp
@@ -0,0 +1,849 @@
+/* decode_as_model.cpp
+ * Data model for Decode As records.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <errno.h>
+
+#include "decode_as_model.h"
+#include <epan/to_str.h>
+#include <epan/decode_as.h>
+#include <epan/epan_dissect.h>
+#include <epan/packet.h>
+#include <epan/prefs.h>
+#include <epan/prefs-int.h>
+#include <epan/dissectors/packet-dcerpc.h>
+
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/utils/variant_pointer.h>
+#include <wsutil/file_util.h>
+#include <wsutil/ws_assert.h>
+
+#include <QVector>
+
+static const char *DEFAULT_TABLE = "tcp.port"; // Arbitrary
+static const char *DEFAULT_UI_TABLE = "TCP port"; // Arbitrary
+
+DecodeAsItem::DecodeAsItem(const char* table_name, gconstpointer selector) :
+ tableName_(DEFAULT_TABLE),
+ tableUIName_(DEFAULT_UI_TABLE),
+ selectorUint_(0),
+ selectorString_(""),
+ selectorDCERPC_(NULL),
+ default_dissector_(DECODE_AS_NONE),
+ current_dissector_(DECODE_AS_NONE),
+ dissector_handle_(NULL)
+{
+ if (table_name == nullptr)
+ return;
+
+ init(table_name, selector);
+}
+
+DecodeAsItem::DecodeAsItem(const decode_as_t *entry, gconstpointer selector) :
+ tableName_(DEFAULT_TABLE),
+ tableUIName_(DEFAULT_UI_TABLE),
+ selectorUint_(0),
+ selectorString_(""),
+ selectorDCERPC_(NULL),
+ default_dissector_(DECODE_AS_NONE),
+ current_dissector_(DECODE_AS_NONE),
+ dissector_handle_(NULL)
+{
+ if (entry == nullptr)
+ return;
+
+ init(entry->table_name, selector);
+}
+
+DecodeAsItem::~DecodeAsItem()
+{
+}
+
+void DecodeAsItem::init(const char* table_name, gconstpointer selector)
+{
+ tableName_ = table_name;
+ tableUIName_ = get_dissector_table_ui_name(tableName_);
+
+ dissector_handle_t default_handle = NULL;
+ ftenum_t selector_type = get_dissector_table_selector_type(tableName_);
+ if (FT_IS_STRING(selector_type)) {
+ if (selector != NULL) {
+ default_handle = dissector_get_default_string_handle(tableName_, (const gchar*)selector);
+ selectorString_ = QString((const char*)selector);
+ }
+ } else if (FT_IS_UINT(selector_type)) {
+ if (selector != NULL) {
+ selectorUint_ = GPOINTER_TO_UINT(selector);
+ default_handle = dissector_get_default_uint_handle(tableName_, selectorUint_);
+ }
+ } else if (selector_type == FT_NONE) {
+ // There is no default for an FT_NONE dissector table
+ } else if (selector_type == FT_GUID) {
+ /* Special handling for DCE/RPC dissectors */
+ if (strcmp(tableName_, DCERPC_TABLE_NAME) == 0) {
+ selectorDCERPC_ = (decode_dcerpc_bind_values_t*)(selector);
+ }
+ }
+
+ if (default_handle != NULL) {
+ default_dissector_ = dissector_handle_get_description(default_handle);
+ // When adding a new record, we set the "current" values equal to
+ // the default, so the user can easily reset the value.
+ // The existing value read from the prefs file should already
+ // be added to the table from reading the prefs file.
+ // When reading existing values the current dissector should be
+ // set explicitly to the actual current value.
+ current_dissector_ = default_dissector_;
+ dissector_handle_ = default_handle;
+ }
+}
+
+void DecodeAsItem::setTable(const decode_as_t *entry)
+{
+ if (entry == nullptr)
+ return;
+
+ tableName_ = entry->table_name;
+ tableUIName_ = get_dissector_table_ui_name(entry->table_name);
+
+ /* XXX: Should the selector values be reset (e.g., to 0 and "")
+ * What if someone tries to change the table to the DCERPC table?
+ * That doesn't really work without the DCERPC special handling.
+ */
+
+ updateHandles();
+}
+
+void DecodeAsItem::setSelector(const QString &value)
+{
+ ftenum_t selector_type = get_dissector_table_selector_type(tableName_);
+
+ if (FT_IS_STRING(selector_type)) {
+ selectorString_ = value;
+ } else if (FT_IS_UINT(selector_type)) {
+ selectorUint_ = value.toUInt(Q_NULLPTR, 0);
+ }
+
+ updateHandles();
+}
+
+void DecodeAsItem::setDissectorHandle(dissector_handle_t handle)
+{
+ dissector_handle_ = handle;
+ if (handle == nullptr) {
+ current_dissector_ = DECODE_AS_NONE;
+ } else {
+ current_dissector_ = dissector_handle_get_description(handle);
+ }
+}
+
+void DecodeAsItem::updateHandles()
+{
+ ftenum_t selector_type = get_dissector_table_selector_type(tableName_);
+ dissector_handle_t default_handle = nullptr;
+
+ if (FT_IS_STRING(selector_type)) {
+ default_handle = dissector_get_default_string_handle(tableName_, qUtf8Printable(selectorString_));
+ } else if (FT_IS_UINT(selector_type)) {
+ default_handle = dissector_get_default_uint_handle(tableName_, selectorUint_);
+ }
+ if (default_handle != nullptr) {
+ default_dissector_ = dissector_handle_get_description(default_handle);
+ } else {
+ default_dissector_ = DECODE_AS_NONE;
+ }
+}
+
+DecodeAsModel::DecodeAsModel(QObject *parent, capture_file *cf) :
+ QAbstractTableModel(parent),
+ cap_file_(cf)
+{
+}
+
+DecodeAsModel::~DecodeAsModel()
+{
+ foreach(DecodeAsItem* item, decode_as_items_)
+ delete item;
+ decode_as_items_.clear();
+}
+
+Qt::ItemFlags DecodeAsModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::ItemFlags();
+
+ DecodeAsItem* item = decode_as_items_[index.row()];
+
+ Qt::ItemFlags flags = QAbstractTableModel::flags(index);
+ switch(index.column())
+ {
+ case DecodeAsModel::colTable:
+ case DecodeAsModel::colProtocol:
+ flags |= Qt::ItemIsEditable;
+ break;
+ case DecodeAsModel::colSelector:
+ {
+ ftenum_t selector_type = get_dissector_table_selector_type(item->tableName());
+ if ((selector_type != FT_NONE) &&
+ (item->selectorDCERPC() == NULL))
+ flags |= Qt::ItemIsEditable;
+ break;
+ }
+ }
+
+ return flags;
+}
+
+QVariant DecodeAsModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid()) {
+ return QVariant();
+ }
+
+ DecodeAsItem* item;
+
+ switch (role)
+ {
+ case Qt::ToolTipRole:
+ switch (index.column())
+ {
+ case colTable:
+ return tr("Match using this field");
+ case colSelector:
+ return tr("Change behavior when the field matches this value");
+ case colType:
+ return tr("Field value type (and base, if Integer)");
+ case colDefault:
+ return tr("Default \"Decode As\" behavior");
+ case colProtocol:
+ return tr("Current\"Decode As\" behavior");
+ }
+ return QVariant();
+ case Qt::DisplayRole:
+ case Qt::EditRole:
+ item = decode_as_items_[index.row()];
+ if (item == NULL)
+ return QVariant();
+
+ switch (index.column())
+ {
+ case colTable:
+ return item->tableUIName();
+ case colSelector:
+ {
+ ftenum_t selector_type = get_dissector_table_selector_type(item->tableName());
+ if (FT_IS_UINT(selector_type)) {
+ return entryString(item->tableName(), GUINT_TO_POINTER(item->selectorUint()));
+ } else if (FT_IS_STRING(selector_type)) {
+ return entryString(item->tableName(), (gconstpointer)item->selectorString().toUtf8().constData());
+ } else if (selector_type == FT_GUID) {
+ if (item->selectorDCERPC() != NULL) {
+ return item->selectorDCERPC()->ctx_id;
+ }
+ }
+
+ return DECODE_AS_NONE;
+ }
+ case colType:
+ {
+ ftenum_t selector_type = get_dissector_table_selector_type(item->tableName());
+
+ if (FT_IS_STRING(selector_type)) {
+ return tr("String");
+ } else if (FT_IS_UINT(selector_type)) {
+ QString type_desc = tr("Integer, base ");
+ switch (get_dissector_table_param(item->tableName())) {
+ case BASE_OCT:
+ type_desc.append("8");
+ break;
+ case BASE_DEC:
+ type_desc.append("10");
+ break;
+ case BASE_HEX:
+ type_desc.append("16");
+ break;
+ default:
+ type_desc.append(tr("unknown"));
+ }
+ return type_desc;
+ } else if (selector_type == FT_NONE) {
+ return tr("<none>");
+ } else if (selector_type == FT_GUID) {
+ if (item->selectorDCERPC() != NULL) {
+ return QString("ctx_id");
+ } else {
+ return tr("GUID");
+ }
+ }
+ break;
+ }
+ case colDefault:
+ return item->defaultDissector();
+ case colProtocol:
+ return item->currentDissector();
+ }
+ return QVariant();
+
+ case Qt::UserRole:
+ item = decode_as_items_[index.row()];
+ return QVariant::fromValue(static_cast<void *>(item));
+ }
+
+ return QVariant();
+}
+
+QVariant DecodeAsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
+ return QVariant();
+
+ switch (section) {
+ case colTable:
+ return tr("Field");
+ case colSelector:
+ return tr("Value");
+ case colType:
+ return tr("Type");
+ case colDefault:
+ return tr("Default");
+ case colProtocol:
+ return tr("Current");
+ default:
+ ws_assert_not_reached();
+ }
+
+ return QVariant();
+}
+
+int DecodeAsModel::rowCount(const QModelIndex &parent) const
+{
+ // there are no children
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return static_cast<int>(decode_as_items_.count());
+}
+
+int DecodeAsModel::columnCount(const QModelIndex &parent) const
+{
+ // there are no children
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return colDecodeAsMax;
+}
+
+bool DecodeAsModel::setData(const QModelIndex &cur_index, const QVariant &value, int role)
+{
+ if (!cur_index.isValid())
+ return false;
+
+ if (role != Qt::EditRole)
+ return false;
+
+ if (data(cur_index, role) == value) {
+ // Data appears unchanged, do not do additional checks.
+ return true;
+ }
+
+ DecodeAsItem* item = decode_as_items_[cur_index.row()];
+
+ switch(cur_index.column())
+ {
+ case DecodeAsModel::colTable:
+ {
+ QString valueStr = value.toString();
+ //grab the table values from the Decode As list because they are persistent
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_t *entry = (decode_as_t *) cur->data;
+ if (valueStr.compare(get_dissector_table_ui_name(entry->table_name)) == 0) {
+ item->setTable(entry);
+ //all other columns affected
+ emit dataChanged(index(cur_index.row(), colSelector),
+ index(cur_index.row(), colProtocol));
+ break;
+ }
+ }
+ }
+ break;
+ case DecodeAsModel::colProtocol:
+ {
+ dissector_handle_t handle = VariantPointer<dissector_handle>::asPtr(value);
+ item->setDissectorHandle(handle);
+ break;
+ }
+ case DecodeAsModel::colSelector:
+ item->setSelector(value.toString());
+ emit dataChanged(index(cur_index.row(), colDefault),
+ index(cur_index.row(), colProtocol));
+ break;
+ }
+
+ return true;
+}
+
+bool DecodeAsModel::insertRows(int row, int count, const QModelIndex &/*parent*/)
+{
+ // support insertion of just one item for now.
+ if (count != 1 || row < 0 || row > rowCount())
+ return false;
+
+ beginInsertRows(QModelIndex(), row, row);
+
+ DecodeAsItem* item = nullptr;
+ const decode_as_t *firstEntry = nullptr;
+
+ if (cap_file_ && cap_file_->edt) {
+ // Populate the new Decode As item with the last protocol layer
+ // that can support Decode As and has a selector field for that
+ // present in the frame.
+ //
+ // XXX: This treats 0 (for UInts) and empty strings the same as
+ // the fields for the tables not being present at all.
+
+ wmem_list_frame_t * protos = wmem_list_tail(cap_file_->edt->pi.layers);
+ gint8 curr_layer_num_saved = cap_file_->edt->pi.curr_layer_num;
+ guint8 curr_layer_num = wmem_list_count(cap_file_->edt->pi.layers);
+
+ while (protos != NULL && item == nullptr) {
+ int proto_id = GPOINTER_TO_INT(wmem_list_frame_data(protos));
+ const gchar * proto_name = proto_get_protocol_filter_name(proto_id);
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_t *entry = (decode_as_t *) cur->data;
+ if (g_strcmp0(proto_name, entry->name) == 0) {
+ if (firstEntry == nullptr) {
+ firstEntry = entry;
+ }
+ ftenum_t selector_type = get_dissector_table_selector_type(entry->table_name);
+ // Pick the first value in the packet for the current
+ // layer for the table
+ // XXX: What if the Decode As table supports multiple
+ // values, but the first possible one is 0/NULL?
+ cap_file_->edt->pi.curr_layer_num = curr_layer_num;
+ gpointer selector = entry->values[0].build_values[0](&cap_file_->edt->pi);
+ // FT_NONE tables don't need a value
+ if (selector != NULL || selector_type == FT_NONE) {
+ item = new DecodeAsItem(entry, selector);
+ break;
+ }
+
+ }
+ }
+ protos = wmem_list_frame_prev(protos);
+ curr_layer_num--;
+ }
+
+ cap_file_->edt->pi.curr_layer_num = curr_layer_num_saved;
+ }
+
+ // If we didn't find an entry with a valid selector, create an entry
+ // from the last table with an empty selector, or an empty entry.
+ if (item == nullptr) {
+ item = new DecodeAsItem(firstEntry);
+ }
+ decode_as_items_ << item;
+
+ endInsertRows();
+
+ return true;
+}
+
+bool DecodeAsModel::removeRows(int row, int count, const QModelIndex &/*parent*/)
+{
+ if (count != 1 || row < 0 || row >= rowCount())
+ return false;
+
+ beginRemoveRows(QModelIndex(), row, row);
+ DecodeAsItem* item = decode_as_items_.takeAt(row);
+ delete item;
+ endRemoveRows();
+
+ return true;
+}
+
+void DecodeAsModel::clearAll()
+{
+ if (rowCount() < 1)
+ return;
+
+ beginResetModel();
+ foreach(DecodeAsItem* item, decode_as_items_)
+ delete item;
+ decode_as_items_.clear();
+ endResetModel();
+}
+
+bool DecodeAsModel::copyRow(int dst_row, int src_row)
+{
+ if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) {
+ return false;
+ }
+
+ DecodeAsItem* src = decode_as_items_[src_row];
+ DecodeAsItem* dst = decode_as_items_[dst_row];
+
+ *dst = *src;
+
+ QVector<int> roles;
+ roles << Qt::EditRole << Qt::BackgroundRole;
+ emit dataChanged(index(dst_row, 0), index(dst_row, columnCount()), roles);
+
+ return true;
+}
+
+prefs_set_pref_e DecodeAsModel::readDecodeAsEntry(gchar *key, const gchar *value, void *private_data, gboolean)
+{
+ DecodeAsModel *model = (DecodeAsModel*)private_data;
+ if (model == NULL)
+ return PREFS_SET_OK;
+
+ if (strcmp(key, DECODE_AS_ENTRY) != 0) {
+ return PREFS_SET_NO_SUCH_PREF;
+ }
+
+ /* Parse into table, selector, initial, current */
+ gchar **values = g_strsplit_set(value, ",", 4);
+ DecodeAsItem *item = nullptr;
+
+ dissector_table_t dissector_table = find_dissector_table(values[0]);
+
+ QString tableName(values[0]);
+ // Get the table values from the Decode As list because they are persistent
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_t *entry = (decode_as_t *) cur->data;
+ if (tableName.compare(entry->table_name) == 0) {
+ item = new DecodeAsItem(entry);
+ break;
+ }
+ }
+
+ if (item == nullptr) {
+ g_strfreev(values);
+ return PREFS_SET_SYNTAX_ERR;
+ }
+
+ QString selector(values[1]);
+ item->setSelector(selector);
+
+ /* The value for the default dissector in the decode_as_entries file
+ * has no effect other than perhaps making the config file more
+ * informative when edited manually.
+ * We will actually display and reset to the programmatic default value.
+ */
+ item->setDissectorHandle(dissector_table_get_dissector_handle(dissector_table, values[3]));
+
+ model->decode_as_items_ << item;
+ g_strfreev(values);
+
+ return PREFS_SET_OK;
+}
+
+bool DecodeAsModel::copyFromProfile(QString filename, const gchar **err)
+{
+ FILE *fp = ws_fopen(filename.toUtf8().constData(), "r");
+
+ if (fp == NULL) {
+ *err = g_strerror(errno);
+ return false;
+ }
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ read_prefs_file(filename.toUtf8().constData(), fp, readDecodeAsEntry, this);
+ endInsertRows();
+
+ fclose(fp);
+
+ return true;
+}
+
+QString DecodeAsModel::entryString(const gchar *table_name, gconstpointer value)
+{
+ QString entry_str;
+ ftenum_t selector_type = get_dissector_table_selector_type(table_name);
+
+ switch (selector_type) {
+
+ case FT_UINT8:
+ case FT_UINT16:
+ case FT_UINT24:
+ case FT_UINT32:
+ {
+ uint num_val = GPOINTER_TO_UINT(value);
+ switch (get_dissector_table_param(table_name)) {
+
+ case BASE_DEC:
+ entry_str = QString::number(num_val);
+ break;
+
+ case BASE_HEX:
+ int width;
+ switch (selector_type) {
+ case FT_UINT8:
+ width = 2;
+ break;
+ case FT_UINT16:
+ width = 4;
+ break;
+ case FT_UINT24:
+ width = 6;
+ break;
+ case FT_UINT32:
+ width = 8;
+ break;
+
+ default:
+ ws_assert_not_reached();
+ break;
+ }
+ entry_str = QString("%1").arg(int_to_qstring(num_val, width, 16));
+ break;
+
+ case BASE_OCT:
+ entry_str = "0" + QString::number(num_val, 8);
+ break;
+ }
+ break;
+ }
+
+ case FT_STRING:
+ case FT_STRINGZ:
+ case FT_UINT_STRING:
+ case FT_STRINGZPAD:
+ case FT_STRINGZTRUNC:
+ entry_str = (const char *)value;
+ break;
+
+ case FT_GUID:
+ //avoid the assert for now
+ break;
+
+ case FT_NONE:
+ //doesn't really matter, just avoiding the assert
+ return "0";
+
+ default:
+ ws_assert_not_reached();
+ break;
+ }
+ return entry_str;
+}
+
+void DecodeAsModel::fillTable()
+{
+ decode_as_items_.clear();
+ beginResetModel();
+
+ dissector_all_tables_foreach_changed(buildChangedList, this);
+ decode_dcerpc_add_show_list(buildDceRpcChangedList, this);
+
+ endResetModel();
+}
+
+void DecodeAsModel::setDissectorHandle(const QModelIndex &index, dissector_handle_t dissector_handle)
+{
+ DecodeAsItem* item = decode_as_items_[index.row()];
+ if (item != NULL)
+ item->setDissectorHandle(dissector_handle);
+}
+
+void DecodeAsModel::buildChangedList(const gchar *table_name, ftenum_t, gpointer key, gpointer value, gpointer user_data)
+{
+ DecodeAsModel *model = (DecodeAsModel*)user_data;
+ if (model == NULL)
+ return;
+
+ dissector_handle_t current_dh;
+ DecodeAsItem* item = new DecodeAsItem(table_name, key);
+
+ current_dh = dtbl_entry_get_handle((dtbl_entry_t *)value);
+ item->setDissectorHandle(current_dh);
+
+ model->decode_as_items_ << item;
+}
+
+void DecodeAsModel::buildDceRpcChangedList(gpointer data, gpointer user_data)
+{
+ dissector_table_t sub_dissectors;
+ guid_key guid_val;
+ decode_dcerpc_bind_values_t *binding = (decode_dcerpc_bind_values_t *)data;
+
+ DecodeAsModel *model = (DecodeAsModel*)user_data;
+ if (model == NULL)
+ return;
+
+ DecodeAsItem* item = new DecodeAsItem(DCERPC_TABLE_NAME, binding);
+
+ sub_dissectors = find_dissector_table(DCERPC_TABLE_NAME);
+
+ guid_val.ver = binding->ver;
+ guid_val.guid = binding->uuid;
+ item->setDissectorHandle(dissector_get_guid_handle(sub_dissectors, &guid_val));
+
+ model->decode_as_items_ << item;
+}
+
+typedef QPair<const char *, guint32> UintPair;
+typedef QPair<const char *, const char *> CharPtrPair;
+
+void DecodeAsModel::gatherChangedEntries(const gchar *table_name,
+ ftenum_t selector_type, gpointer key, gpointer, gpointer user_data)
+{
+ DecodeAsModel *model = qobject_cast<DecodeAsModel*>((DecodeAsModel*)user_data);
+ if (model == NULL)
+ return;
+
+ switch (selector_type) {
+ case FT_UINT8:
+ case FT_UINT16:
+ case FT_UINT24:
+ case FT_UINT32:
+ model->changed_uint_entries_ << UintPair(table_name, GPOINTER_TO_UINT(key));
+ break;
+ case FT_NONE:
+ //need to reset dissector table, so this needs to be in a changed list,
+ //might as well be the uint one.
+ model->changed_uint_entries_ << UintPair(table_name, 0);
+ break;
+
+ case FT_STRING:
+ case FT_STRINGZ:
+ case FT_UINT_STRING:
+ case FT_STRINGZPAD:
+ case FT_STRINGZTRUNC:
+ model->changed_string_entries_ << CharPtrPair(table_name, (const char *) key);
+ break;
+ default:
+ break;
+ }
+}
+
+void DecodeAsModel::applyChanges()
+{
+ dissector_table_t sub_dissectors;
+ module_t *module;
+ pref_t* pref_value;
+ dissector_handle_t handle;
+ // Reset all dissector tables, then apply all rules from model.
+
+ // We can't call g_hash_table_removed from g_hash_table_foreach, which
+ // means we can't call dissector_reset_{string,uint} from
+ // dissector_all_tables_foreach_changed. Collect changed entries in
+ // lists and remove them separately.
+ //
+ // If dissector_all_tables_remove_changed existed we could call it
+ // instead.
+ dissector_all_tables_foreach_changed(gatherChangedEntries, this);
+ foreach (UintPair uint_entry, changed_uint_entries_) {
+ /* Set "Decode As preferences" to default values */
+ sub_dissectors = find_dissector_table(uint_entry.first);
+ handle = dissector_get_uint_handle(sub_dissectors, uint_entry.second);
+ if (handle != NULL) {
+ module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(handle)));
+ pref_value = prefs_find_preference(module, uint_entry.first);
+ if (pref_value != NULL) {
+ module->prefs_changed_flags |= prefs_get_effect_flags(pref_value);
+ reset_pref(pref_value);
+ }
+ }
+
+ dissector_reset_uint(uint_entry.first, uint_entry.second);
+ }
+ changed_uint_entries_.clear();
+ foreach (CharPtrPair char_ptr_entry, changed_string_entries_) {
+ dissector_reset_string(char_ptr_entry.first, char_ptr_entry.second);
+ }
+ changed_string_entries_.clear();
+
+ foreach(DecodeAsItem *item, decode_as_items_) {
+ decode_as_t *decode_as_entry;
+
+ if (item->currentDissector().isEmpty()) {
+ continue;
+ }
+
+ for (GList *cur = decode_as_list; cur; cur = cur->next) {
+ decode_as_entry = (decode_as_t *) cur->data;
+
+ if (!g_strcmp0(decode_as_entry->table_name, item->tableName())) {
+
+ ftenum_t selector_type = get_dissector_table_selector_type(item->tableName());
+ gconstpointer selector_value;
+ QByteArray byteArray;
+
+ switch (selector_type) {
+ case FT_UINT8:
+ case FT_UINT16:
+ case FT_UINT24:
+ case FT_UINT32:
+ selector_value = GUINT_TO_POINTER(item->selectorUint());
+ break;
+ case FT_STRING:
+ case FT_STRINGZ:
+ case FT_UINT_STRING:
+ case FT_STRINGZPAD:
+ case FT_STRINGZTRUNC:
+ byteArray = item->selectorString().toUtf8();
+ selector_value = (gconstpointer) byteArray.constData();
+ break;
+ case FT_NONE:
+ //selector value is ignored, but dissector table needs to happen
+ selector_value = NULL;
+ break;
+ case FT_GUID:
+ if (item->selectorDCERPC() != NULL) {
+ selector_value = (gconstpointer)item->selectorDCERPC();
+ } else {
+ //TODO: Support normal GUID dissector tables
+ selector_value = NULL;
+ }
+ break;
+ default:
+ continue;
+ }
+
+ if ((item->currentDissector() == item->defaultDissector())) {
+ decode_as_entry->reset_value(decode_as_entry->table_name, selector_value);
+ sub_dissectors = find_dissector_table(decode_as_entry->table_name);
+
+ /* For now, only numeric dissector tables can use preferences */
+ if (FT_IS_UINT(dissector_table_get_type(sub_dissectors))) {
+ if (item->dissectorHandle() != NULL) {
+ module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissectorHandle())));
+ pref_value = prefs_find_preference(module, decode_as_entry->table_name);
+ if (pref_value != NULL) {
+ module->prefs_changed_flags |= prefs_get_effect_flags(pref_value);
+ prefs_remove_decode_as_value(pref_value, item->selectorUint(), TRUE);
+ }
+ }
+ }
+ break;
+ } else {
+ decode_as_entry->change_value(decode_as_entry->table_name, selector_value, item->dissectorHandle(), item->currentDissector().toUtf8().constData());
+ sub_dissectors = find_dissector_table(decode_as_entry->table_name);
+
+ /* For now, only numeric dissector tables can use preferences */
+ if (item->dissectorHandle() != NULL) {
+ if (FT_IS_UINT(dissector_table_get_type(sub_dissectors))) {
+ module = prefs_find_module(proto_get_protocol_filter_name(dissector_handle_get_protocol_index(item->dissectorHandle())));
+ pref_value = prefs_find_preference(module, decode_as_entry->table_name);
+ if (pref_value != NULL) {
+ module->prefs_changed_flags |= prefs_get_effect_flags(pref_value);
+ prefs_add_decode_as_value(pref_value, item->selectorUint(), FALSE);
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ }
+ prefs_apply_all();
+}
diff --git a/ui/qt/models/decode_as_model.h b/ui/qt/models/decode_as_model.h
new file mode 100644
index 00000000..cf280965
--- /dev/null
+++ b/ui/qt/models/decode_as_model.h
@@ -0,0 +1,120 @@
+/** @file
+ *
+ * Data model for Decode As records.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef DECODE_AS_MODEL_H
+#define DECODE_AS_MODEL_H
+
+#include <config.h>
+#include <glib.h>
+
+#include <QAbstractItemModel>
+#include <QList>
+
+#include "cfile.h"
+
+#include <epan/packet.h>
+#include <epan/decode_as.h>
+#include <epan/dissectors/packet-dcerpc.h>
+
+class DecodeAsItem
+{
+public:
+ DecodeAsItem(const char *table_name = NULL, gconstpointer selector = NULL);
+ DecodeAsItem(const decode_as_t *entry, gconstpointer selector = NULL);
+ virtual ~DecodeAsItem();
+
+ const gchar* tableName() const { return tableName_; }
+ const gchar* tableUIName() const { return tableUIName_; }
+ uint selectorUint() const { return selectorUint_; }
+ QString selectorString() const { return selectorString_; }
+ decode_dcerpc_bind_values_t* selectorDCERPC() const { return selectorDCERPC_; }
+ QString defaultDissector() const { return default_dissector_; }
+ QString currentDissector() const { return current_dissector_; }
+ dissector_handle_t dissectorHandle() const { return dissector_handle_; }
+ void setTable(const decode_as_t *entry);
+ void setSelector(const QString &value);
+ void setDissectorHandle(dissector_handle_t handle);
+
+ void updateHandles();
+
+private:
+ void init(const char *table_name, gconstpointer selector = NULL);
+
+ const gchar* tableName_;
+ const gchar* tableUIName_;
+
+ //save our sanity and not have to worry about memory management
+ //between (lack of) persistent data in GUI and underlying data
+ uint selectorUint_;
+ QString selectorString_;
+ decode_dcerpc_bind_values_t* selectorDCERPC_; //for special handling of DCE/RPC
+
+ QString default_dissector_;
+ QString current_dissector_;
+ dissector_handle_t dissector_handle_;
+};
+
+class DecodeAsModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ DecodeAsModel(QObject *parent, capture_file *cf = NULL);
+ virtual ~DecodeAsModel();
+
+ enum DecodeAsColumn {
+ colTable = 0, // aka "Field" (or dissector table like "TCP Port")
+ colSelector, // the actual table value (e.g., port number 80)
+ colType, // field type (e.g. "Integer, base 16")
+ colDefault, // aka "initial" protocol chosen by Wireshark
+ colProtocol, // aka "current" protocol selected by user
+ colDecodeAsMax //not used
+ };
+
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ QVariant data(const QModelIndex &index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+ void fillTable();
+
+ void setDissectorHandle(const QModelIndex &index, dissector_handle_t dissector_handle);
+
+ bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ void clearAll();
+ bool copyRow(int dst_row, int src_row);
+ bool copyFromProfile(QString filename, const gchar **err);
+
+ static QString entryString(const gchar *table_name, gconstpointer value);
+
+ void applyChanges();
+
+protected:
+ static void buildChangedList(const gchar *table_name, ftenum_t selector_type,
+ gpointer key, gpointer value, gpointer user_data);
+ static void buildDceRpcChangedList(gpointer data, gpointer user_data);
+ static void gatherChangedEntries(const gchar *table_name, ftenum_t selector_type,
+ gpointer key, gpointer value, gpointer user_data);
+ static prefs_set_pref_e readDecodeAsEntry(gchar *key, const gchar *value,
+ void *user_data, gboolean return_range_errors);
+
+private:
+ capture_file *cap_file_;
+ QList<DecodeAsItem *> decode_as_items_;
+ QList<QPair<const char *, guint32> > changed_uint_entries_;
+ QList<QPair<const char *, const char *> > changed_string_entries_;
+};
+
+#endif // DECODE_AS_MODEL_H
diff --git a/ui/qt/models/dissector_tables_model.cpp b/ui/qt/models/dissector_tables_model.cpp
new file mode 100644
index 00000000..73c757ec
--- /dev/null
+++ b/ui/qt/models/dissector_tables_model.cpp
@@ -0,0 +1,434 @@
+/* dissector_tables_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/dissector_tables_model.h>
+#include <epan/ftypes/ftypes.h>
+#include <epan/packet.h>
+
+#include <ui/qt/utils/variant_pointer.h>
+#include "main_application.h"
+
+static const char* CUSTOM_TABLE_NAME = "Custom Tables";
+static const char* INTEGER_TABLE_NAME = "Integer Tables";
+static const char* STRING_TABLE_NAME = "String Tables";
+static const char* HEURISTIC_TABLE_NAME = "Heuristic Tables";
+
+class IntegerTablesItem : public DissectorTablesItem
+{
+public:
+ IntegerTablesItem(unsigned int value, QString dissectorDescription, DissectorTablesItem* parent);
+ virtual ~IntegerTablesItem();
+
+ virtual bool lessThan(DissectorTablesItem &right) const;
+
+protected:
+ unsigned int value_;
+};
+
+
+DissectorTablesItem::DissectorTablesItem(QString tableName, QString dissectorDescription, DissectorTablesItem* parent) :
+ ModelHelperTreeItem<DissectorTablesItem>(parent),
+ tableName_(tableName),
+ dissectorDescription_(dissectorDescription)
+{
+}
+
+DissectorTablesItem::~DissectorTablesItem()
+{
+}
+
+bool DissectorTablesItem::lessThan(DissectorTablesItem &right) const
+{
+ if (tableName().compare(right.tableName(), Qt::CaseInsensitive) < 0)
+ return true;
+
+ return false;
+}
+
+
+IntegerTablesItem::IntegerTablesItem(unsigned int value, QString dissectorDescription, DissectorTablesItem* parent)
+ : DissectorTablesItem(QString("%1").arg(value), dissectorDescription, parent)
+ , value_(value)
+{
+}
+
+IntegerTablesItem::~IntegerTablesItem()
+{
+}
+
+bool IntegerTablesItem::lessThan(DissectorTablesItem &right) const
+{
+ if (value_ == ((IntegerTablesItem&)right).value_) {
+ return DissectorTablesItem::lessThan(right);
+ }
+
+ if (value_ < ((IntegerTablesItem&)right).value_) {
+ return true;
+ }
+
+ return false;
+}
+
+
+
+
+
+
+
+
+DissectorTablesModel::DissectorTablesModel(QObject *parent) :
+ QAbstractItemModel(parent),
+ root_(new DissectorTablesItem(QString("ROOT"), QString("ROOT"), NULL))
+{
+ populate();
+}
+
+DissectorTablesModel::~DissectorTablesModel()
+{
+ delete root_;
+}
+
+int DissectorTablesModel::rowCount(const QModelIndex &parent) const
+{
+ DissectorTablesItem *parent_item;
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<DissectorTablesItem*>(parent.internalPointer());
+
+ if (parent_item == NULL)
+ return 0;
+
+ return parent_item->childCount();
+}
+
+int DissectorTablesModel::columnCount(const QModelIndex&) const
+{
+ return colLast;
+}
+
+QModelIndex DissectorTablesModel::parent(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ DissectorTablesItem* item = static_cast<DissectorTablesItem*>(index.internalPointer());
+ if (item != NULL) {
+ DissectorTablesItem* parent_item = item->parentItem();
+ if (parent_item != NULL) {
+ if (parent_item == root_)
+ return QModelIndex();
+
+ return createIndex(parent_item->row(), 0, parent_item);
+ }
+ }
+
+ return QModelIndex();
+}
+
+QModelIndex DissectorTablesModel::index(int row, int column, const QModelIndex& parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+
+ DissectorTablesItem *parent_item, *child_item;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<DissectorTablesItem*>(parent.internalPointer());
+
+ Q_ASSERT(parent_item);
+
+ child_item = parent_item->child(row);
+ if (child_item) {
+ return createIndex(row, column, child_item);
+ }
+
+ return QModelIndex();
+}
+
+QVariant DissectorTablesModel::data(const QModelIndex &index, int role) const
+{
+ if ((!index.isValid()) || (role != Qt::DisplayRole))
+ return QVariant();
+
+ DissectorTablesItem* item = static_cast<DissectorTablesItem*>(index.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ switch ((enum DissectorTablesColumn)index.column())
+ {
+ case colTableName:
+ return item->tableName();
+ case colDissectorDescription:
+ return item->dissectorDescription();
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+static void gatherProtocolDecodes(const char *, ftenum_t selector_type, gpointer key, gpointer value, gpointer item_ptr)
+{
+ DissectorTablesItem* pdl_ptr = (DissectorTablesItem*)item_ptr;
+ if (pdl_ptr == NULL)
+ return;
+
+ dtbl_entry_t *dtbl_entry = (dtbl_entry_t*)value;
+ dissector_handle_t handle = dtbl_entry_get_handle(dtbl_entry);
+ const QString dissector_description = dissector_handle_get_description(handle);
+ DissectorTablesItem *ti = NULL;
+
+ switch (selector_type) {
+ case FT_UINT8:
+ case FT_UINT16:
+ case FT_UINT24:
+ case FT_UINT32:
+ ti = new IntegerTablesItem(GPOINTER_TO_UINT(key), dissector_description, pdl_ptr);
+ pdl_ptr->prependChild(ti);
+ break;
+
+ case FT_STRING:
+ case FT_STRINGZ:
+ case FT_UINT_STRING:
+ case FT_STRINGZPAD:
+ case FT_STRINGZTRUNC:
+ ti = new DissectorTablesItem((const char *)key, dissector_description, pdl_ptr);
+ pdl_ptr->prependChild(ti);
+ break;
+
+ case FT_BYTES:
+ ti = new DissectorTablesItem(dissector_handle_get_description(handle), dissector_description, pdl_ptr);
+ pdl_ptr->prependChild(ti);
+ break;
+
+ default:
+ break;
+ }
+}
+
+struct tables_root
+{
+ DissectorTablesItem* custom_table;
+ DissectorTablesItem* integer_table;
+ DissectorTablesItem* string_table;
+};
+
+static void gatherTableNames(const char *short_name, const char *table_name, gpointer model_ptr)
+{
+ struct tables_root* tables = (struct tables_root*)model_ptr;
+ if (model_ptr == NULL)
+ return;
+
+ ftenum_t selector_type = get_dissector_table_selector_type(short_name);
+ DissectorTablesItem *dt_ti = NULL;
+
+ switch (selector_type) {
+ case FT_UINT8:
+ case FT_UINT16:
+ case FT_UINT24:
+ case FT_UINT32:
+ dt_ti = new DissectorTablesItem(table_name, short_name, tables->integer_table);
+ tables->integer_table->prependChild(dt_ti);
+ break;
+ case FT_STRING:
+ case FT_STRINGZ:
+ case FT_UINT_STRING:
+ case FT_STRINGZPAD:
+ case FT_STRINGZTRUNC:
+ dt_ti = new DissectorTablesItem(table_name, short_name, tables->string_table);
+ tables->string_table->prependChild(dt_ti);
+ break;
+ case FT_BYTES:
+ dt_ti = new DissectorTablesItem(table_name, short_name, tables->custom_table);
+ tables->custom_table->prependChild(dt_ti);
+ break;
+ default:
+ // Assert?
+ return;
+ }
+
+ dissector_table_foreach(short_name, gatherProtocolDecodes, dt_ti);
+}
+
+static void gatherHeurProtocolDecodes(const char *, struct heur_dtbl_entry *dtbl_entry, gpointer list_ptr)
+{
+ DissectorTablesItem* hdl_ptr = (DissectorTablesItem*)list_ptr;
+ if (hdl_ptr == NULL)
+ return;
+
+ if (dtbl_entry->protocol) {
+ QString longName = proto_get_protocol_long_name(dtbl_entry->protocol);
+ QString heurDisplayName = dtbl_entry->display_name;
+ if (! heurDisplayName.isEmpty())
+ longName.append(QString(" (%1)").arg(heurDisplayName));
+
+ DissectorTablesItem *heur = new DissectorTablesItem(longName, proto_get_protocol_short_name(dtbl_entry->protocol), hdl_ptr);
+ hdl_ptr->prependChild(heur);
+ }
+}
+
+static void gatherHeurTableNames(const char *table_name, heur_dissector_list *list, gpointer heur_tables)
+{
+ DissectorTablesItem* table = (DissectorTablesItem*)heur_tables;
+ if (table == NULL)
+ return;
+
+ DissectorTablesItem *heur = new DissectorTablesItem(table_name, QString(""), table);
+ table->prependChild(heur);
+
+ if (list) {
+ heur_dissector_table_foreach(table_name, gatherHeurProtocolDecodes, heur);
+ }
+}
+
+void DissectorTablesModel::populate()
+{
+ beginResetModel();
+
+ struct tables_root tables;
+
+ tables.custom_table = new DissectorTablesItem(tr(CUSTOM_TABLE_NAME), QString(""), root_);
+ root_->prependChild(tables.custom_table);
+ tables.integer_table = new DissectorTablesItem(tr(INTEGER_TABLE_NAME), QString(""), root_);
+ root_->prependChild(tables.integer_table);
+ tables.string_table = new DissectorTablesItem(tr(STRING_TABLE_NAME), QString(""), root_);
+ root_->prependChild(tables.string_table);
+
+ dissector_all_tables_foreach_table(gatherTableNames, &tables, NULL);
+
+ DissectorTablesItem* heuristic_table = new DissectorTablesItem(tr(HEURISTIC_TABLE_NAME), QString(""), root_);
+ root_->prependChild(heuristic_table);
+
+ dissector_all_heur_tables_foreach_table(gatherHeurTableNames, heuristic_table, NULL);
+
+ endResetModel();
+}
+
+
+
+
+
+DissectorTablesProxyModel::DissectorTablesProxyModel(QObject * parent)
+: QSortFilterProxyModel(parent),
+tableName_(tr("Table Type")),
+dissectorDescription_(),
+filter_()
+{
+}
+
+QVariant DissectorTablesProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+
+ switch ((enum DissectorTablesModel::DissectorTablesColumn)section) {
+ case DissectorTablesModel::colTableName:
+ return tableName_;
+ case DissectorTablesModel::colDissectorDescription:
+ return dissectorDescription_;
+ default:
+ break;
+ }
+ }
+ return QVariant();
+}
+
+bool DissectorTablesProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ //Use DissectorTablesItem directly for better performance
+ DissectorTablesItem* left_item = static_cast<DissectorTablesItem*>(left.internalPointer());
+ DissectorTablesItem* right_item = static_cast<DissectorTablesItem*>(right.internalPointer());
+
+ if ((left_item != NULL) && (right_item != NULL)) {
+ return left_item->lessThan(*right_item);
+ }
+
+ return false;
+}
+
+bool DissectorTablesProxyModel::filterAcceptItem(DissectorTablesItem& item) const
+{
+ if (filter_.isEmpty())
+ return true;
+
+ if (item.tableName().contains(filter_, Qt::CaseInsensitive) || item.dissectorDescription().contains(filter_, Qt::CaseInsensitive))
+ return true;
+
+ DissectorTablesItem *child_item;
+ for (int child_row = 0; child_row < item.childCount(); child_row++)
+ {
+ child_item = item.child(child_row);
+ if ((child_item != NULL) && (filterAcceptItem(*child_item)))
+ return true;
+ }
+
+ return false;
+}
+
+bool DissectorTablesProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex nameIdx = sourceModel()->index(sourceRow, DissectorTablesModel::colTableName, sourceParent);
+ DissectorTablesItem* item = static_cast<DissectorTablesItem*>(nameIdx.internalPointer());
+ if (item == NULL)
+ return false;
+
+ if (filterAcceptItem(*item))
+ return true;
+
+ return false;
+}
+
+void DissectorTablesProxyModel::setFilter(const QString& filter)
+{
+ filter_ = filter;
+ invalidateFilter();
+}
+
+void DissectorTablesProxyModel::adjustHeader(const QModelIndex &currentIndex)
+{
+ tableName_ = tr("Table Type");
+ dissectorDescription_ = QString();
+ if (currentIndex.isValid() && currentIndex.parent().isValid()) {
+ QString table;
+
+ if (currentIndex.parent().parent().isValid()) {
+ table = data(index(currentIndex.parent().parent().row(), DissectorTablesModel::colTableName), Qt::DisplayRole).toString();
+ if ((table.compare(CUSTOM_TABLE_NAME) == 0) ||
+ (table.compare(STRING_TABLE_NAME) == 0)) {
+ tableName_ = tr("String");
+ dissectorDescription_ = tr("Dissector Description");
+ } else if (table.compare(INTEGER_TABLE_NAME) == 0) {
+ tableName_ = tr("Integer");
+ dissectorDescription_ = tr("Dissector Description");
+ } else if (table.compare(HEURISTIC_TABLE_NAME) == 0) {
+ tableName_ = tr("Protocol");
+ dissectorDescription_ = tr("Short Name");
+ }
+ } else {
+ table = data(index(currentIndex.parent().row(), DissectorTablesModel::colTableName), Qt::DisplayRole).toString();
+ if ((table.compare(CUSTOM_TABLE_NAME) == 0) ||
+ (table.compare(INTEGER_TABLE_NAME) == 0) ||
+ (table.compare(STRING_TABLE_NAME) == 0)) {
+ tableName_ = tr("Table Name");
+ dissectorDescription_ = tr("Selector Name");
+ } else if (table.compare(HEURISTIC_TABLE_NAME) == 0) {
+ tableName_ = tr("Protocol");
+ dissectorDescription_ = tr("Short Name");
+ }
+ }
+ }
+
+
+ emit headerDataChanged(Qt::Vertical, 0, 1);
+}
diff --git a/ui/qt/models/dissector_tables_model.h b/ui/qt/models/dissector_tables_model.h
new file mode 100644
index 00000000..40d63f94
--- /dev/null
+++ b/ui/qt/models/dissector_tables_model.h
@@ -0,0 +1,89 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef DISSECTOR_TABLES_MODEL_H
+#define DISSECTOR_TABLES_MODEL_H
+
+#include <config.h>
+
+#include <ui/qt/models/tree_model_helpers.h>
+
+#include <QSortFilterProxyModel>
+
+class DissectorTablesItem : public ModelHelperTreeItem<DissectorTablesItem>
+{
+public:
+ DissectorTablesItem(QString tableName, QString dissectorDescription, DissectorTablesItem* parent);
+ virtual ~DissectorTablesItem();
+
+ QString tableName() const {return tableName_;}
+ QString dissectorDescription() const {return dissectorDescription_;}
+
+ virtual bool lessThan(DissectorTablesItem &right) const;
+
+protected:
+ QString tableName_;
+ QString dissectorDescription_;
+};
+
+class DissectorTablesModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit DissectorTablesModel(QObject * parent = Q_NULLPTR);
+ virtual ~DissectorTablesModel();
+
+ enum DissectorTablesColumn {
+ colTableName = 0,
+ colDissectorDescription,
+ colLast
+ };
+
+ QModelIndex index(int row, int column,
+ const QModelIndex & = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &) const;
+ QVariant data(const QModelIndex &index, int role) const;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ void populate();
+
+private:
+ DissectorTablesItem* root_;
+};
+
+class DissectorTablesProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit DissectorTablesProxyModel(QObject * parent = Q_NULLPTR);
+
+ virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+
+ void adjustHeader(const QModelIndex &currentIndex);
+ void setFilter(const QString& filter);
+
+protected:
+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
+ bool filterAcceptItem(DissectorTablesItem& item) const;
+
+private:
+
+ QString tableName_;
+ QString dissectorDescription_;
+ QString filter_;
+};
+
+#endif // DISSECTOR_TABLES_MODEL_H
diff --git a/ui/qt/models/enabled_protocols_model.cpp b/ui/qt/models/enabled_protocols_model.cpp
new file mode 100644
index 00000000..ecbc47a9
--- /dev/null
+++ b/ui/qt/models/enabled_protocols_model.cpp
@@ -0,0 +1,520 @@
+/* enabled_protocols_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <QSortFilterProxyModel>
+
+#include <ui/qt/models/enabled_protocols_model.h>
+#include <epan/packet.h>
+#include <epan/disabled_protos.h>
+
+#include <ui/qt/utils/variant_pointer.h>
+#include "main_application.h"
+
+#include <QRegularExpression>
+
+class ProtocolTreeItem : public EnabledProtocolItem
+{
+public:
+ ProtocolTreeItem(protocol_t* proto, EnabledProtocolItem* parent)
+ : EnabledProtocolItem(proto_get_protocol_short_name(proto), proto_get_protocol_long_name(proto), proto_is_protocol_enabled(proto), parent),
+ proto_(proto)
+ {
+
+ }
+
+ virtual ~ProtocolTreeItem() {}
+
+protected:
+ virtual void applyValuePrivate(gboolean value)
+ {
+ if (! proto_can_toggle_protocol(proto_get_id(proto_))) {
+ return;
+ }
+ proto_set_decoding(proto_get_id(proto_), value);
+ }
+
+private:
+ protocol_t* proto_;
+};
+
+class HeuristicTreeItem : public EnabledProtocolItem
+{
+public:
+ HeuristicTreeItem(heur_dtbl_entry_t *heuristic, EnabledProtocolItem* parent)
+ : EnabledProtocolItem(heuristic->short_name, heuristic->display_name, heuristic->enabled, parent),
+ heuristic_table_(heuristic)
+ {
+ type_ = EnabledProtocolItem::Heuristic;
+ }
+
+ virtual ~HeuristicTreeItem() {}
+
+protected:
+ virtual void applyValuePrivate(gboolean value)
+ {
+ heuristic_table_->enabled = value;
+ }
+
+private:
+ heur_dtbl_entry_t *heuristic_table_;
+};
+
+
+EnabledProtocolItem::EnabledProtocolItem(QString name, QString description, bool enabled, EnabledProtocolItem* parent) :
+ ModelHelperTreeItem<EnabledProtocolItem>(parent),
+ name_(name),
+ description_(description),
+ enabled_(enabled),
+ enabledInit_(enabled),
+ type_(EnabledProtocolItem::Standard)
+{
+}
+
+EnabledProtocolItem::~EnabledProtocolItem()
+{
+}
+
+EnabledProtocolItem::EnableProtocolType EnabledProtocolItem::type() const
+{
+ return type_;
+}
+
+bool EnabledProtocolItem::applyValue()
+{
+ if (enabledInit_ != enabled_) {
+ applyValuePrivate(enabled_);
+ return true;
+ }
+
+ return false;
+}
+
+
+
+
+EnabledProtocolsModel::EnabledProtocolsModel(QObject *parent) :
+ QAbstractItemModel(parent),
+ root_(new ProtocolTreeItem(NULL, NULL))
+{
+}
+
+EnabledProtocolsModel::~EnabledProtocolsModel()
+{
+ delete root_;
+}
+
+int EnabledProtocolsModel::rowCount(const QModelIndex &parent) const
+{
+ EnabledProtocolItem *parent_item;
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<EnabledProtocolItem*>(parent.internalPointer());
+
+ if (parent_item == NULL)
+ return 0;
+
+ return parent_item->childCount();
+}
+
+int EnabledProtocolsModel::columnCount(const QModelIndex&) const
+{
+ return colLast;
+}
+
+QVariant EnabledProtocolsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+
+ switch ((enum EnabledProtocolsColumn)section) {
+ case colProtocol:
+ return tr("Protocol");
+ case colDescription:
+ return tr("Description");
+ default:
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QModelIndex EnabledProtocolsModel::parent(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(index.internalPointer());
+ if (item != NULL) {
+ EnabledProtocolItem* parent_item = item->parentItem();
+ if (parent_item != NULL) {
+ if (parent_item == root_)
+ return QModelIndex();
+
+ return createIndex(parent_item->row(), 0, parent_item);
+ }
+ }
+
+ return QModelIndex();
+}
+
+QModelIndex EnabledProtocolsModel::index(int row, int column, const QModelIndex& parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+
+ EnabledProtocolItem *parent_item, *child_item;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<EnabledProtocolItem*>(parent.internalPointer());
+
+ Q_ASSERT(parent_item);
+
+ child_item = parent_item->child(row);
+ if (child_item) {
+ return createIndex(row, column, child_item);
+ }
+
+ return QModelIndex();
+}
+
+Qt::ItemFlags EnabledProtocolsModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::ItemFlags();
+
+ Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+ switch(index.column())
+ {
+ case colProtocol:
+ flags |= Qt::ItemIsUserCheckable;
+ break;
+ default:
+ break;
+ }
+
+ return flags;
+}
+
+QVariant EnabledProtocolsModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(index.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch ((enum EnabledProtocolsColumn)index.column())
+ {
+ case colProtocol:
+ return item->name();
+ case colDescription:
+ return item->description();
+ default:
+ break;
+ }
+ break;
+ case Qt::CheckStateRole:
+ switch ((enum EnabledProtocolsColumn)index.column())
+ {
+ case colProtocol:
+ return item->enabled() ? Qt::Checked : Qt::Unchecked;
+ default:
+ break;
+ }
+ break;
+ case DATA_PROTOCOL_TYPE:
+ return QVariant::fromValue(item->type());
+ break;
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+bool EnabledProtocolsModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid())
+ return false;
+
+ if ((role != Qt::EditRole) &&
+ ((index.column() == colProtocol) && (role != Qt::CheckStateRole)))
+ return false;
+
+ if (data(index, role) == value) {
+ // Data appears unchanged, do not do additional checks.
+ return true;
+ }
+
+ EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(index.internalPointer());
+ if (item == NULL)
+ return false;
+
+ item->setEnabled(value.toInt() == Qt::Checked ? true : false);
+
+ return true;
+}
+
+static void addHeuristicItem(gpointer data, gpointer user_data)
+{
+ heur_dtbl_entry_t* heur = (heur_dtbl_entry_t*)data;
+ ProtocolTreeItem* protocol_item = (ProtocolTreeItem*)user_data;
+
+ HeuristicTreeItem* heuristic_row = new HeuristicTreeItem(heur, protocol_item);
+ protocol_item->prependChild(heuristic_row);
+}
+
+void EnabledProtocolsModel::populate()
+{
+ void *cookie;
+ protocol_t *protocol;
+
+ beginResetModel();
+
+ // Iterate over all the protocols
+ for (int i = proto_get_first_protocol(&cookie); i != -1; i = proto_get_next_protocol(&cookie))
+ {
+ if (proto_can_toggle_protocol(i))
+ {
+ protocol = find_protocol_by_id(i);
+ if (!proto_is_pino(protocol)) {
+ ProtocolTreeItem* protocol_row = new ProtocolTreeItem(protocol, root_);
+ root_->prependChild(protocol_row);
+
+ proto_heuristic_dissector_foreach(protocol, addHeuristicItem, protocol_row);
+ }
+ }
+ }
+
+ endResetModel();
+}
+
+void EnabledProtocolsModel::applyChanges(bool writeChanges)
+{
+ bool redissect = false;
+
+ for (int proto_index = 0; proto_index < root_->childCount(); proto_index++) {
+ EnabledProtocolItem* proto = root_->child(proto_index);
+ redissect |= proto->applyValue();
+ for (int heur_index = 0; heur_index < proto->childCount(); heur_index++) {
+ EnabledProtocolItem* heur = proto->child(heur_index);
+ redissect |= heur->applyValue();
+ }
+ }
+
+ if (redissect) {
+ saveChanges(writeChanges);
+ }
+}
+
+void EnabledProtocolsModel::disableProtocol(struct _protocol *protocol)
+{
+ ProtocolTreeItem disabled_proto(protocol, NULL);
+ disabled_proto.setEnabled(false);
+ if (disabled_proto.applyValue()) {
+ saveChanges();
+ }
+}
+
+void EnabledProtocolsModel::saveChanges(bool writeChanges)
+{
+ if (writeChanges) {
+ save_enabled_and_disabled_lists();
+ }
+ mainApp->emitAppSignal(MainApplication::PacketDissectionChanged);
+}
+
+
+EnabledProtocolsProxyModel::EnabledProtocolsProxyModel(QObject * parent)
+: QSortFilterProxyModel(parent),
+type_(EnabledProtocolsProxyModel::EveryWhere),
+protocolType_(EnabledProtocolItem::Any),
+filter_()
+{}
+
+bool EnabledProtocolsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ //Use EnabledProtocolItem directly for better performance
+ EnabledProtocolItem* left_item = static_cast<EnabledProtocolItem*>(left.internalPointer());
+ EnabledProtocolItem* right_item = static_cast<EnabledProtocolItem*>(right.internalPointer());
+
+ if ((left_item != NULL) && (right_item != NULL)) {
+
+ int compare_ret = 0;
+
+ if (left.column() == EnabledProtocolsModel::colProtocol)
+ compare_ret = left_item->name().compare(right_item->name(), Qt::CaseInsensitive);
+ else if (left.column() == EnabledProtocolsModel::colDescription)
+ compare_ret = left_item->description().compare(right_item->description(), Qt::CaseInsensitive);
+
+ if (compare_ret < 0)
+ return true;
+ }
+
+ return false;
+}
+
+Qt::ItemFlags EnabledProtocolsProxyModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags flags = Qt::NoItemFlags;
+ if (index.isValid())
+ {
+ QModelIndex source = mapToSource(index);
+ if (filterAcceptsSelf(source.row(), source.parent()) )
+ {
+ flags = Qt::ItemIsEnabled;
+ flags |= Qt::ItemIsSelectable;
+ flags |= Qt::ItemIsUserCheckable;
+ }
+ }
+
+ return flags;
+}
+
+bool EnabledProtocolsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ if (filterAcceptsSelf(sourceRow, sourceParent))
+ return true;
+
+#if 0
+ QModelIndex parent = sourceParent;
+ while (parent.isValid())
+ {
+ if (filterAcceptsSelf(parent.row(), parent.parent()))
+ return true;
+ parent = parent.parent();
+ }
+#endif
+
+ if (filterAcceptsChild(sourceRow, sourceParent))
+ return true;
+
+ return false;
+}
+
+bool EnabledProtocolsProxyModel::filterAcceptsSelf(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex nameIdx = sourceModel()->index(sourceRow, EnabledProtocolsModel::colProtocol, sourceParent);
+ if (! nameIdx.isValid())
+ return false;
+ EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(nameIdx.internalPointer());
+ if (! item)
+ return false;
+
+ QRegularExpression regex(filter_, QRegularExpression::CaseInsensitiveOption);
+ if (! regex.isValid())
+ return false;
+
+ if (protocolType_ == EnabledProtocolItem::Any || protocolType_ == item->type())
+ {
+ if (type_ != EnabledProtocolsProxyModel::EnabledItems && type_ != EnabledProtocolsProxyModel::DisabledItems)
+ {
+ if (! filter_.isEmpty())
+ {
+ if (item->name().contains(regex) && type_ != OnlyDescription)
+ return true;
+
+ if (item->description().contains(regex) && type_ != OnlyProtocol)
+ return true;
+ }
+ else
+ return true;
+ }
+ else if (filter_.isEmpty() || (! filter_.isEmpty() && (item->name().contains(regex) || item->description().contains(regex))))
+ {
+ if (type_ == EnabledProtocolsProxyModel::EnabledItems && item->enabled())
+ return true;
+ else if (type_ == EnabledProtocolsProxyModel::DisabledItems && ! item->enabled())
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool EnabledProtocolsProxyModel::filterAcceptsChild(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex item = sourceModel()->index(sourceRow, EnabledProtocolsModel::colProtocol, sourceParent);
+ if (! item.isValid())
+ return false;
+
+ int childCount = item.model()->rowCount(item);
+ if (childCount == 0)
+ return false;
+
+ for (int i = 0; i < childCount; i++)
+ {
+ if (filterAcceptsSelf(i, item))
+ return true;
+#if 0
+ /* Recursive search disabled for performance reasons */
+ if (filterAcceptsChild(i, item))
+ return true;
+#endif
+ }
+
+ return false;
+}
+
+void EnabledProtocolsProxyModel::setFilter(const QString& filter, EnabledProtocolsProxyModel::SearchType type,
+ EnabledProtocolItem::EnableProtocolType protocolType)
+{
+ filter_ = filter;
+ type_ = type;
+ protocolType_ = protocolType;
+ invalidateFilter();
+}
+
+void EnabledProtocolsProxyModel::setItemsEnable(EnabledProtocolsProxyModel::EnableType enableType, QModelIndex parent)
+{
+ if (! sourceModel())
+ return;
+
+ if (! parent.isValid())
+ beginResetModel();
+
+ int rowcount = rowCount(parent);
+ for (int row = 0; row < rowcount; row++)
+ {
+ QModelIndex idx = index(row, EnabledProtocolsModel::colProtocol, parent);
+
+ QModelIndex sIdx = mapToSource(idx);
+ if (sIdx.isValid())
+ {
+ EnabledProtocolItem* item = static_cast<EnabledProtocolItem*>(sIdx.internalPointer());
+ if (item && (protocolType_ == EnabledProtocolItem::Any || protocolType_ == item->type()) )
+ {
+ Qt::CheckState enable = idx.data(Qt::CheckStateRole).value<Qt::CheckState>();
+ if (enableType == Enable)
+ enable = Qt::Checked;
+ else if (enableType == Disable)
+ enable = Qt::Unchecked;
+ else
+ enable = enable == Qt::Checked ? Qt::Unchecked : Qt::Checked;
+
+ sourceModel()->setData(mapToSource(idx), QVariant::fromValue(enable), Qt::CheckStateRole);
+ }
+ }
+
+ setItemsEnable(enableType, idx);
+ }
+
+
+ if (! parent.isValid())
+ endResetModel();
+}
diff --git a/ui/qt/models/enabled_protocols_model.h b/ui/qt/models/enabled_protocols_model.h
new file mode 100644
index 00000000..23b2c1bb
--- /dev/null
+++ b/ui/qt/models/enabled_protocols_model.h
@@ -0,0 +1,143 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef ENABLED_PROTOCOLS_MODEL_H
+#define ENABLED_PROTOCOLS_MODEL_H
+
+#include <config.h>
+
+#include <ui/qt/models/tree_model_helpers.h>
+
+#include <epan/proto.h>
+
+#include <QAbstractItemModel>
+#include <QSortFilterProxyModel>
+
+class EnabledProtocolItem : public ModelHelperTreeItem<EnabledProtocolItem>
+{
+ Q_GADGET
+public:
+ enum EnableProtocolType{
+ Any,
+ Standard,
+ Heuristic
+ };
+ Q_ENUM(EnableProtocolType)
+
+ EnabledProtocolItem(QString name, QString description, bool enabled, EnabledProtocolItem* parent);
+ virtual ~EnabledProtocolItem();
+
+ QString name() const {return name_;}
+ QString description() const {return description_;}
+ bool enabled() const {return enabled_;}
+ void setEnabled(bool enable) {enabled_ = enable;}
+
+ EnableProtocolType type() const;
+
+ bool applyValue();
+
+protected:
+ virtual void applyValuePrivate(gboolean value) = 0;
+
+ QString name_;
+ QString description_;
+ bool enabled_;
+ bool enabledInit_; //value that model starts with to determine change
+ EnableProtocolType type_;
+};
+
+class EnabledProtocolsModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit EnabledProtocolsModel(QObject * parent = Q_NULLPTR);
+ virtual ~EnabledProtocolsModel();
+
+ enum EnabledProtocolsColumn {
+ colProtocol = 0,
+ colDescription,
+ colLast
+ };
+
+ enum EnableProtocolData {
+ DATA_ENABLE = Qt::UserRole,
+ DATA_PROTOCOL_TYPE
+ };
+
+ QModelIndex index(int row, int column,
+ const QModelIndex & = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &) const;
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ QVariant data(const QModelIndex &index, int role) const;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ void populate();
+
+ void applyChanges(bool writeChanges = true);
+ static void disableProtocol(struct _protocol *protocol);
+
+protected:
+ static void saveChanges(bool writeChanges = true);
+
+private:
+ EnabledProtocolItem* root_;
+};
+
+class EnabledProtocolsProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ enum SearchType
+ {
+ EveryWhere,
+ OnlyProtocol,
+ OnlyDescription,
+ EnabledItems,
+ DisabledItems
+ };
+ Q_ENUM(SearchType)
+
+ enum EnableType
+ {
+ Enable,
+ Disable,
+ Invert
+ };
+
+ explicit EnabledProtocolsProxyModel(QObject * parent = Q_NULLPTR);
+
+ virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+ void setFilter(const QString& filter, EnabledProtocolsProxyModel::SearchType type,
+ EnabledProtocolItem::EnableProtocolType protocolType);
+
+ void setItemsEnable(EnabledProtocolsProxyModel::EnableType enable, QModelIndex parent = QModelIndex());
+
+protected:
+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
+
+private:
+ EnabledProtocolsProxyModel::SearchType type_;
+ EnabledProtocolItem::EnableProtocolType protocolType_;
+ QString filter_;
+
+ bool filterAcceptsSelf(int sourceRow, const QModelIndex &sourceParent) const;
+ bool filterAcceptsChild(int sourceRow, const QModelIndex &sourceParent) const;
+};
+
+#endif // ENABLED_PROTOCOLS_MODEL_H
diff --git a/ui/qt/models/expert_info_model.cpp b/ui/qt/models/expert_info_model.cpp
new file mode 100644
index 00000000..017dba3d
--- /dev/null
+++ b/ui/qt/models/expert_info_model.cpp
@@ -0,0 +1,424 @@
+/* expert_info_model.cpp
+ * Data model for Expert Info tap data.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "expert_info_model.h"
+
+#include "file.h"
+#include <epan/proto.h>
+
+ExpertPacketItem::ExpertPacketItem(const expert_info_t& expert_info, column_info *cinfo, ExpertPacketItem* parent) :
+ packet_num_(expert_info.packet_num),
+ group_(expert_info.group),
+ severity_(expert_info.severity),
+ hf_id_(expert_info.hf_index),
+ protocol_(expert_info.protocol),
+ summary_(expert_info.summary),
+ parentItem_(parent)
+{
+ if (cinfo) {
+ info_ = col_get_text(cinfo, COL_INFO);
+ }
+}
+
+ExpertPacketItem::~ExpertPacketItem()
+{
+ for (int row = 0; row < childItems_.count(); row++)
+ {
+ delete childItems_.value(row);
+ }
+
+ childItems_.clear();
+}
+
+QString ExpertPacketItem::groupKey(bool group_by_summary, int severity, int group, QString protocol, int expert_hf)
+{
+ QString key = QString("%1|%2|%3")
+ .arg(severity)
+ .arg(group)
+ .arg(protocol);
+ if (group_by_summary) {
+ key += QString("|%1").arg(expert_hf);
+ }
+ return key;
+}
+
+QString ExpertPacketItem::groupKey(bool group_by_summary) {
+ return groupKey(group_by_summary, severity_, group_, protocol_, hf_id_);
+}
+
+void ExpertPacketItem::appendChild(ExpertPacketItem* child, QString hash)
+{
+ childItems_.append(child);
+ hashChild_[hash] = child;
+}
+
+ExpertPacketItem* ExpertPacketItem::child(int row)
+{
+ return childItems_.value(row);
+}
+
+ExpertPacketItem* ExpertPacketItem::child(QString hash)
+{
+ return hashChild_[hash];
+}
+
+int ExpertPacketItem::childCount() const
+{
+ return static_cast<int>(childItems_.count());
+}
+
+int ExpertPacketItem::row() const
+{
+ if (parentItem_)
+ return static_cast<int>(parentItem_->childItems_.indexOf(const_cast<ExpertPacketItem*>(this)));
+
+ return 0;
+}
+
+ExpertPacketItem* ExpertPacketItem::parentItem()
+{
+ return parentItem_;
+}
+
+
+
+
+ExpertInfoModel::ExpertInfoModel(CaptureFile& capture_file, QObject *parent) :
+ QAbstractItemModel(parent),
+ capture_file_(capture_file),
+ group_by_summary_(true),
+ root_(createRootItem())
+{
+}
+
+ExpertInfoModel::~ExpertInfoModel()
+{
+ delete root_;
+}
+
+void ExpertInfoModel::clear()
+{
+ beginResetModel();
+
+ eventCounts_.clear();
+ delete root_;
+ root_ = createRootItem();
+
+ endResetModel();
+}
+
+ExpertPacketItem* ExpertInfoModel::createRootItem()
+{
+ static const char* rootName = "ROOT";
+DIAG_OFF_CAST_AWAY_CONST
+ static expert_info_t root_expert = { 0, -1, -1, -1, rootName, (gchar*)rootName, NULL };
+DIAG_ON_CAST_AWAY_CONST
+
+ return new ExpertPacketItem(root_expert, NULL, NULL);
+}
+
+
+
+int ExpertInfoModel::numEvents(enum ExpertSeverity severity)
+{
+ return eventCounts_[severity];
+}
+
+QModelIndex ExpertInfoModel::index(int row, int column, const QModelIndex& parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+
+ ExpertPacketItem *parent_item, *child_item;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<ExpertPacketItem*>(parent.internalPointer());
+
+ Q_ASSERT(parent_item);
+ if (group_by_summary_) {
+ //don't allow group layer
+ if (parent_item == root_) {
+ int row_count = 0;
+ ExpertPacketItem *grandchild_item;
+
+ for (int subrow = 0; subrow < parent_item->childCount(); subrow++) {
+ child_item = parent_item->child(subrow);
+ //summary children are always stored in first child of group
+ grandchild_item = child_item->child(0);
+
+ if (row_count+grandchild_item->childCount() > row) {
+ return createIndex(row, column, grandchild_item->child(row-row_count));
+ }
+ row_count += grandchild_item->childCount();
+ }
+
+ //shouldn't happen
+ return QModelIndex();
+ }
+
+ int root_level = 0;
+ ExpertPacketItem *item = parent_item;
+ while (item != root_)
+ {
+ root_level++;
+ item = item->parentItem();
+ }
+
+ if (root_level == 3) {
+ child_item = parent_item->child(row);
+ if (child_item) {
+ return createIndex(row, column, child_item);
+ }
+ }
+
+ } else {
+ child_item = parent_item->child(row);
+ if (child_item) {
+ //only allow 2 levels deep
+ if (((parent_item == root_) || (parent_item->parentItem() == root_)))
+ return createIndex(row, column, child_item);
+ }
+ }
+ return QModelIndex();
+}
+
+QModelIndex ExpertInfoModel::parent(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ ExpertPacketItem *item = static_cast<ExpertPacketItem*>(index.internalPointer());
+ ExpertPacketItem *parent_item = item->parentItem();
+
+ if (group_by_summary_)
+ {
+ //don't allow group layer
+ int root_level = 0;
+ item = parent_item;
+ while ((item != root_) && (item != NULL))
+ {
+ root_level++;
+ item = item->parentItem();
+ }
+
+ if (root_level == 3)
+ return createIndex(parent_item->row(), 0, parent_item);
+
+ } else {
+ if (parent_item == root_)
+ return QModelIndex();
+
+ return createIndex(parent_item->row(), 0, parent_item);
+ }
+
+ return QModelIndex();
+}
+
+#if 0
+Qt::ItemFlags ExpertInfoModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return 0;
+
+ ExpertPacketItem* item = static_cast<ExpertPacketItem*>(index.internalPointer());
+ Qt::ItemFlags flags = QAbstractTableModel::flags(index);
+
+ //collapse???
+ return flags;
+}
+#endif
+
+QVariant ExpertInfoModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::ToolTipRole))
+ return QVariant();
+
+ ExpertPacketItem* item = static_cast<ExpertPacketItem*>(index.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ if (role == Qt::ToolTipRole)
+ {
+ QString filterName = proto_registrar_get_abbrev(item->hfId());
+ return filterName;
+ }
+ else if (role == Qt::DisplayRole)
+ {
+ switch ((enum ExpertColumn)index.column()) {
+ case colSeverity:
+ return QString(val_to_str_const(item->severity(), expert_severity_vals, "Unknown"));
+ case colSummary:
+ if (index.parent().isValid())
+ {
+ if (item->severity() == PI_COMMENT)
+ return item->summary().simplified();
+ if (group_by_summary_)
+ return item->colInfo().simplified();
+
+ return item->summary().simplified();
+ }
+ else
+ {
+ if (group_by_summary_)
+ {
+ if (item->severity() == PI_COMMENT)
+ return "Packet comments listed below.";
+ if (item->hfId() != -1) {
+ return proto_registrar_get_name(item->hfId());
+ } else {
+ return item->summary().simplified();
+ }
+ }
+ }
+ return QVariant();
+ case colGroup:
+ return QString(val_to_str_const(item->group(), expert_group_vals, "Unknown"));
+ case colProtocol:
+ return item->protocol();
+ case colCount:
+ if (!index.parent().isValid())
+ {
+ return item->childCount();
+ }
+ break;
+ case colPacket:
+ return item->packetNum();
+ case colHf:
+ return item->hfId();
+ default:
+ break;
+ }
+ }
+
+ return QVariant();
+}
+
+//GUI helpers
+void ExpertInfoModel::setGroupBySummary(bool group_by_summary)
+{
+ beginResetModel();
+ group_by_summary_ = group_by_summary;
+ endResetModel();
+}
+
+int ExpertInfoModel::rowCount(const QModelIndex &parent) const
+{
+ ExpertPacketItem *parent_item;
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<ExpertPacketItem*>(parent.internalPointer());
+
+ if (group_by_summary_) {
+ int row_count = 0;
+
+ //don't allow group layer
+ if (parent_item == root_) {
+ ExpertPacketItem *child_item, *grandchild_item;
+
+ for (int row = 0; row < parent_item->childCount(); row++) {
+ child_item = parent_item->child(row);
+ grandchild_item = child_item->child(0);
+ row_count += grandchild_item->childCount();
+ }
+
+ return row_count;
+ }
+
+ return parent_item->childCount();
+
+ } else {
+ //only allow 2 levels deep
+ if ((parent_item == root_) || (parent_item->parentItem() == root_))
+ return parent_item->childCount();
+ }
+
+ return 0;
+}
+
+int ExpertInfoModel::columnCount(const QModelIndex&) const
+{
+ return colLast;
+}
+
+void ExpertInfoModel::addExpertInfo(const struct expert_info_s& expert_info)
+{
+ QString groupKey = ExpertPacketItem::groupKey(FALSE, expert_info.severity, expert_info.group, QString(expert_info.protocol), expert_info.hf_index);
+ QString summaryKey = ExpertPacketItem::groupKey(TRUE, expert_info.severity, expert_info.group, QString(expert_info.protocol), expert_info.hf_index);
+
+ ExpertPacketItem* expert_root = root_->child(groupKey);
+ if (expert_root == NULL) {
+ ExpertPacketItem *new_item = new ExpertPacketItem(expert_info, &(capture_file_.capFile()->cinfo), root_);
+
+ root_->appendChild(new_item, groupKey);
+
+ expert_root = new_item;
+ }
+
+ ExpertPacketItem *expert = new ExpertPacketItem(expert_info, &(capture_file_.capFile()->cinfo), expert_root);
+ expert_root->appendChild(expert, groupKey);
+
+ //add the summary children off of the first child of the root children
+ ExpertPacketItem* summary_root = expert_root->child(0);
+
+ //make a summary child
+ ExpertPacketItem* expert_summary_root = summary_root->child(summaryKey);
+ if (expert_summary_root == NULL) {
+ ExpertPacketItem *new_summary = new ExpertPacketItem(expert_info, &(capture_file_.capFile()->cinfo), summary_root);
+
+ summary_root->appendChild(new_summary, summaryKey);
+ expert_summary_root = new_summary;
+ }
+
+ ExpertPacketItem *expert_summary = new ExpertPacketItem(expert_info, &(capture_file_.capFile()->cinfo), expert_summary_root);
+ expert_summary_root->appendChild(expert_summary, summaryKey);
+}
+
+void ExpertInfoModel::tapReset(void *eid_ptr)
+{
+ ExpertInfoModel *model = static_cast<ExpertInfoModel*>(eid_ptr);
+ if (!model)
+ return;
+
+ model->clear();
+}
+
+tap_packet_status ExpertInfoModel::tapPacket(void *eid_ptr, struct _packet_info *pinfo, struct epan_dissect *, const void *data, tap_flags_t)
+{
+ ExpertInfoModel *model = static_cast<ExpertInfoModel*>(eid_ptr);
+ const expert_info_t *expert_info = (const expert_info_t *) data;
+ tap_packet_status status = TAP_PACKET_DONT_REDRAW;
+
+ if (!pinfo || !model || !expert_info)
+ return TAP_PACKET_DONT_REDRAW;
+
+ model->addExpertInfo(*expert_info);
+
+ status = TAP_PACKET_REDRAW;
+
+ model->eventCounts_[(enum ExpertSeverity)expert_info->severity]++;
+
+ return status;
+}
+
+void ExpertInfoModel::tapDraw(void *eid_ptr)
+{
+ ExpertInfoModel *model = static_cast<ExpertInfoModel*>(eid_ptr);
+ if (!model)
+ return;
+
+ model->beginResetModel();
+ model->endResetModel();
+}
diff --git a/ui/qt/models/expert_info_model.h b/ui/qt/models/expert_info_model.h
new file mode 100644
index 00000000..ada10aea
--- /dev/null
+++ b/ui/qt/models/expert_info_model.h
@@ -0,0 +1,128 @@
+/** @file
+ *
+ * Data model for Expert Info tap data.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef EXPERT_INFO_MODEL_H
+#define EXPERT_INFO_MODEL_H
+
+#include <config.h>
+
+#include <QAbstractItemModel>
+#include <QList>
+#include <QMap>
+
+#include <ui/qt/capture_file.h>
+
+#include <epan/expert.h>
+#include <epan/tap.h>
+#include <epan/column-utils.h>
+
+class ExpertPacketItem
+{
+public:
+ ExpertPacketItem(const expert_info_t& expert_info, column_info *cinfo, ExpertPacketItem* parent);
+ virtual ~ExpertPacketItem();
+
+ unsigned int packetNum() const { return packet_num_; }
+ int group() const { return group_; }
+ int severity() const { return severity_; }
+ int hfId() const { return hf_id_; }
+ QString protocol() const { return protocol_; }
+ QString summary() const { return summary_; }
+ QString colInfo() const { return info_; }
+
+ static QString groupKey(bool group_by_summary, int severity, int group, QString protocol, int expert_hf);
+ QString groupKey(bool group_by_summary);
+
+ void appendChild(ExpertPacketItem* child, QString hash);
+ ExpertPacketItem* child(int row);
+ ExpertPacketItem* child(QString hash);
+ int childCount() const;
+ int row() const;
+ ExpertPacketItem* parentItem();
+
+private:
+ unsigned int packet_num_;
+ int group_;
+ int severity_;
+ int hf_id_;
+ // Half-hearted attempt at conserving memory. If this isn't sufficient,
+ // PacketListRecord interns column strings in a GStringChunk.
+ QByteArray protocol_;
+ QByteArray summary_;
+ QByteArray info_;
+
+ QList<ExpertPacketItem*> childItems_;
+ ExpertPacketItem* parentItem_;
+ QHash<QString, ExpertPacketItem*> hashChild_; //optimization for insertion
+};
+
+class ExpertInfoModel : public QAbstractItemModel
+{
+public:
+ ExpertInfoModel(CaptureFile& capture_file, QObject *parent = 0);
+ virtual ~ExpertInfoModel();
+
+ enum ExpertColumn {
+ colSeverity = 0,
+ colSummary,
+ colGroup,
+ colProtocol,
+ colCount,
+ colPacket,
+ colHf,
+ colLast
+ };
+
+ enum ExpertSeverity {
+ severityError = PI_ERROR,
+ severityWarn = PI_WARN,
+ severityNote = PI_NOTE,
+ severityChat = PI_CHAT,
+ severityComment = PI_COMMENT
+ };
+
+ QModelIndex index(int row, int column,
+ const QModelIndex & = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &) const;
+#if 0
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+#endif
+ QVariant data(const QModelIndex &index, int role) const;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ int numEvents(enum ExpertSeverity severity);
+
+ void clear();
+
+ //GUI helpers
+ void setGroupBySummary(bool group_by_summary);
+
+ // Called from tapPacket
+ void addExpertInfo(const struct expert_info_s& expert_info);
+
+ // Callbacks for register_tap_listener
+ static void tapReset(void *eid_ptr);
+ static tap_packet_status tapPacket(void *eid_ptr, struct _packet_info *pinfo, struct epan_dissect *, const void *data, tap_flags_t flags);
+ static void tapDraw(void *eid_ptr);
+
+private:
+ CaptureFile& capture_file_;
+
+ ExpertPacketItem* createRootItem();
+
+ bool group_by_summary_;
+ ExpertPacketItem* root_;
+
+ QHash<enum ExpertSeverity, int> eventCounts_;
+};
+#endif // EXPERT_INFO_MODEL_H
diff --git a/ui/qt/models/expert_info_proxy_model.cpp b/ui/qt/models/expert_info_proxy_model.cpp
new file mode 100644
index 00000000..ac13eff5
--- /dev/null
+++ b/ui/qt/models/expert_info_proxy_model.cpp
@@ -0,0 +1,290 @@
+/* expert_info_model.cpp
+ * Data model for Expert Info tap data.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/expert_info_model.h>
+#include <ui/qt/models/expert_info_proxy_model.h>
+#include <ui/qt/utils/color_utils.h>
+
+#include <QRegularExpression>
+
+ExpertInfoProxyModel::ExpertInfoProxyModel(QObject *parent) : QSortFilterProxyModel(parent),
+ severityMode_(Group)
+{
+}
+
+bool ExpertInfoProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
+{
+ ExpertPacketItem *left_item,
+ *right_item;
+ QString leftStr, rightStr;
+ bool checkPacketNumber = false;
+ int compare_ret;
+
+ if (source_left.parent().isValid() && source_right.parent().isValid()) {
+ left_item = static_cast<ExpertPacketItem*>(source_left.parent().internalPointer());
+ right_item = static_cast<ExpertPacketItem*>(source_right.parent().internalPointer());
+ } else {
+ left_item = static_cast<ExpertPacketItem*>(source_left.internalPointer());
+ right_item = static_cast<ExpertPacketItem*>(source_right.internalPointer());
+ }
+
+ if ((left_item != NULL) && (right_item != NULL)) {
+ switch (source_left.column())
+ {
+ case colProxySeverity:
+ if (left_item->severity() != right_item->severity()) {
+ return (left_item->severity() < right_item->severity());
+ }
+
+ checkPacketNumber = true;
+ break;
+ case colProxySummary:
+ compare_ret = left_item->summary().compare(right_item->summary());
+ if (compare_ret < 0)
+ return true;
+ if (compare_ret > 0)
+ return false;
+
+ checkPacketNumber = true;
+ break;
+ case colProxyGroup:
+ if (left_item->group() != right_item->group()) {
+ return (left_item->group() < right_item->group());
+ }
+
+ checkPacketNumber = true;
+ break;
+ case colProxyProtocol:
+ compare_ret = left_item->protocol().compare(right_item->protocol());
+ if (compare_ret < 0)
+ return true;
+ if (compare_ret > 0)
+ return false;
+
+ checkPacketNumber = true;
+ break;
+ case colProxyCount:
+ break;
+ default:
+ break;
+ }
+
+ if (checkPacketNumber) {
+ return (left_item->packetNum() < right_item->packetNum());
+ }
+ }
+
+ // fallback to string cmp on other fields
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+}
+
+QVariant ExpertInfoProxyModel::data(const QModelIndex &proxy_index, int role) const
+{
+ QModelIndex source_index;
+
+ switch (role)
+ {
+ case Qt::BackgroundRole:
+ {
+ source_index = mapToSource(proxy_index);
+
+ // only color base row
+ if (!source_index.isValid() || source_index.parent().isValid())
+ return QVariant();
+
+ ExpertPacketItem* item = static_cast<ExpertPacketItem*>(source_index.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ // provide background color for groups
+ switch(item->severity()) {
+ case(PI_COMMENT):
+ return QBrush(ColorUtils::expert_color_comment);
+ case(PI_CHAT):
+ return QBrush(ColorUtils::expert_color_chat);
+ case(PI_NOTE):
+ return QBrush(ColorUtils::expert_color_note);
+ case(PI_WARN):
+ return QBrush(ColorUtils::expert_color_warn);
+ case(PI_ERROR):
+ return QBrush(ColorUtils::expert_color_error);
+ }
+ }
+ break;
+ case Qt::ForegroundRole:
+ {
+ source_index = mapToSource(proxy_index);
+
+ // only color base row
+ if (!source_index.isValid() || source_index.parent().isValid())
+ return QVariant();
+
+ ExpertPacketItem* item = static_cast<ExpertPacketItem*>(source_index.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ // provide foreground color for groups
+ switch(item->severity()) {
+ case(PI_COMMENT):
+ case(PI_CHAT):
+ case(PI_NOTE):
+ case(PI_WARN):
+ case(PI_ERROR):
+ return QBrush(ColorUtils::expert_color_foreground);
+ }
+ }
+ break;
+ case Qt::TextAlignmentRole:
+ switch (proxy_index.column())
+ {
+ case colProxySeverity:
+ //packet number should be right aligned
+ if (source_index.parent().isValid())
+ return Qt::AlignRight;
+ break;
+ case colProxyCount:
+ return Qt::AlignRight;
+ default:
+ break;
+ }
+ return Qt::AlignLeft;
+
+ case Qt::DisplayRole:
+ source_index = mapToSource(proxy_index);
+
+ switch (proxy_index.column())
+ {
+ case colProxySeverity:
+ if (source_index.parent().isValid())
+ return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colPacket), role);
+
+ return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colSeverity), role);
+ case colProxySummary:
+ return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colSummary), role);
+ case colProxyGroup:
+ return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colGroup), role);
+ case colProxyProtocol:
+ return sourceModel()->data(source_index.sibling(source_index.row(), ExpertInfoModel::colProtocol), role);
+ case colProxyCount:
+ //only show counts for parent
+ if (!source_index.parent().isValid()) {
+ //because of potential filtering, count is computed manually
+ unsigned int count = 0;
+ ExpertPacketItem *child_item,
+ *item = static_cast<ExpertPacketItem*>(source_index.internalPointer());
+ for (int row = 0; row < item->childCount(); row++) {
+ child_item = item->child(row);
+ if (child_item == NULL)
+ continue;
+ if (filterAcceptItem(*child_item))
+ count++;
+ }
+
+ return count;
+ }
+ }
+ break;
+ }
+
+ return QSortFilterProxyModel::data(proxy_index, role);
+}
+
+QVariant ExpertInfoProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+
+ switch ((enum ExpertProxyColumn)section) {
+ case colProxySeverity:
+ if (severityMode_ == Packet)
+ return tr("Packet");
+ else
+ return tr("Severity");
+ case colProxySummary:
+ return tr("Summary");
+ case colProxyGroup:
+ return tr("Group");
+ case colProxyProtocol:
+ return tr("Protocol");
+ case colProxyCount:
+ return tr("Count");
+ default:
+ break;
+ }
+ }
+ return QVariant();
+}
+
+int ExpertInfoProxyModel::columnCount(const QModelIndex&) const
+{
+ return colProxyLast;
+}
+
+bool ExpertInfoProxyModel::filterAcceptItem(ExpertPacketItem& item) const
+{
+ if (hidden_severities_.contains(item.severity()))
+ return false;
+
+ if (!textFilter_.isEmpty()) {
+ QRegularExpression regex(textFilter_, QRegularExpression::CaseInsensitiveOption |
+ QRegularExpression::UseUnicodePropertiesOption);
+ if (! regex.isValid())
+ return false;
+
+ if (item.protocol().contains(regex))
+ return true;
+
+ if (item.summary().contains(regex))
+ return true;
+
+ if (item.colInfo().contains(regex))
+ return true;
+
+ return false;
+ }
+
+ return true;
+}
+
+bool ExpertInfoProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex severityIdx = sourceModel()->index(sourceRow, ExpertInfoModel::colSeverity, sourceParent);
+ ExpertPacketItem* item = static_cast<ExpertPacketItem*>(severityIdx.internalPointer());
+ if (item == NULL)
+ return true;
+
+ return filterAcceptItem(*item);
+}
+
+//GUI helpers
+void ExpertInfoProxyModel::setSeverityMode(enum SeverityMode mode)
+{
+ severityMode_ = mode;
+ emit headerDataChanged(Qt::Vertical, 0, 1);
+}
+
+void ExpertInfoProxyModel::setSeverityFilter(int severity, bool hide)
+{
+ if (hide)
+ {
+ hidden_severities_ << severity;
+ }
+ else
+ {
+ hidden_severities_.removeOne(severity);
+ }
+
+ invalidateFilter();
+}
+
+void ExpertInfoProxyModel::setSummaryFilter(const QString &filter)
+{
+ textFilter_ = filter;
+ invalidateFilter();
+}
diff --git a/ui/qt/models/expert_info_proxy_model.h b/ui/qt/models/expert_info_proxy_model.h
new file mode 100644
index 00000000..411d88d2
--- /dev/null
+++ b/ui/qt/models/expert_info_proxy_model.h
@@ -0,0 +1,61 @@
+/** @file
+ *
+ * Data model for Expert Info tap data.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef EXPERT_INFO_PROXY_MODEL_H
+#define EXPERT_INFO_PROXY_MODEL_H
+
+#include <config.h>
+
+#include <QSortFilterProxyModel>
+
+class ExpertPacketItem;
+
+class ExpertInfoProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ ExpertInfoProxyModel(QObject *parent = 0);
+
+ enum SeverityMode { Group, Packet };
+ enum ExpertProxyColumn {
+ colProxySeverity = 0,
+ colProxySummary,
+ colProxyGroup,
+ colProxyProtocol,
+ colProxyCount,
+ colProxyLast
+ };
+
+ QVariant data(const QModelIndex &index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
+
+ //GUI helpers
+ void setSeverityMode(enum SeverityMode);
+ void setSeverityFilter(int severity, bool hide);
+ void setSummaryFilter(const QString &filter);
+
+protected:
+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
+ bool filterAcceptItem(ExpertPacketItem& item) const;
+
+ enum SeverityMode severityMode_;
+ QList<int> hidden_severities_;
+
+ QString textFilter_;
+
+};
+
+#endif // EXPERT_INFO_PROXY_MODEL_H
diff --git a/ui/qt/models/export_objects_model.cpp b/ui/qt/models/export_objects_model.cpp
new file mode 100644
index 00000000..3cf9ec63
--- /dev/null
+++ b/ui/qt/models/export_objects_model.cpp
@@ -0,0 +1,316 @@
+/* export_objects_model.cpp
+ * Data model for Export Objects.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "export_objects_model.h"
+
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/utils/variant_pointer.h>
+#include <wsutil/filesystem.h>
+#include <epan/prefs.h>
+
+#include <QDir>
+
+extern "C" {
+
+static void
+object_list_add_entry(void *gui_data, export_object_entry_t *entry) {
+ export_object_list_gui_t *object_list = (export_object_list_gui_t*)gui_data;
+
+ if (object_list && object_list->model)
+ object_list->model->addObjectEntry(entry);
+}
+
+static export_object_entry_t*
+object_list_get_entry(void *gui_data, int row) {
+ export_object_list_gui_t *object_list = (export_object_list_gui_t*)gui_data;
+
+ if (object_list && object_list->model)
+ return object_list->model->objectEntry(row);
+
+ return NULL;
+}
+
+} // extern "C"
+
+
+
+
+ExportObjectModel::ExportObjectModel(register_eo_t* eo, QObject *parent) :
+ QAbstractTableModel(parent),
+ eo_(eo)
+{
+ eo_gui_data_.model = this;
+
+ export_object_list_.add_entry = object_list_add_entry;
+ export_object_list_.get_entry = object_list_get_entry;
+ export_object_list_.gui_data = (void*)&eo_gui_data_;
+}
+
+ExportObjectModel::~ExportObjectModel()
+{
+ foreach (QVariant v, objects_) {
+ eo_free_entry(VariantPointer<export_object_entry_t>::asPtr(v));
+ }
+}
+
+QVariant ExportObjectModel::data(const QModelIndex &index, int role) const
+{
+ if ((!index.isValid()) || ((role != Qt::DisplayRole) && (role != Qt::UserRole))) {
+ return QVariant();
+ }
+
+ if (role == Qt::DisplayRole)
+ {
+ export_object_entry_t *entry = VariantPointer<export_object_entry_t>::asPtr(objects_.value(index.row()));
+ if (entry == NULL)
+ return QVariant();
+
+ switch(index.column())
+ {
+ case colPacket:
+ return QString::number(entry->pkt_num);
+ case colHostname:
+ return QString::fromUtf8(entry->hostname);
+ case colContent:
+ return QString::fromUtf8(entry->content_type);
+ case colSize:
+ return file_size_to_qstring(entry->payload_len);
+ case colFilename:
+ return QString::fromUtf8(entry->filename);
+ }
+ }
+ else if (role == Qt::UserRole)
+ {
+ return objects_.value(index.row());
+ }
+
+ return QVariant();
+}
+
+QVariant ExportObjectModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
+ return QVariant();
+
+ switch (section) {
+ case colPacket:
+ return tr("Packet");
+ case colHostname:
+ return tr("Hostname");
+ case colContent:
+ return tr("Content Type");
+ case colSize:
+ return tr("Size");
+ case colFilename:
+ return tr("Filename");
+ }
+
+ return QVariant();
+}
+
+int ExportObjectModel::rowCount(const QModelIndex &parent) const
+{
+ // there are no children
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return static_cast<int>(objects_.count());
+}
+
+int ExportObjectModel::columnCount(const QModelIndex&) const
+{
+ return colExportObjectMax;
+}
+
+void ExportObjectModel::addObjectEntry(export_object_entry_t *entry)
+{
+ if (entry == NULL)
+ return;
+
+ int count = static_cast<int>(objects_.count());
+ beginInsertRows(QModelIndex(), count, count);
+ objects_.append(VariantPointer<export_object_entry_t>::asQVariant(entry));
+ endInsertRows();
+}
+
+export_object_entry_t* ExportObjectModel::objectEntry(int row)
+{
+ return VariantPointer<export_object_entry_t>::asPtr(objects_.value(row));
+}
+
+bool ExportObjectModel::saveEntry(QModelIndex &index, QString filename)
+{
+ if (!index.isValid() || filename.isEmpty())
+ return false;
+
+ export_object_entry_t *entry = VariantPointer<export_object_entry_t>::asPtr(objects_.value(index.row()));
+ if (entry == NULL)
+ return false;
+
+ if (filename.length() > 0) {
+ write_file_binary_mode(qUtf8Printable(filename), entry->payload_data, entry->payload_len);
+ }
+
+ return true;
+}
+
+void ExportObjectModel::saveAllEntries(QString path)
+{
+ if (path.isEmpty())
+ return;
+
+ QDir save_dir(path);
+ export_object_entry_t *entry;
+
+ for (QList<QVariant>::iterator it = objects_.begin(); it != objects_.end(); ++it)
+ {
+ entry = VariantPointer<export_object_entry_t>::asPtr(*it);
+ if (entry == NULL)
+ continue;
+
+ guint count = 0;
+ QString filename;
+
+ do {
+ GString *safe_filename;
+
+ if (entry->filename)
+ safe_filename = eo_massage_str(entry->filename,
+ EXPORT_OBJECT_MAXFILELEN, count);
+ else {
+ char generic_name[EXPORT_OBJECT_MAXFILELEN+1];
+ const char *ext;
+ ext = eo_ct2ext(entry->content_type);
+ snprintf(generic_name, sizeof(generic_name),
+ "object%u%s%s", entry->pkt_num, ext ? "." : "",
+ ext ? ext : "");
+ safe_filename = eo_massage_str(generic_name,
+ EXPORT_OBJECT_MAXFILELEN, count);
+ }
+ filename = QString::fromUtf8(safe_filename->str);
+ g_string_free(safe_filename, TRUE);
+ } while (save_dir.exists(filename) && ++count < prefs.gui_max_export_objects);
+ write_file_binary_mode(qUtf8Printable(save_dir.filePath(filename)),
+ entry->payload_data, entry->payload_len);
+ }
+}
+
+void ExportObjectModel::resetObjects()
+{
+ export_object_gui_reset_cb reset_cb = get_eo_reset_func(eo_);
+
+ beginResetModel();
+ objects_.clear();
+ endResetModel();
+
+ if (reset_cb)
+ reset_cb();
+}
+
+// Called by taps
+/* Runs at the beginning of tapping only */
+void ExportObjectModel::resetTap(void *tapdata)
+{
+ export_object_list_t *tap_object = (export_object_list_t *)tapdata;
+ export_object_list_gui_t *object_list = (export_object_list_gui_t *)tap_object->gui_data;
+ if (object_list && object_list->model)
+ object_list->model->resetObjects();
+}
+
+const char* ExportObjectModel::getTapListenerName()
+{
+ return get_eo_tap_listener_name(eo_);
+}
+
+void* ExportObjectModel::getTapData()
+{
+ return &export_object_list_;
+}
+
+tap_packet_cb ExportObjectModel::getTapPacketFunc()
+{
+ return get_eo_packet_func(eo_);
+}
+
+void ExportObjectModel::removeTap()
+{
+ eo_gui_data_.model = NULL;
+}
+
+
+
+ExportObjectProxyModel::ExportObjectProxyModel(QObject * parent)
+ : QSortFilterProxyModel(parent)
+{
+
+}
+
+bool ExportObjectProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
+{
+ export_object_entry_t *left_entry = VariantPointer<export_object_entry_t>::asPtr(sourceModel()->data(source_left, Qt::UserRole)),
+ *right_entry = VariantPointer<export_object_entry_t>::asPtr(sourceModel()->data(source_right, Qt::UserRole));
+
+ if ((left_entry != NULL) && (right_entry != NULL))
+ {
+ switch (source_left.column())
+ {
+ case ExportObjectModel::colPacket:
+ return left_entry->pkt_num < right_entry->pkt_num;
+ case ExportObjectModel::colSize:
+ return left_entry->payload_len < right_entry->payload_len;
+ case ExportObjectModel::colFilename:
+ break;
+ }
+ }
+
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+}
+
+void ExportObjectProxyModel::setContentFilterString(QString filter_)
+{
+ contentFilter_ = filter_;
+ invalidateFilter();
+}
+
+void ExportObjectProxyModel::setTextFilterString(QString filter_)
+{
+ textFilter_ = filter_;
+ invalidateFilter();
+}
+
+bool ExportObjectProxyModel::filterAcceptsRow(int source_row, const QModelIndex &/*source_parent*/) const
+{
+ if (contentFilter_.length() > 0)
+ {
+ QModelIndex idx = sourceModel()->index(source_row, ExportObjectModel::colContent);
+ if (!idx.isValid())
+ return false;
+
+ if (contentFilter_.compare(idx.data().toString()) != 0)
+ return false;
+ }
+
+ if (textFilter_.length() > 0)
+ {
+ QModelIndex hostIdx = sourceModel()->index(source_row, ExportObjectModel::colHostname);
+ QModelIndex fileIdx = sourceModel()->index(source_row, ExportObjectModel::colFilename);
+ if (!hostIdx.isValid() || !fileIdx.isValid())
+ return false;
+
+ QString host = hostIdx.data().toString();
+ QString file = fileIdx.data().toString();
+
+ if (!host.contains(textFilter_) && !file.contains(textFilter_))
+ return false;
+ }
+
+ return true;
+}
diff --git a/ui/qt/models/export_objects_model.h b/ui/qt/models/export_objects_model.h
new file mode 100644
index 00000000..1aff4345
--- /dev/null
+++ b/ui/qt/models/export_objects_model.h
@@ -0,0 +1,91 @@
+/** @file
+ *
+ * Data model for Export Objects.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef EXPORT_OBJECTS_MODEL_H
+#define EXPORT_OBJECTS_MODEL_H
+
+#include <config.h>
+
+#include <epan/tap.h>
+#include <epan/export_object.h>
+
+#include <QAbstractTableModel>
+#include <QSortFilterProxyModel>
+#include <QList>
+
+typedef struct export_object_list_gui_t {
+ class ExportObjectModel *model;
+} export_object_list_gui_t;
+
+class ExportObjectModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ ExportObjectModel(register_eo_t* eo, QObject *parent);
+ virtual ~ExportObjectModel();
+
+ enum ExportObjectColumn {
+ colPacket = 0,
+ colHostname,
+ colContent,
+ colSize,
+ colFilename,
+ colExportObjectMax
+ };
+
+ void addObjectEntry(export_object_entry_t *entry);
+ export_object_entry_t *objectEntry(int row);
+ void resetObjects();
+
+ bool saveEntry(QModelIndex &index, QString filename);
+ void saveAllEntries(QString path);
+
+ const char* getTapListenerName();
+ void* getTapData();
+ tap_packet_cb getTapPacketFunc();
+ static void resetTap(void *tapdata);
+ void removeTap();
+
+ QVariant data(const QModelIndex &index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+private:
+ QList<QVariant> objects_;
+
+ export_object_list_t export_object_list_;
+ export_object_list_gui_t eo_gui_data_;
+ register_eo_t* eo_;
+};
+
+class ExportObjectProxyModel : public QSortFilterProxyModel
+{
+public:
+
+ explicit ExportObjectProxyModel(QObject * parent = Q_NULLPTR);
+
+ void setContentFilterString(QString contentFilter);
+ void setTextFilterString(QString textFilter);
+
+protected:
+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
+
+private:
+ QString contentFilter_;
+ QString textFilter_;
+
+};
+
+#endif // EXPORT_OBJECTS_MODEL_H
diff --git a/ui/qt/models/fileset_entry_model.cpp b/ui/qt/models/fileset_entry_model.cpp
new file mode 100644
index 00000000..8c9f504b
--- /dev/null
+++ b/ui/qt/models/fileset_entry_model.cpp
@@ -0,0 +1,155 @@
+/* fileset_entry_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/fileset_entry_model.h>
+
+#include "wsutil/utf8_entities.h"
+
+#include <ui/qt/utils/qt_ui_utils.h>
+
+#include <QRegularExpression>
+
+FilesetEntryModel::FilesetEntryModel(QObject * parent) :
+ QAbstractItemModel(parent)
+{}
+
+QModelIndex FilesetEntryModel::index(int row, int column, const QModelIndex &) const
+{
+ if (row >= entries_.count() || row < 0 || column > ColumnCount) {
+ return QModelIndex();
+ }
+
+ return createIndex(row, column, const_cast<fileset_entry *>(entries_.at(row)));
+}
+
+int FilesetEntryModel::rowCount(const QModelIndex &) const
+{
+ return static_cast<int>(entries_.count());
+}
+
+QVariant FilesetEntryModel::data(const QModelIndex &index, int role) const
+{
+ if (! index.isValid() || index.row() >= rowCount())
+ return QVariant();
+
+ const fileset_entry *entry = static_cast<fileset_entry*>(index.internalPointer());
+ if (role == Qt::DisplayRole && entry) {
+ switch (index.column()) {
+ case Name:
+ return QString(entry->name);
+ case Created:
+ {
+ QString created = nameToDate(entry->name);
+ if (created.length() < 1) {
+ /* if this file doesn't follow the file set pattern, */
+ /* use the creation time of that file if available */
+ /* https://en.wikipedia.org/wiki/ISO_8601 */
+ /*
+ * macOS provides 0 if the file system doesn't support the
+ * creation time; FreeBSD provides -1.
+ *
+ * If this OS doesn't provide the creation time with stat(),
+ * it will be 0.
+ */
+ if (entry->ctime > 0) {
+ created = time_tToString(entry->ctime);
+ } else {
+ created = UTF8_EM_DASH;
+ }
+ }
+ return created;
+ }
+ case Modified:
+ return time_tToString(entry->mtime);
+ case Size:
+ return file_size_to_qstring(entry->size);
+ default:
+ break;
+ }
+ } else if (role == Qt::ToolTipRole) {
+ return QString(tr("Open this capture file"));
+ } else if (role == Qt::TextAlignmentRole) {
+ switch (index.column()) {
+ case Size:
+ // Not perfect but better than nothing.
+ return Qt::AlignRight;
+ default:
+ return Qt::AlignLeft;
+ }
+ }
+ return QVariant();
+}
+
+QVariant FilesetEntryModel::headerData(int section, Qt::Orientation, int role) const
+{
+ if (role != Qt::DisplayRole) return QVariant();
+
+ switch (section) {
+ case Name:
+ return tr("Filename");
+ case Created:
+ return tr("Created");
+ case Modified:
+ return tr("Modified");
+ case Size:
+ return tr("Size");
+ default:
+ break;
+ }
+ return QVariant();
+}
+
+void FilesetEntryModel::appendEntry(const fileset_entry *entry)
+{
+ emit beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ entries_ << entry;
+ emit endInsertRows();
+}
+
+void FilesetEntryModel::clear()
+{
+ fileset_delete();
+ beginResetModel();
+ entries_.clear();
+ endResetModel();
+}
+
+QString FilesetEntryModel::nameToDate(const char *name) const {
+ QString dn;
+
+ if (!fileset_filename_match_pattern(name))
+ return NULL;
+
+ dn = name;
+ dn.remove(QRegularExpression(".*_"));
+ dn.truncate(14);
+ dn.insert(4, '-');
+ dn.insert(7, '-');
+ dn.insert(10, ' ');
+ dn.insert(13, ':');
+ dn.insert(16, ':');
+ return dn;
+}
+
+QString FilesetEntryModel::time_tToString(time_t clock) const
+{
+ struct tm *local = localtime(&clock);
+ if (!local) return UTF8_EM_DASH;
+
+ // yyyy-MM-dd HH:mm:ss
+ // The equivalent QDateTime call is pretty slow here, possibly related to QTBUG-21678
+ // and/or QTBUG-41714.
+ return QString("%1-%2-%3 %4:%5:%6")
+ .arg(local->tm_year + 1900, 4, 10, QChar('0'))
+ .arg(local->tm_mon+1, 2, 10, QChar('0'))
+ .arg(local->tm_mday, 2, 10, QChar('0'))
+ .arg(local->tm_hour, 2, 10, QChar('0'))
+ .arg(local->tm_min, 2, 10, QChar('0'))
+ .arg(local->tm_sec, 2, 10, QChar('0'));
+}
diff --git a/ui/qt/models/fileset_entry_model.h b/ui/qt/models/fileset_entry_model.h
new file mode 100644
index 00000000..aa812b0c
--- /dev/null
+++ b/ui/qt/models/fileset_entry_model.h
@@ -0,0 +1,52 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef FILESET_ENTRY_MODEL_H
+#define FILESET_ENTRY_MODEL_H
+
+#include <config.h>
+
+#include <glib.h>
+
+#include <fileset.h>
+
+#include <QAbstractItemModel>
+#include <QModelIndex>
+#include <QVector>
+
+class FilesetEntryModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit FilesetEntryModel(QObject * parent = 0);
+
+ QModelIndex index(int row, int column, const QModelIndex & = QModelIndex()) const;
+ // Everything is under the root.
+ virtual QModelIndex parent(const QModelIndex &) const { return QModelIndex(); }
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex &) const { return ColumnCount; }
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual QVariant headerData(int section, Qt::Orientation, int role = Qt::DisplayRole) const;
+
+ virtual void appendEntry(const fileset_entry *entry);
+ const fileset_entry *getRowEntry(int row) const { return entries_.value(row, NULL); }
+ int entryCount() const { return static_cast<int>(entries_.count()); }
+ // Calls fileset_delete and clears our model data.
+ void clear();
+
+private:
+ QVector<const fileset_entry *> entries_;
+ enum Column { Name, Created, Modified, Size, ColumnCount };
+
+ QString nameToDate(const char *name) const ;
+ QString time_tToString(time_t clock) const;
+};
+
+#endif // FILESET_ENTRY_MODEL_H
diff --git a/ui/qt/models/filter_list_model.cpp b/ui/qt/models/filter_list_model.cpp
new file mode 100644
index 00000000..3ed25839
--- /dev/null
+++ b/ui/qt/models/filter_list_model.cpp
@@ -0,0 +1,314 @@
+/* filter_list_model.cpp
+ * Model for all filter types
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <glib.h>
+
+#include <wsutil/filesystem.h>
+
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/utils/wireshark_mime_data.h>
+#include <ui/qt/models/filter_list_model.h>
+#include <ui/qt/models/profile_model.h>
+
+#include <QFile>
+#include <QTextStream>
+#include <QRegularExpression>
+#include <QDir>
+#include <QMimeData>
+
+/*
+ * Old filter file name.
+ */
+#define FILTER_FILE_NAME "filters"
+
+/*
+ * Capture filter file name.
+ */
+#define CFILTER_FILE_NAME "cfilters"
+
+/*
+ * Display filter file name.
+ */
+#define DFILTER_FILE_NAME "dfilters"
+
+FilterListModel::FilterListModel(QObject * parent) :
+ QAbstractListModel(parent),
+ type_(FilterListModel::Display)
+{
+ reload();
+}
+
+FilterListModel::FilterListModel(FilterListModel::FilterListType type, QObject * parent) :
+ QAbstractListModel(parent),
+ type_(type)
+{
+ reload();
+}
+
+void FilterListModel::reload()
+{
+ storage.clear();
+
+ const char * cfile = (type_ == FilterListModel::Capture) ? CFILTER_FILE_NAME : DFILTER_FILE_NAME;
+
+ /* Try personal config file first */
+ QString fileName = gchar_free_to_qstring(get_persconffile_path(cfile, TRUE));
+ if (fileName.length() <= 0 || ! QFileInfo::exists(fileName))
+ fileName = gchar_free_to_qstring(get_persconffile_path(FILTER_FILE_NAME, TRUE));
+ if (fileName.length() <= 0 || ! QFileInfo::exists(fileName))
+ fileName = gchar_free_to_qstring(get_datafile_path(cfile));
+ if (fileName.length() <= 0 || ! QFileInfo::exists(fileName))
+ return;
+
+ QFile file(fileName);
+ /* Still can use the model, just have to start from an empty set */
+ if (! file.open(QIODevice::ReadOnly | QIODevice::Text))
+ return;
+
+ QTextStream in(&file);
+ QRegularExpression rx("\\s*\\\"\\s*(.*?)\\s*\\\"\\s(.*)");
+ while (!in.atEnd())
+ {
+ QString data = in.readLine().trimmed();
+ /* Filter out lines that do not contain content:
+ * - Starting with # is a comment
+ * - Does not start with a quoted string
+ */
+ if (data.startsWith("#") || ! data.trimmed().startsWith("\""))
+ continue;
+
+ QStringList content = data.split(QChar('\n'));
+ foreach (QString line, content)
+ {
+ QRegularExpressionMatch match = rx.match(line);
+ if (match.hasMatch()) {
+ addFilter(match.captured(1).trimmed(), match.captured(2).trimmed());
+ }
+ }
+ }
+}
+
+void FilterListModel::setFilterType(FilterListModel::FilterListType type)
+{
+ type_ = type;
+ reload();
+}
+
+FilterListModel::FilterListType FilterListModel::filterType() const
+{
+ return type_;
+}
+
+int FilterListModel::rowCount(const QModelIndex &/* parent */) const
+{
+ return static_cast<int>(storage.count());
+}
+
+int FilterListModel::columnCount(const QModelIndex &/* parent */) const
+{
+ return 2;
+}
+
+QVariant FilterListModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (section >= columnCount() || section < 0 || orientation != Qt::Horizontal)
+ return QVariant();
+
+ if (role == Qt::DisplayRole)
+ {
+ switch (section) {
+ case ColumnName:
+ return tr("Filter Name");
+ break;
+ case ColumnExpression:
+ return tr("Filter Expression");
+ break;
+ }
+ }
+
+ return QVariant();
+}
+
+QVariant FilterListModel::data(const QModelIndex &index, int role) const
+{
+ if (! index.isValid() || index.row() >= rowCount())
+ return QVariant();
+
+ QStringList row = storage.at(index.row()).split("\n");
+ if (role == Qt::DisplayRole)
+ return row.at(index.column());
+
+ return QVariant();
+}
+
+bool FilterListModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (! index.isValid() || index.row() >= rowCount() || role != Qt::EditRole)
+ return false;
+
+ QStringList row = storage.at(index.row()).split("\n");
+ if (row.count() <= index.column())
+ return false;
+
+ if (index.column() == FilterListModel::ColumnName && value.toString().contains("\""))
+ return false;
+
+ row[index.column()] = value.toString();
+ storage[index.row()] = row.join("\n");
+
+ return true;
+}
+
+Qt::ItemFlags FilterListModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags fl = QAbstractListModel::flags(index);
+ fl |= Qt::ItemIsDropEnabled;
+
+ if (! index.isValid() || index.row() >= rowCount())
+ return fl;
+
+ fl |= Qt::ItemIsEditable | Qt::ItemIsDragEnabled;
+
+ return fl;
+}
+QModelIndex FilterListModel::addFilter(QString name, QString expression)
+{
+ if (name.length() == 0 || expression.length() == 0)
+ return QModelIndex();
+
+ beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ storage << QString("%1\n%2").arg(name).arg(expression);
+ endInsertRows();
+
+ return index(rowCount() - 1, 0);
+}
+
+QModelIndex FilterListModel::findByName(QString name)
+{
+ if (name.length() == 0)
+ return QModelIndex();
+
+ for (int cnt = 0; cnt < rowCount(); cnt++)
+ {
+ if (storage.at(cnt).startsWith(QString("%1\n").arg(name)))
+ return index(cnt, 0);
+ }
+
+ return QModelIndex();
+}
+
+QModelIndex FilterListModel::findByExpression(QString expression)
+{
+ if (expression.length() == 0)
+ return QModelIndex();
+
+ for (int cnt = 0; cnt < rowCount(); cnt++)
+ {
+ if (storage.at(cnt).endsWith(QString("\n%1").arg(expression)))
+ return index(cnt, 0);
+ }
+
+ return QModelIndex();
+}
+
+void FilterListModel::removeFilter(QModelIndex idx)
+{
+ if (! idx.isValid() || idx.row() >= rowCount())
+ return;
+
+ beginRemoveRows(QModelIndex(), idx.row(), idx.row());
+ storage.removeAt(idx.row());
+ endRemoveRows();
+}
+
+void FilterListModel::saveList()
+{
+ QString filename = (type_ == FilterListModel::Capture) ? CFILTER_FILE_NAME : DFILTER_FILE_NAME;
+
+ filename = QString("%1%2%3").arg(ProfileModel::activeProfilePath()).arg("/").arg(filename);
+ QFile file(filename);
+
+ if (! file.open(QIODevice::WriteOnly | QIODevice::Text))
+ return;
+
+ QTextStream out(&file);
+ for (int row = 0; row < rowCount(); row++)
+ {
+ QString line = QString("\"%1\"").arg(index(row, ColumnName).data().toString().trimmed());
+ line.append(QString(" %1").arg(index(row, ColumnExpression).data().toString()));
+
+#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)
+ out << line << Qt::endl;
+#else
+ out << line << endl;
+#endif
+ }
+
+ file.close();
+}
+
+Qt::DropActions FilterListModel::supportedDropActions() const
+{
+ return Qt::MoveAction;
+}
+
+QStringList FilterListModel::mimeTypes() const
+{
+ return QStringList() << WiresharkMimeData::FilterListMimeType;
+}
+
+QMimeData *FilterListModel::mimeData(const QModelIndexList &indexes) const
+{
+ QMimeData *mimeData = new QMimeData();
+ QStringList rows;
+
+ foreach (const QModelIndex &index, indexes)
+ {
+ if (! rows.contains(QString::number(index.row())))
+ rows << QString::number(index.row());
+ }
+
+ mimeData->setData(WiresharkMimeData::FilterListMimeType, rows.join(",").toUtf8());
+ return mimeData;
+}
+
+bool FilterListModel::dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int /* column */, const QModelIndex & parent)
+{
+ if (action != Qt::MoveAction)
+ return true;
+
+ if (! data->hasFormat(WiresharkMimeData::FilterListMimeType))
+ return true;
+
+ QStringList rows = QString(data->data(WiresharkMimeData::FilterListMimeType)).split(",");
+
+ int insertRow = parent.isValid() ? parent.row() : row;
+
+ /* for now, only single rows can be selected */
+ if (rows.count() > 0)
+ {
+ bool ok = false;
+ int strow = rows[0].toInt(&ok);
+ if (ok)
+ {
+ int storeTo = insertRow;
+ if (storeTo < 0 || storeTo >= storage.count())
+ {
+ storeTo = static_cast<int>(storage.count()) - 1;
+ }
+
+ beginResetModel();
+ storage.move(strow, storeTo);
+ endResetModel();
+ }
+ }
+
+ return true;
+}
diff --git a/ui/qt/models/filter_list_model.h b/ui/qt/models/filter_list_model.h
new file mode 100644
index 00000000..4f528d2f
--- /dev/null
+++ b/ui/qt/models/filter_list_model.h
@@ -0,0 +1,71 @@
+/** @file
+ *
+ * Model for all filter types
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef FILTER_LIST_MODEL_h
+#define FILTER_LIST_MODEL_h
+
+#include <config.h>
+
+#include <QAbstractListModel>
+#include <QList>
+#include <QStringList>
+
+class FilterListModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+public:
+ enum FilterListType {
+ Display,
+ Capture
+ };
+
+ explicit FilterListModel(FilterListType type = FilterListModel::Display, QObject * parent = Q_NULLPTR);
+ explicit FilterListModel(QObject * parent = Q_NULLPTR);
+
+ enum {
+ ColumnName,
+ ColumnExpression
+ };
+
+ void setFilterType(FilterListModel::FilterListType type);
+ FilterListModel::FilterListType filterType() const;
+
+ QModelIndex findByName(QString name);
+ QModelIndex findByExpression(QString expression);
+
+ QModelIndex addFilter(QString name, QString expression);
+ void removeFilter(QModelIndex idx);
+
+ void saveList();
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role) override;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
+
+ virtual Qt::DropActions supportedDropActions() const override;
+ virtual QStringList mimeTypes() const override;
+ virtual QMimeData *mimeData(const QModelIndexList &indexes) const override;
+ virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) override;
+
+private:
+
+ FilterListModel::FilterListType type_;
+
+ QStringList storage;
+
+ void reload();
+};
+
+#endif // FILTER_LIST_MODEL_h
diff --git a/ui/qt/models/info_proxy_model.cpp b/ui/qt/models/info_proxy_model.cpp
new file mode 100644
index 00000000..7b256b9d
--- /dev/null
+++ b/ui/qt/models/info_proxy_model.cpp
@@ -0,0 +1,119 @@
+/* info_proxy_model.cpp
+ * Proxy model for displaying an info text at the end of any QAbstractListModel
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <config.h>
+
+#include <ui/qt/models/info_proxy_model.h>
+
+#include <QFont>
+
+InfoProxyModel::InfoProxyModel(QObject * parent)
+ : QIdentityProxyModel(parent),
+ column_(-1)
+{
+}
+
+InfoProxyModel::~InfoProxyModel()
+{
+ infos_.clear();
+}
+
+void InfoProxyModel::appendInfo(QString info)
+{
+ if (! infos_.contains(info))
+ infos_ << info;
+}
+
+void InfoProxyModel::clearInfos()
+{
+ infos_.clear();
+}
+
+int InfoProxyModel::rowCount(const QModelIndex &parent) const
+{
+ return static_cast<int>(sourceModel()->rowCount(parent) + infos_.count());
+}
+
+QVariant InfoProxyModel::data (const QModelIndex &index, int role) const
+{
+ if (! index.isValid())
+ return QVariant();
+
+ if (index.row() < sourceModel()->rowCount())
+ return sourceModel()->data(mapToSource(index), role);
+
+ int ifIdx = index.row() - sourceModel()->rowCount();
+ if (index.column() != column_ || ifIdx < 0 || ifIdx >= infos_.count())
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ return infos_.at(ifIdx);
+ break;
+ case Qt::FontRole:
+ QFont font = QIdentityProxyModel::data(index, Qt::FontRole).value<QFont>();
+ font.setItalic(true);
+ return font;
+ }
+
+ return QIdentityProxyModel::data(index, role);
+}
+
+Qt::ItemFlags InfoProxyModel::flags(const QModelIndex &index) const
+{
+ if (index.row() < sourceModel()->rowCount())
+ return sourceModel()->flags(mapToSource(index));
+
+ return Qt::ItemFlags();
+}
+
+QModelIndex InfoProxyModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (row >= sourceModel()->rowCount() && row < rowCount())
+ return createIndex(row, column);
+
+ return QIdentityProxyModel::index(row, column, parent);
+}
+
+QModelIndex InfoProxyModel::mapToSource(const QModelIndex &proxyIndex) const
+{
+ if (! proxyIndex.isValid())
+ return QModelIndex();
+
+ if (proxyIndex.row() >= sourceModel()->rowCount())
+ return QModelIndex();
+
+ return QIdentityProxyModel::mapToSource(proxyIndex);
+}
+
+QModelIndex InfoProxyModel::mapFromSource(const QModelIndex &fromIndex) const
+{
+ return QIdentityProxyModel::mapFromSource(fromIndex);
+}
+
+void InfoProxyModel::setColumn(int column)
+{
+ int old_column = column_;
+ column_ = column;
+
+ QVector<int> roles;
+ roles << Qt::DisplayRole;
+
+ if (old_column >= 0) {
+ //Notify old column has changed
+ emit dataChanged(index(0, old_column), index(rowCount(), old_column), roles);
+ }
+
+ if (column_ >= 0) {
+ //Notify new column has changed
+ emit dataChanged(index(0, column_), index(rowCount(), column_), roles);
+ }
+}
diff --git a/ui/qt/models/info_proxy_model.h b/ui/qt/models/info_proxy_model.h
new file mode 100644
index 00000000..f9775b4d
--- /dev/null
+++ b/ui/qt/models/info_proxy_model.h
@@ -0,0 +1,47 @@
+/** @file
+ *
+ * Proxy model for displaying an info text at the end of any QAbstractListModel
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef INFO_PROXY_MODEL_H
+#define INFO_PROXY_MODEL_H
+
+#include <config.h>
+
+#include <QStringList>
+#include <QIdentityProxyModel>
+
+class InfoProxyModel : public QIdentityProxyModel
+{
+public:
+ explicit InfoProxyModel(QObject * parent = 0);
+ ~InfoProxyModel();
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
+ virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+
+ virtual QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ virtual QModelIndex mapFromSource(const QModelIndex &fromIndex) const;
+
+ void appendInfo(QString info);
+ void clearInfos();
+
+ void setColumn(int column);
+
+private:
+
+ int column_;
+
+ QStringList infos_;
+};
+
+#endif // INFO_PROXY_MODEL_H
diff --git a/ui/qt/models/interface_sort_filter_model.cpp b/ui/qt/models/interface_sort_filter_model.cpp
new file mode 100644
index 00000000..9d9c5dc8
--- /dev/null
+++ b/ui/qt/models/interface_sort_filter_model.cpp
@@ -0,0 +1,396 @@
+/* interface_sort_filter_model.cpp
+ * Proxy model for the display of interface data for the interface tree
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/interface_tree_model.h>
+#include <ui/qt/models/interface_tree_cache_model.h>
+#include <ui/qt/models/interface_sort_filter_model.h>
+
+#include <glib.h>
+
+#include <epan/prefs.h>
+#include <ui/preference_utils.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+
+#include "main_application.h"
+
+#include <QAbstractItemModel>
+
+InterfaceSortFilterModel::InterfaceSortFilterModel(QObject *parent) :
+ QSortFilterProxyModel(parent)
+{
+ resetAllFilter();
+}
+
+void InterfaceSortFilterModel::resetAllFilter()
+{
+ _filterHidden = true;
+ _filterTypes = true;
+ _invertTypeFilter = false;
+ _storeOnChange = false;
+ _sortByActivity = false;
+#ifdef HAVE_PCAP_REMOTE
+ _remoteDisplay = true;
+#endif
+
+ /* Adding all columns, to have a default setting */
+ for (int col = 0; col < IFTREE_COL_MAX; col++)
+ _columns.append((InterfaceTreeColumns)col);
+
+ invalidateFilter();
+ invalidate();
+}
+
+void InterfaceSortFilterModel::setStoreOnChange(bool storeOnChange)
+{
+ _storeOnChange = storeOnChange;
+ if (storeOnChange)
+ {
+ connect(mainApp, &MainApplication::preferencesChanged, this, &InterfaceSortFilterModel::resetPreferenceData);
+ resetPreferenceData();
+ }
+}
+
+void InterfaceSortFilterModel::setFilterHidden(bool filter)
+{
+ _filterHidden = filter;
+
+ invalidate();
+}
+
+
+void InterfaceSortFilterModel::setSortByActivity(bool sort)
+{
+ _sortByActivity = sort;
+ invalidate();
+}
+
+bool InterfaceSortFilterModel::sortByActivity() const
+{
+ return _sortByActivity;
+}
+
+#ifdef HAVE_PCAP_REMOTE
+void InterfaceSortFilterModel::setRemoteDisplay(bool remoteDisplay)
+{
+ _remoteDisplay = remoteDisplay;
+
+ invalidate();
+}
+
+bool InterfaceSortFilterModel::remoteDisplay()
+{
+ return _remoteDisplay;
+}
+
+void InterfaceSortFilterModel::toggleRemoteDisplay()
+{
+ _remoteDisplay = ! _remoteDisplay;
+
+ if (_storeOnChange)
+ {
+ prefs.gui_interfaces_remote_display = ! _remoteDisplay;
+
+ prefs_main_write();
+ }
+
+ invalidateFilter();
+ invalidate();
+}
+
+bool InterfaceSortFilterModel::remoteInterfacesExist()
+{
+ bool exist = false;
+
+ if (sourceModel()->rowCount() == 0)
+ return exist;
+
+ for (int idx = 0; idx < sourceModel()->rowCount() && ! exist; idx++)
+ exist = ((InterfaceTreeModel *)sourceModel())->isRemote(idx);
+
+ return exist;
+}
+#endif
+
+void InterfaceSortFilterModel::setFilterByType(bool filter, bool invert)
+{
+ _filterTypes = filter;
+ _invertTypeFilter = invert;
+
+ invalidate();
+}
+
+void InterfaceSortFilterModel::resetPreferenceData()
+{
+ displayHiddenTypes.clear();
+ QString stored_prefs(prefs.gui_interfaces_hide_types);
+ if (stored_prefs.length() > 0)
+ {
+ QStringList ifTypesStored = stored_prefs.split(',');
+ QStringList::const_iterator it = ifTypesStored.constBegin();
+ while (it != ifTypesStored.constEnd())
+ {
+ int i_val = (*it).toInt();
+ if (! displayHiddenTypes.contains(i_val))
+ displayHiddenTypes.append(i_val);
+ ++it;
+ }
+ }
+
+#if 0
+ // Disabled until bug 13354 is fixed
+ _filterHidden = ! prefs.gui_interfaces_show_hidden;
+#endif
+#ifdef HAVE_PCAP_REMOTE
+ _remoteDisplay = prefs.gui_interfaces_remote_display;
+#endif
+
+ invalidate();
+}
+
+bool InterfaceSortFilterModel::filterHidden() const
+{
+ return _filterHidden;
+}
+
+void InterfaceSortFilterModel::toggleFilterHidden()
+{
+ _filterHidden = ! _filterHidden;
+
+ if (_storeOnChange)
+ {
+ prefs.gui_interfaces_show_hidden = ! _filterHidden;
+
+ prefs_main_write();
+ }
+
+ invalidateFilter();
+ invalidate();
+}
+
+bool InterfaceSortFilterModel::filterByType() const
+{
+ return _filterTypes;
+}
+
+int InterfaceSortFilterModel::interfacesHidden()
+{
+#ifdef HAVE_LIBPCAP
+ if (! global_capture_opts.all_ifaces)
+ return 0;
+#endif
+
+ return sourceModel()->rowCount() - rowCount();
+}
+
+QList<int> InterfaceSortFilterModel::typesDisplayed()
+{
+ QList<int> shownTypes;
+
+ if (sourceModel()->rowCount() == 0)
+ return shownTypes;
+
+ for (int idx = 0; idx < sourceModel()->rowCount(); idx++)
+ {
+ int type = ((InterfaceTreeModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_TYPE).toInt();
+ bool hidden = ((InterfaceTreeModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_HIDDEN).toBool();
+
+ if (! hidden)
+ {
+ if (! shownTypes.contains(type))
+ shownTypes.append(type);
+ }
+ }
+
+ return shownTypes;
+}
+
+void InterfaceSortFilterModel::setInterfaceTypeVisible(int ifType, bool visible)
+{
+ if (visible && displayHiddenTypes.contains(ifType))
+ displayHiddenTypes.removeAll(ifType);
+ else if (! visible && ! displayHiddenTypes.contains(ifType))
+ displayHiddenTypes.append(ifType);
+ else
+ /* Nothing should have changed */
+ return;
+
+ if (_storeOnChange)
+ {
+ QString new_pref;
+ QList<int>::const_iterator it = displayHiddenTypes.constBegin();
+ while (it != displayHiddenTypes.constEnd())
+ {
+ new_pref.append(QString("%1,").arg(*it));
+ ++it;
+ }
+ if (new_pref.length() > 0)
+ new_pref = new_pref.left(new_pref.length() - 1);
+
+ prefs.gui_interfaces_hide_types = qstring_strdup(new_pref);
+
+ prefs_main_write();
+ }
+
+ invalidateFilter();
+ invalidate();
+}
+
+void InterfaceSortFilterModel::toggleTypeVisibility(int ifType)
+{
+ bool checked = isInterfaceTypeShown(ifType);
+
+ setInterfaceTypeVisible(ifType, checked ? false : true);
+}
+
+bool InterfaceSortFilterModel::isInterfaceTypeShown(int ifType) const
+{
+ bool result = false;
+
+ if (displayHiddenTypes.size() == 0)
+ result = true;
+ else if (! displayHiddenTypes.contains(ifType))
+ result = true;
+
+ return ((_invertTypeFilter && ! result) || (! _invertTypeFilter && result) );
+}
+
+bool InterfaceSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex & sourceParent) const
+{
+ QModelIndex realIndex = sourceModel()->index(sourceRow, 0, sourceParent);
+
+ if (! realIndex.isValid())
+ return false;
+
+#ifdef HAVE_LIBPCAP
+ int idx = realIndex.row();
+
+ /* No data loaded, we do not display anything */
+ if (sourceModel()->rowCount() == 0)
+ return false;
+
+ int type = -1;
+ bool hidden = false;
+
+ if (dynamic_cast<InterfaceTreeCacheModel*>(sourceModel()) != 0)
+ {
+ type = ((InterfaceTreeCacheModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_TYPE).toInt();
+ hidden = ((InterfaceTreeCacheModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::UserRole).toBool();
+ }
+ else if (dynamic_cast<InterfaceTreeModel*>(sourceModel()) != 0)
+ {
+ type = ((InterfaceTreeModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_TYPE).toInt();
+ hidden = ((InterfaceTreeModel *)sourceModel())->getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::UserRole).toBool();
+ }
+ else
+ return false;
+
+ if (hidden && _filterHidden)
+ return false;
+
+ if (_filterTypes && ! isInterfaceTypeShown(type))
+ {
+#ifdef HAVE_PCAP_REMOTE
+ /* Remote interfaces have the if type IF_WIRED, therefore would be filtered, if not explicitly checked here */
+ if (type != IF_WIRED || ! ((InterfaceTreeModel *)sourceModel())->isRemote(idx))
+#endif
+ return false;
+ }
+
+#ifdef HAVE_PCAP_REMOTE
+ if (((InterfaceTreeModel *)sourceModel())->isRemote(idx))
+ {
+ if (! _remoteDisplay)
+ return false;
+ }
+#endif
+
+#endif
+
+ return true;
+}
+
+bool InterfaceSortFilterModel::filterAcceptsColumn(int sourceColumn, const QModelIndex & sourceParent) const
+{
+ QModelIndex realIndex = sourceModel()->index(0, sourceColumn, sourceParent);
+
+ if (! realIndex.isValid())
+ return false;
+
+ if (! _columns.contains((InterfaceTreeColumns)sourceColumn))
+ return false;
+
+ return true;
+}
+
+void InterfaceSortFilterModel::setColumns(QList<InterfaceTreeColumns> columns)
+{
+ _columns.clear();
+ _columns.append(columns);
+}
+
+int InterfaceSortFilterModel::mapSourceToColumn(InterfaceTreeColumns mdlIndex)
+{
+ if (! _columns.contains(mdlIndex))
+ return -1;
+
+ return static_cast<int>(_columns.indexOf(mdlIndex, 0));
+}
+
+QModelIndex InterfaceSortFilterModel::mapToSource(const QModelIndex &proxyIndex) const
+{
+ if (! proxyIndex.isValid())
+ return QModelIndex();
+
+ if (! sourceModel())
+ return QModelIndex();
+
+ QModelIndex baseIndex = QSortFilterProxyModel::mapToSource(proxyIndex);
+ QModelIndex newIndex = sourceModel()->index(baseIndex.row(), _columns.at(proxyIndex.column()));
+
+ return newIndex;
+}
+
+QModelIndex InterfaceSortFilterModel::mapFromSource(const QModelIndex &sourceIndex) const
+{
+ if (! sourceIndex.isValid())
+ return QModelIndex();
+ else if (! _columns.contains((InterfaceTreeColumns) sourceIndex.column()) )
+ return QModelIndex();
+
+ QModelIndex newIndex = QSortFilterProxyModel::mapFromSource(sourceIndex);
+
+ return index(newIndex.row(), static_cast<int>(_columns.indexOf((InterfaceTreeColumns) sourceIndex.column())));
+}
+
+QString InterfaceSortFilterModel::interfaceError()
+{
+ QString result;
+
+ InterfaceTreeModel * sourceModel = dynamic_cast<InterfaceTreeModel *>(this->sourceModel());
+ if (sourceModel != NULL)
+ result = sourceModel->interfaceError();
+
+ if (result.size() == 0 && rowCount() == 0)
+ result = tr("No interfaces to be displayed. %1 interfaces hidden.").arg(interfacesHidden());
+
+ return result;
+}
+
+bool InterfaceSortFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
+{
+ bool leftActive = source_left.sibling(source_left.row(), InterfaceTreeColumns::IFTREE_COL_ACTIVE).data(Qt::UserRole).toBool();
+ bool rightActive = source_right.sibling(source_right.row(), InterfaceTreeColumns::IFTREE_COL_ACTIVE).data(Qt::UserRole).toBool();
+
+ if (_sortByActivity && rightActive && ! leftActive)
+ return true;
+
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+}
+
diff --git a/ui/qt/models/interface_sort_filter_model.h b/ui/qt/models/interface_sort_filter_model.h
new file mode 100644
index 00000000..5bf69d9d
--- /dev/null
+++ b/ui/qt/models/interface_sort_filter_model.h
@@ -0,0 +1,87 @@
+/** @file
+ *
+ * Proxy model for the display of interface data for the interface tree
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef INTERFACE_SORT_FILTER_MODEL_H
+#define INTERFACE_SORT_FILTER_MODEL_H
+
+#include <config.h>
+
+#include <ui/qt/models/interface_tree_model.h>
+
+#include <glib.h>
+
+#include <QSortFilterProxyModel>
+
+class InterfaceSortFilterModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+public:
+ InterfaceSortFilterModel(QObject *parent);
+
+ void setStoreOnChange(bool storeOnChange);
+ void resetAllFilter();
+
+ void setFilterHidden(bool filter);
+ bool filterHidden() const;
+ int interfacesHidden();
+ void toggleFilterHidden();
+
+ void setSortByActivity(bool sort);
+ bool sortByActivity() const;
+
+#ifdef HAVE_PCAP_REMOTE
+ void setRemoteDisplay(bool remoteDisplay);
+ bool remoteDisplay();
+ void toggleRemoteDisplay();
+ bool remoteInterfacesExist();
+#endif
+
+ void setInterfaceTypeVisible(int ifType, bool visible);
+ bool isInterfaceTypeShown(int ifType) const;
+ void setFilterByType(bool filter, bool invert = false);
+ bool filterByType() const;
+ void toggleTypeVisibility(int ifType);
+
+ QList<int> typesDisplayed();
+
+ void setColumns(QList<InterfaceTreeColumns> columns);
+ int mapSourceToColumn(InterfaceTreeColumns mdlIndex);
+
+ QModelIndex mapToSource(const QModelIndex &proxyIndex) const;
+ QModelIndex mapFromSource(const QModelIndex &sourceIndex) const;
+
+ QString interfaceError();
+
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const;
+ bool filterAcceptsColumn(int source_column, const QModelIndex & source_parent) const;
+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
+
+private:
+ bool _filterHidden;
+ bool _filterTypes;
+ bool _invertTypeFilter;
+ bool _storeOnChange;
+ bool _sortByActivity;
+
+#ifdef HAVE_PCAP_REMOTE
+ bool _remoteDisplay;
+#endif
+
+ QList<int> displayHiddenTypes;
+
+ QList<InterfaceTreeColumns> _columns;
+
+private slots:
+ void resetPreferenceData();
+};
+
+#endif // INTERFACE_SORT_FILTER_MODEL_H
diff --git a/ui/qt/models/interface_tree_cache_model.cpp b/ui/qt/models/interface_tree_cache_model.cpp
new file mode 100644
index 00000000..71eda509
--- /dev/null
+++ b/ui/qt/models/interface_tree_cache_model.cpp
@@ -0,0 +1,584 @@
+/* interface_tree_cache_model.cpp
+ * Model caching interface changes before sending them to global storage
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+
+#include <ui/qt/models/interface_tree_cache_model.h>
+
+#include "epan/prefs.h"
+
+#include <ui/qt/utils/qt_ui_utils.h>
+#include "ui/capture_globals.h"
+#include "wsutil/utf8_entities.h"
+
+#include "wiretap/wtap.h"
+
+#include "main_application.h"
+
+#include <QIdentityProxyModel>
+
+InterfaceTreeCacheModel::InterfaceTreeCacheModel(QObject *parent) :
+ QIdentityProxyModel(parent)
+{
+ /* ATTENTION: This cache model is not intended to be used with anything
+ * else then InterfaceTreeModel, and will break with anything else
+ * leading to unintended results. */
+ sourceModel = new InterfaceTreeModel(parent);
+
+ QIdentityProxyModel::setSourceModel(sourceModel);
+ storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>();
+
+ checkableColumns << IFTREE_COL_HIDDEN << IFTREE_COL_PROMISCUOUSMODE;
+#ifdef HAVE_PCAP_CREATE
+ checkableColumns << IFTREE_COL_MONITOR_MODE;
+#endif
+
+ editableColumns << IFTREE_COL_COMMENT << IFTREE_COL_SNAPLEN << IFTREE_COL_PIPE_PATH;
+
+#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
+ editableColumns << IFTREE_COL_BUFFERLEN;
+#endif
+}
+
+InterfaceTreeCacheModel::~InterfaceTreeCacheModel()
+{
+#ifdef HAVE_LIBPCAP
+ /* This list should only exist, if the dialog is closed, without calling save first */
+ newDevices.clear();
+#endif
+
+ delete storage;
+ delete sourceModel;
+}
+
+QVariant InterfaceTreeCacheModel::getColumnContent(int idx, int col, int role)
+{
+ return InterfaceTreeCacheModel::data(index(idx, col), role);
+}
+
+#ifdef HAVE_LIBPCAP
+void InterfaceTreeCacheModel::reset(int row)
+{
+ if (row < 0)
+ {
+ delete storage;
+ storage = new QMap<int, QMap<InterfaceTreeColumns, QVariant> *>();
+ }
+ else
+ {
+ if (storage->count() > row)
+ storage->remove(storage->keys().at(row));
+ }
+}
+
+void InterfaceTreeCacheModel::saveNewDevices()
+{
+ QList<interface_t>::const_iterator it = newDevices.constBegin();
+ /* idx is used for iterating only over the indices of the new devices. As all new
+ * devices are stored with an index higher then sourceModel->rowCount(), we start
+ * only with those storage indices.
+ * it is just the iterator over the new devices. A new device must not necessarily
+ * have storage, which will lead to that device not being stored in global_capture_opts */
+ for (int idx = sourceModel->rowCount(); it != newDevices.constEnd(); ++it, idx++)
+ {
+ interface_t *device = const_cast<interface_t *>(&(*it));
+ bool useDevice = false;
+
+ QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0);
+ /* When devices are being added, they are added using generic values. So only devices
+ * whose data have been changed should be used from here on out. */
+ if (dataField != 0)
+ {
+ if (device->if_info.type != IF_PIPE)
+ {
+ continue;
+ }
+
+ if (device->if_info.type == IF_PIPE)
+ {
+ QVariant saveValue = dataField->value(IFTREE_COL_PIPE_PATH);
+ if (saveValue.isValid())
+ {
+ g_free(device->if_info.name);
+ device->if_info.name = qstring_strdup(saveValue.toString());
+
+ g_free(device->name);
+ device->name = qstring_strdup(saveValue.toString());
+
+ g_free(device->display_name);
+ device->display_name = qstring_strdup(saveValue.toString());
+ useDevice = true;
+ }
+ }
+
+ if (useDevice)
+ g_array_append_val(global_capture_opts.all_ifaces, *device);
+
+ }
+
+ /* All entries of this new devices have been considered */
+ storage->remove(idx);
+ delete dataField;
+ }
+
+ newDevices.clear();
+}
+
+void InterfaceTreeCacheModel::save()
+{
+ if (storage->count() == 0)
+ return;
+
+ QMap<char**, QStringList> prefStorage;
+
+ /* No devices are hidden until checking "Show" state */
+ prefStorage[&prefs.capture_devices_hide] = QStringList();
+
+ /* Storing new devices first including their changed values */
+ saveNewDevices();
+
+
+ for (unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++)
+ {
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
+
+ if (! device->name)
+ continue;
+
+ /* Try to load a saved value row for this index */
+ QMap<InterfaceTreeColumns, QVariant> * dataField = storage->value(idx, 0);
+
+ /* Handle the storing of values for this device here */
+ if (dataField)
+ {
+ QMap<InterfaceTreeColumns, QVariant>::const_iterator it = dataField->constBegin();
+ while (it != dataField->constEnd())
+ {
+ InterfaceTreeColumns col = it.key();
+ QVariant saveValue = it.value();
+
+ /* Setting the field values for each individual saved value cannot be generic, as the
+ * struct cannot be accessed in a generic way. Therefore below, each individually changed
+ * value has to be handled separately. Comments are stored only in the preference file
+ * and applied to the data name during loading. Therefore comments are not handled here */
+
+ if (col == IFTREE_COL_HIDDEN)
+ {
+ device->hidden = saveValue.toBool();
+ }
+ else if (device->if_info.type == IF_EXTCAP)
+ {
+ /* extcap interfaces do not have the following columns.
+ * ATTENTION: all generic columns must be added, BEFORE this
+ * if-clause, or they will be ignored for extcap interfaces */
+ }
+ else if (col == IFTREE_COL_PROMISCUOUSMODE)
+ {
+ device->pmode = saveValue.toBool();
+ }
+#ifdef HAVE_PCAP_CREATE
+ else if (col == IFTREE_COL_MONITOR_MODE)
+ {
+ device->monitor_mode_enabled = saveValue.toBool();
+ }
+#endif
+ else if (col == IFTREE_COL_SNAPLEN)
+ {
+ int iVal = saveValue.toInt();
+ if (iVal != WTAP_MAX_PACKET_SIZE_STANDARD)
+ {
+ device->has_snaplen = true;
+ device->snaplen = iVal;
+ }
+ else
+ {
+ device->has_snaplen = false;
+ device->snaplen = WTAP_MAX_PACKET_SIZE_STANDARD;
+ }
+ }
+#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
+ else if (col == IFTREE_COL_BUFFERLEN)
+ {
+ device->buffer = saveValue.toInt();
+ }
+#endif
+ ++it;
+ }
+ }
+
+ QVariant content = getColumnContent(idx, IFTREE_COL_HIDDEN, Qt::CheckStateRole);
+ if (content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Unchecked)
+ prefStorage[&prefs.capture_devices_hide] << QString(device->name);
+
+ content = getColumnContent(idx, IFTREE_COL_COMMENT);
+ if (content.isValid() && content.toString().size() > 0)
+ prefStorage[&prefs.capture_devices_descr] << QString("%1(%2)").arg(device->name).arg(content.toString());
+
+ bool allowExtendedColumns = true;
+
+ if (device->if_info.type == IF_EXTCAP)
+ allowExtendedColumns = false;
+
+ if (allowExtendedColumns)
+ {
+ content = getColumnContent(idx, IFTREE_COL_PROMISCUOUSMODE, Qt::CheckStateRole);
+ if (content.isValid())
+ {
+ bool value = static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked;
+ prefStorage[&prefs.capture_devices_pmode] << QString("%1(%2)").arg(device->name).arg(value ? 1 : 0);
+ }
+
+#ifdef HAVE_PCAP_CREATE
+ content = getColumnContent(idx, IFTREE_COL_MONITOR_MODE, Qt::CheckStateRole);
+ if (content.isValid() && static_cast<Qt::CheckState>(content.toInt()) == Qt::Checked)
+ prefStorage[&prefs.capture_devices_monitor_mode] << QString(device->name);
+#endif
+
+ content = getColumnContent(idx, IFTREE_COL_SNAPLEN);
+ if (content.isValid())
+ {
+ int value = content.toInt();
+ prefStorage[&prefs.capture_devices_snaplen] <<
+ QString("%1:%2(%3)").arg(device->name).
+ arg(device->has_snaplen ? 1 : 0).
+ arg(device->has_snaplen ? value : WTAP_MAX_PACKET_SIZE_STANDARD);
+ }
+
+#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
+ content = getColumnContent(idx, IFTREE_COL_BUFFERLEN);
+ if (content.isValid())
+ {
+ int value = content.toInt();
+ if (value != -1)
+ {
+ prefStorage[&prefs.capture_devices_buffersize] <<
+ QString("%1(%2)").arg(device->name).
+ arg(value);
+ }
+ }
+#endif
+ }
+ }
+
+ QMap<char**, QStringList>::const_iterator it = prefStorage.constBegin();
+ while (it != prefStorage.constEnd())
+ {
+ char ** key = it.key();
+
+ g_free(*key);
+ *key = qstring_strdup(it.value().join(","));
+
+ ++it;
+ }
+
+ mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged);
+}
+#endif
+
+int InterfaceTreeCacheModel::rowCount(const QModelIndex & parent) const
+{
+ int totalCount = sourceModel->rowCount(parent);
+#ifdef HAVE_LIBPCAP
+ totalCount += newDevices.size();
+#endif
+ return totalCount;
+}
+
+bool InterfaceTreeCacheModel::changeIsAllowed(InterfaceTreeColumns col) const
+{
+ if (editableColumns.contains(col) || checkableColumns.contains(col))
+ return true;
+ return false;
+}
+
+#ifdef HAVE_LIBPCAP
+const interface_t * InterfaceTreeCacheModel::lookup(const QModelIndex &index) const
+{
+ const interface_t * result = 0;
+
+ if (! index.isValid() || ! global_capture_opts.all_ifaces)
+ return result;
+
+ int idx = index.row();
+
+ if ((unsigned int) idx >= global_capture_opts.all_ifaces->len)
+ {
+ idx = idx - global_capture_opts.all_ifaces->len;
+ if (idx < newDevices.size())
+ result = &newDevices[idx];
+ }
+ else
+ {
+ result = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
+ }
+
+ return result;
+}
+#endif
+
+/* This checks if the column can be edited for the given index. This differs from
+ * isAvailableField in such a way, that it is only used in flags and not any
+ * other method.*/
+bool InterfaceTreeCacheModel::isAllowedToBeEdited(const QModelIndex &index) const
+{
+#ifndef HAVE_LIBPCAP
+ Q_UNUSED(index);
+#else
+ const interface_t * device = lookup(index);
+ if (device == 0)
+ return false;
+
+ InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
+ if (device->if_info.type == IF_EXTCAP)
+ {
+ /* extcap interfaces do not have those settings */
+ if (col == IFTREE_COL_PROMISCUOUSMODE || col == IFTREE_COL_SNAPLEN)
+ return false;
+#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
+ if (col == IFTREE_COL_BUFFERLEN)
+ return false;
+#endif
+ }
+#endif
+ return true;
+}
+
+// Whether this field is available for modification and display.
+bool InterfaceTreeCacheModel::isAvailableField(const QModelIndex &index) const
+{
+#ifndef HAVE_LIBPCAP
+ Q_UNUSED(index);
+#else
+ const interface_t * device = lookup(index);
+
+ if (device == 0)
+ return false;
+
+ InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
+ if (col == IFTREE_COL_HIDDEN)
+ {
+ // Do not allow default capture interface to be hidden.
+ if (! g_strcmp0(prefs.capture_device, device->display_name))
+ return false;
+ }
+#endif
+
+ return true;
+}
+
+Qt::ItemFlags InterfaceTreeCacheModel::flags(const QModelIndex &index) const
+{
+ if (! index.isValid())
+ return Qt::ItemFlags();
+
+ Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable;
+
+ InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
+
+ if (changeIsAllowed(col) && isAvailableField(index) && isAllowedToBeEdited(index))
+ {
+ if (checkableColumns.contains(col))
+ {
+ flags |= Qt::ItemIsUserCheckable;
+ }
+ else
+ {
+ flags |= Qt::ItemIsEditable;
+ }
+ }
+
+ return flags;
+}
+
+bool InterfaceTreeCacheModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (! index.isValid())
+ return false;
+
+ if (! isAvailableField(index))
+ return false;
+
+ int row = index.row();
+ InterfaceTreeColumns col = (InterfaceTreeColumns)index.column();
+
+ if (role == Qt::CheckStateRole || role == Qt::EditRole)
+ {
+ if (changeIsAllowed(col) )
+ {
+ QVariant saveValue = value;
+
+ QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
+ /* obtain the list of already stored changes for this row. If none exist
+ * create a new storage row for this entry */
+ if ((dataField = storage->value(row, 0)) == 0)
+ {
+ dataField = new QMap<InterfaceTreeColumns, QVariant>();
+ storage->insert(row, dataField);
+ }
+
+ dataField->insert(col, saveValue);
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+QVariant InterfaceTreeCacheModel::data(const QModelIndex &index, int role) const
+{
+ if (! index.isValid())
+ return QVariant();
+
+ int row = index.row();
+
+ InterfaceTreeColumns col = (InterfaceTreeColumns)index.column();
+
+ if (isAvailableField(index) && isAllowedToBeEdited(index))
+ {
+ if (((role == Qt::DisplayRole || role == Qt::EditRole) && editableColumns.contains(col)) ||
+ (role == Qt::CheckStateRole && checkableColumns.contains(col)) )
+ {
+ QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
+ if ((dataField = storage->value(row, 0)) != 0)
+ {
+ if (dataField->contains(col))
+ {
+ return dataField->value(col, QVariant());
+ }
+ }
+ }
+ }
+ else
+ {
+ if (role == Qt::CheckStateRole)
+ return QVariant();
+ else if (role == Qt::DisplayRole)
+ return QString(UTF8_EM_DASH);
+ }
+
+ if (row < sourceModel->rowCount())
+ {
+ return sourceModel->data(index, role);
+ }
+#ifdef HAVE_LIBPCAP
+ else
+ {
+ /* Handle all fields, which will have to be displayed for new devices. Only pipes
+ * are supported at the moment, so the information to be displayed is pretty limited.
+ * After saving, the devices are stored in global_capture_opts and no longer
+ * classify as new devices. */
+ const interface_t * device = lookup(index);
+
+ if (device != 0)
+ {
+ if (role == Qt::DisplayRole || role == Qt::EditRole)
+ {
+ if (col == IFTREE_COL_PIPE_PATH ||
+ col == IFTREE_COL_NAME ||
+ col == IFTREE_COL_DESCRIPTION)
+ {
+
+ QMap<InterfaceTreeColumns, QVariant> * dataField = 0;
+ if ((dataField = storage->value(row, 0)) != 0 &&
+ dataField->contains(IFTREE_COL_PIPE_PATH))
+ {
+ return dataField->value(IFTREE_COL_PIPE_PATH, QVariant());
+ }
+ else
+ return QString(device->name);
+ }
+ else if (col == IFTREE_COL_TYPE)
+ {
+ return QVariant::fromValue((int)device->if_info.type);
+ }
+ }
+ else if (role == Qt::CheckStateRole)
+ {
+ if (col == IFTREE_COL_HIDDEN)
+ {
+ // Do not allow default capture interface to be hidden.
+ if (! g_strcmp0(prefs.capture_device, device->display_name))
+ return QVariant();
+
+ /* Hidden is a de-selection, therefore inverted logic here */
+ return device->hidden ? Qt::Unchecked : Qt::Checked;
+ }
+ }
+ }
+ }
+#endif
+
+ return QVariant();
+}
+
+#ifdef HAVE_LIBPCAP
+QModelIndex InterfaceTreeCacheModel::index(int row, int column, const QModelIndex &parent) const
+{
+ if (row >= sourceModel->rowCount() && (row - sourceModel->rowCount()) < newDevices.count())
+ {
+ return createIndex(row, column, (void *)0);
+ }
+
+ return QIdentityProxyModel::index(row, column, parent);
+}
+
+void InterfaceTreeCacheModel::addDevice(const interface_t * newDevice)
+{
+ emit beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ newDevices << *newDevice;
+ emit endInsertRows();
+}
+
+void InterfaceTreeCacheModel::deleteDevice(const QModelIndex &index)
+{
+ if (! index.isValid())
+ return;
+
+ emit beginRemoveRows(QModelIndex(), index.row(), index.row());
+
+ int row = index.row();
+
+ /* device is in newDevices */
+ if (row >= sourceModel->rowCount())
+ {
+ int newDeviceIdx = row - sourceModel->rowCount();
+
+ newDevices.removeAt(newDeviceIdx);
+ if (storage->contains(index.row()))
+ storage->remove(index.row());
+
+ /* The storage array has to be resorted, if the index, that was removed
+ * had been in the middle of the array. Can't start at index.row(), as
+ * it may not be contained in storage
+ * We must iterate using a list, not an iterator, otherwise the change
+ * will fold on itself. */
+ QList<int> storageKeys = storage->keys();
+ for (int i = 0; i < storageKeys.size(); ++i)
+ {
+ int key = storageKeys.at(i);
+ if (key > index.row())
+ {
+ storage->insert(key - 1, storage->value(key));
+ storage->remove(key);
+ }
+ }
+
+ emit endRemoveRows();
+ }
+ else
+ {
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, row);
+ capture_opts_free_interface_t(device);
+ global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, row);
+ emit endRemoveRows();
+ mainApp->emitAppSignal(MainApplication::LocalInterfacesChanged);
+ }
+}
+#endif
diff --git a/ui/qt/models/interface_tree_cache_model.h b/ui/qt/models/interface_tree_cache_model.h
new file mode 100644
index 00000000..9b51c10b
--- /dev/null
+++ b/ui/qt/models/interface_tree_cache_model.h
@@ -0,0 +1,66 @@
+/** @file
+ *
+ * Model caching interface changes before sending them to global storage
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef INTERFACE_TREE_CACHE_MODEL_H_
+#define INTERFACE_TREE_CACHE_MODEL_H_
+
+#include <ui/qt/models/interface_tree_model.h>
+
+#include <QMap>
+#include <QAbstractItemModel>
+#include <QIdentityProxyModel>
+
+class InterfaceTreeCacheModel : public QIdentityProxyModel
+{
+public:
+ explicit InterfaceTreeCacheModel(QObject *parent);
+ ~InterfaceTreeCacheModel();
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+
+ QVariant getColumnContent(int idx, int col, int role = Qt::DisplayRole);
+
+#ifdef HAVE_LIBPCAP
+ QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+
+ void reset(int row);
+ void save();
+
+ void addDevice(const interface_t * newDevice);
+ void deleteDevice(const QModelIndex &index);
+#endif
+
+private:
+ InterfaceTreeModel * sourceModel;
+
+#ifdef HAVE_LIBPCAP
+ QList<interface_t> newDevices;
+
+ void saveNewDevices();
+#endif
+ QMap<int, QMap<InterfaceTreeColumns, QVariant> *> * storage;
+ QList<InterfaceTreeColumns> editableColumns;
+ QList<InterfaceTreeColumns> checkableColumns;
+
+#ifdef HAVE_LIBPCAP
+ const interface_t * lookup(const QModelIndex &index) const;
+#endif
+
+ bool changeIsAllowed(InterfaceTreeColumns col) const;
+ bool isAvailableField(const QModelIndex &index) const;
+ bool isAllowedToBeEdited(const QModelIndex &index) const;
+
+};
+#endif /* INTERFACE_TREE_CACHE_MODEL_H_ */
diff --git a/ui/qt/models/interface_tree_model.cpp b/ui/qt/models/interface_tree_model.cpp
new file mode 100644
index 00000000..547b9010
--- /dev/null
+++ b/ui/qt/models/interface_tree_model.cpp
@@ -0,0 +1,545 @@
+/* interface_tree_model.cpp
+ * Model for the interface data for display in the interface frame
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+
+#include <ui/qt/models/interface_tree_model.h>
+
+#ifdef HAVE_LIBPCAP
+#include "ui/capture.h"
+#include "capture/capture-pcap-util.h"
+#include "capture_opts.h"
+#include "ui/capture_ui_utils.h"
+#include "ui/capture_globals.h"
+#endif
+
+#include "wsutil/filesystem.h"
+
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/utils/stock_icon.h>
+#include "main_application.h"
+
+/* Needed for the meta type declaration of QList<int>* */
+#include <ui/qt/models/sparkline_delegate.h>
+
+#include "extcap.h"
+
+const QString InterfaceTreeModel::DefaultNumericValue = QObject::tr("default");
+
+/**
+ * This is the data model for interface trees. It implies, that the index within
+ * global_capture_opts.all_ifaces is identical to the row. This is always the case, even
+ * when interfaces are hidden by the proxy model. But for this to work, every access
+ * to the index from within the view, has to be filtered through the proxy model.
+ */
+InterfaceTreeModel::InterfaceTreeModel(QObject *parent) :
+ QAbstractTableModel(parent)
+#ifdef HAVE_LIBPCAP
+ ,stat_cache_(NULL)
+#endif
+{
+ connect(mainApp, &MainApplication::appInitialized, this, &InterfaceTreeModel::interfaceListChanged);
+ connect(mainApp, &MainApplication::localInterfaceListChanged, this, &InterfaceTreeModel::interfaceListChanged);
+}
+
+InterfaceTreeModel::~InterfaceTreeModel(void)
+{
+#ifdef HAVE_LIBPCAP
+ if (stat_cache_) {
+ capture_stat_stop(stat_cache_);
+ stat_cache_ = NULL;
+ }
+#endif // HAVE_LIBPCAP
+}
+
+QString InterfaceTreeModel::interfaceError()
+{
+#ifdef HAVE_LIBPCAP
+ //
+ // First, see if there was an error fetching the interfaces.
+ // If so, report it.
+ //
+ if (global_capture_opts.ifaces_err != 0)
+ {
+ return tr(global_capture_opts.ifaces_err_info);
+ }
+
+ //
+ // Otherwise, if there are no rows, there were no interfaces
+ // found.
+ //
+ if (rowCount() == 0)
+ {
+ return tr("No interfaces found.");
+ }
+
+ //
+ // No error. Return an empty string.
+ //
+ return "";
+#else
+ //
+ // We were built without pcap support, so we have no notion of
+ // local interfaces.
+ //
+ return tr("This version of Wireshark was built without packet capture support.");
+#endif
+}
+
+int InterfaceTreeModel::rowCount(const QModelIndex &) const
+{
+#ifdef HAVE_LIBPCAP
+ return (global_capture_opts.all_ifaces ? global_capture_opts.all_ifaces->len : 0);
+#else
+ /* Currently no interfaces available for libpcap-less builds */
+ return 0;
+#endif
+}
+
+int InterfaceTreeModel::columnCount(const QModelIndex &) const
+{
+ /* IFTREE_COL_MAX is not being displayed, it is the definition for the maximum numbers of columns */
+ return ((int) IFTREE_COL_MAX);
+}
+
+QVariant InterfaceTreeModel::data(const QModelIndex &index, int role) const
+{
+#ifdef HAVE_LIBPCAP
+ bool interfacesLoaded = true;
+ if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len == 0)
+ interfacesLoaded = false;
+
+ if (!index.isValid())
+ return QVariant();
+
+ int row = index.row();
+ InterfaceTreeColumns col = (InterfaceTreeColumns) index.column();
+
+ if (interfacesLoaded)
+ {
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, row);
+
+ /* Data for display in cell */
+ if (role == Qt::DisplayRole)
+ {
+ /* Only the name is being displayed */
+ if (col == IFTREE_COL_NAME)
+ {
+ return QString(device->name);
+ }
+ else if (col == IFTREE_COL_DESCRIPTION)
+ {
+ return QString(device->friendly_name);
+ }
+ else if (col == IFTREE_COL_DISPLAY_NAME)
+ {
+ return QString(device->display_name);
+ }
+ else if (col == IFTREE_COL_PIPE_PATH)
+ {
+ return QString(device->if_info.name);
+ }
+ else if (col == IFTREE_COL_CAPTURE_FILTER)
+ {
+ if (device->cfilter && strlen(device->cfilter) > 0)
+ return html_escape(QString(device->cfilter));
+ }
+ else if (col == IFTREE_COL_EXTCAP_PATH)
+ {
+ return QString(device->if_info.extcap);
+ }
+ else if (col == IFTREE_COL_SNAPLEN)
+ {
+ return device->has_snaplen ? QString::number(device->snaplen) : DefaultNumericValue;
+ }
+#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
+ else if (col == IFTREE_COL_BUFFERLEN)
+ {
+ return QString::number(device->buffer);
+ }
+#endif
+ else if (col == IFTREE_COL_TYPE)
+ {
+ return QVariant::fromValue((int)device->if_info.type);
+ }
+ else if (col == IFTREE_COL_COMMENT)
+ {
+ QString comment = gchar_free_to_qstring(capture_dev_user_descr_find(device->name));
+ if (comment.length() > 0)
+ return comment;
+ else
+ return QString(device->if_info.vendor_description);
+ }
+ else if (col == IFTREE_COL_DLT)
+ {
+ // XXX - this is duplicated in
+ // InterfaceTreeWidgetItem::updateInterfaceColumns;
+ // it should be done in common code somewhere.
+ QString linkname;
+ if (device->active_dlt == -1)
+ linkname = "Unknown";
+ else {
+ linkname = QObject::tr("DLT %1").arg(device->active_dlt);
+ for (GList *list = device->links; list != Q_NULLPTR; list = gxx_list_next(list)) {
+ link_row *linkr = gxx_list_data(link_row *, list);
+ if (linkr->dlt == device->active_dlt) {
+ linkname = linkr->name;
+ break;
+ }
+ }
+ }
+
+ return linkname;
+ }
+ else
+ {
+ /* Return empty string for every other DisplayRole */
+ return QVariant();
+ }
+ }
+ else if (role == Qt::CheckStateRole)
+ {
+ if (col == IFTREE_COL_HIDDEN)
+ {
+ /* Hidden is a de-selection, therefore inverted logic here */
+ return device->hidden ? Qt::Unchecked : Qt::Checked;
+ }
+ else if (col == IFTREE_COL_PROMISCUOUSMODE)
+ {
+ return device->pmode ? Qt::Checked : Qt::Unchecked;
+ }
+#ifdef HAVE_PCAP_CREATE
+ else if (col == IFTREE_COL_MONITOR_MODE)
+ {
+ return device->monitor_mode_enabled ? Qt::Checked : Qt::Unchecked;
+ }
+#endif
+ }
+ /* Used by SparkLineDelegate for loading the data for the statistics line */
+ else if (role == Qt::UserRole)
+ {
+ if (col == IFTREE_COL_STATS)
+ {
+ if ((active.contains(device->name) && active[device->name]) && points.contains(device->name))
+ return QVariant::fromValue(points[device->name]);
+ }
+ else if (col == IFTREE_COL_ACTIVE)
+ {
+ if (active.contains(device->name))
+ return QVariant::fromValue(active[device->name]);
+ }
+ else if (col == IFTREE_COL_HIDDEN)
+ {
+ return QVariant::fromValue((bool)device->hidden);
+ }
+ }
+ /* Displays the configuration icon for extcap interfaces */
+ else if (role == Qt::DecorationRole)
+ {
+ if (col == IFTREE_COL_EXTCAP)
+ {
+ if (device->if_info.type == IF_EXTCAP)
+ return QIcon(StockIcon("x-capture-options"));
+ }
+ }
+ else if (role == Qt::TextAlignmentRole)
+ {
+ if (col == IFTREE_COL_EXTCAP)
+ {
+ return Qt::AlignRight;
+ }
+ }
+ /* Displays the tooltip for each row */
+ else if (role == Qt::ToolTipRole)
+ {
+ return toolTipForInterface(row);
+ }
+ }
+#else
+ Q_UNUSED(index)
+ Q_UNUSED(role)
+#endif
+
+ return QVariant();
+}
+
+QVariant InterfaceTreeModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal)
+ {
+ if (role == Qt::DisplayRole)
+ {
+ if (section == IFTREE_COL_HIDDEN)
+ {
+ return tr("Show");
+ }
+ else if (section == IFTREE_COL_NAME)
+ {
+ return tr("Interface Name");
+ }
+ else if (section == IFTREE_COL_DESCRIPTION)
+ {
+ return tr("Friendly Name");
+ }
+ else if (section == IFTREE_COL_DISPLAY_NAME)
+ {
+ return tr("Friendly Name");
+ }
+ else if (section == IFTREE_COL_PIPE_PATH)
+ {
+ return tr("Local Pipe Path");
+ }
+ else if (section == IFTREE_COL_COMMENT)
+ {
+ return tr("Comment");
+ }
+ else if (section == IFTREE_COL_DLT)
+ {
+ return tr("Link-Layer Header");
+ }
+ else if (section == IFTREE_COL_PROMISCUOUSMODE)
+ {
+ return tr("Promiscuous");
+ }
+ else if (section == IFTREE_COL_SNAPLEN)
+ {
+ return tr("Snaplen (B)");
+ }
+#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
+ else if (section == IFTREE_COL_BUFFERLEN)
+ {
+ return tr("Buffer (MB)");
+ }
+#endif
+#ifdef HAVE_PCAP_CREATE
+ else if (section == IFTREE_COL_MONITOR_MODE)
+ {
+ return tr("Monitor Mode");
+ }
+#endif
+ else if (section == IFTREE_COL_CAPTURE_FILTER)
+ {
+ return tr("Capture Filter");
+ }
+ }
+ }
+
+ return QVariant();
+}
+
+QVariant InterfaceTreeModel::getColumnContent(int idx, int col, int role)
+{
+ return InterfaceTreeModel::data(index(idx, col), role);
+}
+
+#ifdef HAVE_PCAP_REMOTE
+bool InterfaceTreeModel::isRemote(int idx)
+{
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
+ if (device->remote_opts.src_type == CAPTURE_IFREMOTE)
+ return true;
+ return false;
+}
+#endif
+
+/**
+ * The interface list has changed. global_capture_opts.all_ifaces may have been reloaded
+ * or changed with current data. beginResetModel() and endResetModel() will signalize the
+ * proxy model and the view, that the data has changed and the view has to reload
+ */
+void InterfaceTreeModel::interfaceListChanged()
+{
+ beginResetModel();
+
+ points.clear();
+ active.clear();
+
+ endResetModel();
+}
+
+/*
+ * Displays the tooltip code for the given device index.
+ */
+QVariant InterfaceTreeModel::toolTipForInterface(int idx) const
+{
+#ifdef HAVE_LIBPCAP
+ if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx)
+ return QVariant();
+
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
+
+ QString tt_str = "<p>";
+ if (device->no_addresses > 0)
+ {
+ tt_str += QString("%1: %2")
+ .arg(device->no_addresses > 1 ? tr("Addresses") : tr("Address"))
+ .arg(html_escape(device->addresses))
+ .replace('\n', ", ");
+ }
+ else if (device->if_info.type == IF_EXTCAP)
+ {
+ tt_str = QString(tr("Extcap interface: %1")).arg(get_basename(device->if_info.extcap));
+ }
+ else
+ {
+ tt_str = tr("No addresses");
+ }
+ tt_str += "<br/>";
+
+ QString cfilter = device->cfilter;
+ if (cfilter.isEmpty())
+ {
+ tt_str += tr("No capture filter");
+ }
+ else
+ {
+ tt_str += QString("%1: %2")
+ .arg(tr("Capture filter"))
+ .arg(html_escape(cfilter));
+ }
+ tt_str += "</p>";
+
+ return tt_str;
+#else
+ Q_UNUSED(idx)
+
+ return QVariant();
+#endif
+}
+
+#ifdef HAVE_LIBPCAP
+void InterfaceTreeModel::stopStatistic()
+{
+ if (stat_cache_)
+ {
+ capture_stat_stop(stat_cache_);
+ stat_cache_ = NULL;
+ }
+}
+#endif
+
+void InterfaceTreeModel::updateStatistic(unsigned int idx)
+{
+#ifdef HAVE_LIBPCAP
+ if (! global_capture_opts.all_ifaces || global_capture_opts.all_ifaces->len <= (guint) idx)
+ return;
+
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
+
+ if (device->if_info.type == IF_PIPE || device->if_info.type == IF_EXTCAP)
+ return;
+
+ if (!stat_cache_)
+ {
+ // Start gathering statistics using dumpcap
+ // We crash (on macOS at least) if we try to do this from ::showEvent.
+ stat_cache_ = capture_stat_start(&global_capture_opts);
+ }
+
+ struct pcap_stat stats;
+ unsigned diff = 0;
+ bool isActive = false;
+
+ if (capture_stats(stat_cache_, device->name, &stats))
+ {
+ if ( (int) stats.ps_recv > 0 )
+ isActive = true;
+
+ if ((int)(stats.ps_recv - device->last_packets) >= 0)
+ {
+ diff = stats.ps_recv - device->last_packets;
+ device->packet_diff = diff;
+ }
+ device->last_packets = stats.ps_recv;
+ }
+
+ points[device->name].append(diff);
+
+ if (active[device->name] != isActive)
+ {
+ emit layoutAboutToBeChanged();
+ active[device->name] = isActive;
+ emit layoutChanged();
+ }
+
+ emit dataChanged(index(idx, IFTREE_COL_STATS), index(idx, IFTREE_COL_STATS));
+
+#else
+ Q_UNUSED(idx)
+#endif
+}
+
+QItemSelection InterfaceTreeModel::selectedDevices()
+{
+ QItemSelection mySelection;
+#ifdef HAVE_LIBPCAP
+ for (int idx = 0; idx < rowCount(); idx++)
+ {
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
+
+ if (device->selected)
+ {
+ QModelIndex selectIndex = index(idx, 0);
+ mySelection.merge(
+ QItemSelection(selectIndex, index(selectIndex.row(), columnCount() - 1)),
+ QItemSelectionModel::SelectCurrent
+ );
+ }
+ }
+#endif
+ return mySelection;
+}
+
+bool InterfaceTreeModel::updateSelectedDevices(QItemSelection sourceSelection)
+{
+ bool selectionHasChanged = false;
+#ifdef HAVE_LIBPCAP
+ QList<int> selectedIndices;
+
+ QItemSelection::const_iterator it = sourceSelection.constBegin();
+ while (it != sourceSelection.constEnd())
+ {
+ QModelIndexList indeces = ((QItemSelectionRange) (*it)).indexes();
+
+ QModelIndexList::const_iterator cit = indeces.constBegin();
+ while (cit != indeces.constEnd())
+ {
+ QModelIndex index = (QModelIndex) (*cit);
+ if (! selectedIndices.contains(index.row()))
+ {
+ selectedIndices.append(index.row());
+ }
+ ++cit;
+ }
+ ++it;
+ }
+
+ global_capture_opts.num_selected = 0;
+
+ for (unsigned int idx = 0; idx < global_capture_opts.all_ifaces->len; idx++)
+ {
+ interface_t *device = &g_array_index(global_capture_opts.all_ifaces, interface_t, idx);
+ if (selectedIndices.contains(idx))
+ {
+ if (! device->selected)
+ selectionHasChanged = true;
+ device->selected = TRUE;
+ global_capture_opts.num_selected++;
+ } else {
+ if (device->selected)
+ selectionHasChanged = true;
+ device->selected = FALSE;
+ }
+ }
+#else
+ Q_UNUSED(sourceSelection)
+#endif
+ return selectionHasChanged;
+}
diff --git a/ui/qt/models/interface_tree_model.h b/ui/qt/models/interface_tree_model.h
new file mode 100644
index 00000000..cdf2ac89
--- /dev/null
+++ b/ui/qt/models/interface_tree_model.h
@@ -0,0 +1,99 @@
+/** @file
+ *
+ * Model for the interface data for display in the interface frame
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef INTERFACE_TREE_MODEL_H
+#define INTERFACE_TREE_MODEL_H
+
+#include <config.h>
+#include <wireshark.h>
+
+#ifdef HAVE_LIBPCAP
+#include "ui/capture.h"
+#include "ui/capture_globals.h"
+#endif
+
+#include <QAbstractTableModel>
+#include <QList>
+#include <QMap>
+#include <QItemSelection>
+
+typedef QList<int> PointList;
+
+enum InterfaceTreeColumns
+{
+ IFTREE_COL_EXTCAP,
+ IFTREE_COL_EXTCAP_PATH,
+ IFTREE_COL_NAME,
+ IFTREE_COL_DESCRIPTION,
+ IFTREE_COL_DISPLAY_NAME,
+ IFTREE_COL_COMMENT,
+ IFTREE_COL_HIDDEN,
+ IFTREE_COL_DLT,
+ IFTREE_COL_PROMISCUOUSMODE,
+ IFTREE_COL_TYPE,
+ IFTREE_COL_STATS,
+ IFTREE_COL_ACTIVE,
+ IFTREE_COL_SNAPLEN,
+#ifdef CAN_SET_CAPTURE_BUFFER_SIZE
+ IFTREE_COL_BUFFERLEN,
+#endif
+#ifdef HAVE_PCAP_CREATE
+ IFTREE_COL_MONITOR_MODE,
+#endif
+ IFTREE_COL_CAPTURE_FILTER,
+ IFTREE_COL_PIPE_PATH,
+ IFTREE_COL_MAX /* is not being displayed, it is the definition for the maximum numbers of columns */
+};
+
+class InterfaceTreeModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ InterfaceTreeModel(QObject *parent);
+ ~InterfaceTreeModel();
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ QVariant data (const QModelIndex &index, int role = Qt::DisplayRole) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role) const;
+
+ void updateStatistic(unsigned int row);
+#ifdef HAVE_LIBPCAP
+ void stopStatistic();
+#endif
+
+ QString interfaceError();
+ QItemSelection selectedDevices();
+ bool updateSelectedDevices(QItemSelection sourceSelection);
+
+ QVariant getColumnContent(int idx, int col, int role = Qt::DisplayRole);
+
+#ifdef HAVE_PCAP_REMOTE
+ bool isRemote(int idx);
+#endif
+
+ static const QString DefaultNumericValue;
+
+public slots:
+ void interfaceListChanged();
+
+private:
+ QVariant toolTipForInterface(int idx) const;
+ QMap<QString, PointList> points;
+ QMap<QString, bool> active;
+
+#ifdef HAVE_LIBPCAP
+ if_stat_cache_t *stat_cache_;
+#endif // HAVE_LIBPCAP
+};
+
+#endif // INTERFACE_TREE_MODEL_H
diff --git a/ui/qt/models/manuf_table_model.cpp b/ui/qt/models/manuf_table_model.cpp
new file mode 100644
index 00000000..35792273
--- /dev/null
+++ b/ui/qt/models/manuf_table_model.cpp
@@ -0,0 +1,220 @@
+/*
+ * manuf_table_model.h
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "manuf_table_model.h"
+
+ManufTableItem::ManufTableItem(struct ws_manuf *ptr) :
+ short_name_(QString::fromUtf8(ptr->short_name)),
+ long_name_(QString::fromUtf8(ptr->long_name))
+{
+ qsizetype size;
+ switch (ptr->mask) {
+ case 24:
+ size = 3;
+ break;
+ case 28:
+ size = 4;
+ break;
+ case 36:
+ size = 5;
+ break;
+ default:
+ ws_assert_not_reached();
+ }
+ // Note: since 'ptr' is not stable, a deep copy is needed.
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ block_bytes_ = QByteArray(reinterpret_cast<const char *>(ptr->block), size);
+#else
+ block_bytes_ = QByteArray(reinterpret_cast<const char *>(ptr->block), static_cast<int>(size));
+#endif
+
+ char buf[64];
+ block_name_ = QString::fromUtf8(ws_manuf_block_str(buf, sizeof(buf), ptr));
+}
+
+ManufTableItem::~ManufTableItem()
+{
+}
+
+ManufTableModel::ManufTableModel(QObject *parent) : QAbstractTableModel(parent)
+{
+ ws_manuf_iter_t iter;
+ struct ws_manuf item;
+
+ ws_manuf_iter_init(&iter);
+ while (ws_manuf_iter_next(&iter, &item)) {
+ rows_.append(new ManufTableItem(&item));
+ }
+}
+
+ManufTableModel::~ManufTableModel()
+{
+ clear();
+}
+
+int ManufTableModel::rowCount(const QModelIndex &) const
+{
+ return static_cast<int>(rows_.count());
+}
+
+int ManufTableModel::columnCount(const QModelIndex &) const
+{
+ return NUM_COLS;
+}
+
+QVariant ManufTableModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid())
+ return QVariant();
+
+ if (index.row() >= rows_.size())
+ return QVariant();
+
+ ManufTableItem *item = rows_.at(index.row());
+ if (index.column() >= NUM_COLS)
+ return QVariant();
+
+ if (role == Qt::DisplayRole) {
+ switch (index.column()) {
+ case COL_MAC_PREFIX:
+ return item->block_name_;
+ case COL_SHORT_NAME:
+ return item->short_name_;
+ case COL_VENDOR_NAME:
+ return item->long_name_;
+ default:
+ return QVariant();
+ }
+ }
+
+ if (role == Qt::UserRole) {
+ switch (index.column()) {
+ case COL_MAC_PREFIX:
+ return item->block_bytes_;
+ case COL_SHORT_NAME:
+ return item->short_name_;
+ case COL_VENDOR_NAME:
+ return item->long_name_;
+ default:
+ return QVariant();
+ }
+ }
+
+ return QVariant();
+}
+
+void ManufTableModel::addRecord(struct ws_manuf *ptr)
+{
+ emit beginInsertRows(QModelIndex(), rowCount(), rowCount());
+ ManufTableItem *item = new ManufTableItem(ptr);
+ rows_.append(item);
+ emit endInsertRows();
+}
+
+void ManufTableModel::clear()
+{
+ if (!rows_.isEmpty()) {
+ emit beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
+ qDeleteAll(rows_.begin(), rows_.end());
+ rows_.clear();
+ emit endRemoveRows();
+ }
+}
+
+QVariant ManufTableModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole)
+ return QVariant();
+
+ if (orientation == Qt::Horizontal) {
+ switch (section) {
+ case COL_MAC_PREFIX:
+ return QString(tr("Address Block"));
+ case COL_SHORT_NAME:
+ return QString(tr("Short Name"));
+ case COL_VENDOR_NAME:
+ return QString(tr("Vendor Name"));
+ }
+ }
+
+ return QVariant();
+}
+
+
+ManufSortFilterProxyModel::ManufSortFilterProxyModel(QObject* parent) :
+ QSortFilterProxyModel(parent),
+ filter_type_(FilterEmpty)
+{
+}
+
+void ManufSortFilterProxyModel::clearFilter()
+{
+ if (filter_type_ == FilterEmpty)
+ return;
+ filter_type_ = FilterEmpty;
+ invalidateFilter();
+}
+
+void ManufSortFilterProxyModel::setFilterAddress(const QByteArray &bytes)
+{
+ filter_type_ = FilterByAddress;
+ filter_bytes_ = bytes;
+ invalidateFilter();
+}
+
+void ManufSortFilterProxyModel::setFilterName(QRegularExpression &name)
+{
+ filter_type_ = FilterByName;
+ filter_name_ = name;
+ invalidateFilter();
+}
+
+static bool match_filter(const QByteArray &bytes, const QByteArray &mac_block)
+{
+ if (bytes.size() < mac_block.size())
+ return mac_block.startsWith(bytes);
+
+#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
+ QByteArray prefix = bytes.first(mac_block.size());
+#else
+ QByteArray prefix = bytes.left(mac_block.size());
+#endif
+ // Blocks are 3, 4 or 5 bytes wide
+ if (mac_block.size() > 3) {
+ // Mask out the last nibble of the bytes for 28 and 36 bit block lengths
+ // (but not 24 bit OUIs)
+ prefix[prefix.size() - 1] = prefix[prefix.size() - 1] & 0xF0;
+ }
+ return prefix == mac_block;
+}
+
+bool ManufSortFilterProxyModel::filterAddressAcceptsRow(int source_row, const QModelIndex& source_parent) const
+{
+ QModelIndex chkIdx = sourceModel()->index(source_row, ManufTableModel::COL_MAC_PREFIX, source_parent);
+ QByteArray mac_block = chkIdx.data(Qt::UserRole).toByteArray();
+ return match_filter(filter_bytes_, mac_block);
+}
+
+bool ManufSortFilterProxyModel::filterNameAcceptsRow(int source_row, const QModelIndex& source_parent) const
+{
+ QModelIndex chkIdx = sourceModel()->index(source_row, ManufTableModel::COL_VENDOR_NAME, source_parent);
+ QString vendor_name = chkIdx.data(Qt::UserRole).toString();
+ return filter_name_.match(vendor_name).hasMatch();
+}
+
+bool ManufSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const
+{
+ switch (filter_type_) {
+ case FilterEmpty: return true;
+ case FilterByAddress: return filterAddressAcceptsRow(source_row, source_parent);
+ case FilterByName: return filterNameAcceptsRow(source_row, source_parent);
+ }
+ ws_error("unknown filter type %d", filter_type_);
+}
diff --git a/ui/qt/models/manuf_table_model.h b/ui/qt/models/manuf_table_model.h
new file mode 100644
index 00000000..9f850774
--- /dev/null
+++ b/ui/qt/models/manuf_table_model.h
@@ -0,0 +1,90 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef MANUF_TABLE_MODEL_H
+#define MANUF_TABLE_MODEL_H
+
+#include <QSortFilterProxyModel>
+#include <QAbstractTableModel>
+#include <QList>
+
+#include <wireshark.h>
+#include <epan/manuf.h>
+
+class ManufTableItem
+{
+public:
+ ManufTableItem(struct ws_manuf *ptr);
+ ~ManufTableItem();
+
+ QByteArray block_bytes_;
+ QString block_name_;
+ QString short_name_;
+ QString long_name_;
+};
+
+class ManufTableModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ ManufTableModel(QObject *parent);
+ ~ManufTableModel();
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const ;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ void addRecord(struct ws_manuf *ptr);
+
+ void clear();
+
+ enum {
+ COL_MAC_PREFIX,
+ COL_SHORT_NAME,
+ COL_VENDOR_NAME,
+ NUM_COLS,
+ };
+
+private:
+ QList<ManufTableItem *> rows_;
+};
+
+class ManufSortFilterProxyModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ enum ManufProxyFilterType
+ {
+ FilterEmpty = 0,
+ FilterByAddress,
+ FilterByName,
+ };
+ Q_ENUM(ManufProxyFilterType)
+
+ ManufSortFilterProxyModel(QObject *parent);
+
+ virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
+
+public slots:
+ void setFilterAddress(const QByteArray&);
+ void setFilterName(QRegularExpression&);
+ void clearFilter();
+
+private:
+ ManufProxyFilterType filter_type_;
+ QByteArray filter_bytes_;
+ QRegularExpression filter_name_;
+
+ bool filterAddressAcceptsRow(int source_row, const QModelIndex& source_parent) const;
+ bool filterNameAcceptsRow(int source_row, const QModelIndex& source_parent) const;
+};
+
+#endif
diff --git a/ui/qt/models/numeric_value_chooser_delegate.cpp b/ui/qt/models/numeric_value_chooser_delegate.cpp
new file mode 100644
index 00000000..9bed91b2
--- /dev/null
+++ b/ui/qt/models/numeric_value_chooser_delegate.cpp
@@ -0,0 +1,93 @@
+/* numeric_value_chooser_delegate.cpp
+ * Delegate to select a numeric value for a treeview entry
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+
+#include <ui/qt/models/numeric_value_chooser_delegate.h>
+
+#include <QStyledItemDelegate>
+#include <QSpinBox>
+
+NumericValueChooserDelegate::NumericValueChooserDelegate(int min, int max, QObject *parent)
+ : QStyledItemDelegate(parent)
+{
+ _min = min;
+ _max = max;
+ _default = min;
+}
+
+NumericValueChooserDelegate::~NumericValueChooserDelegate()
+{
+}
+
+void NumericValueChooserDelegate::setMinMaxRange(int min, int max)
+{
+ _min = qMin(min, max);
+ _max = qMax(min, max);
+ /* ensure, that the default value is within the new min<->max */
+ _default = qMin(_max, qMax(_min, _default));
+ _defReturn = QVariant::fromValue(_default);
+}
+
+void NumericValueChooserDelegate::setDefaultValue(int defValue, QVariant defaultReturn)
+{
+ /* ensure, that the new default value is within min<->max */
+ _default = qMin(_max, qMax(_min, defValue));
+ _defReturn = defaultReturn;
+}
+
+QWidget* NumericValueChooserDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ if (!index.isValid()) {
+ return QStyledItemDelegate::createEditor(parent, option, index);
+ }
+
+ QSpinBox * editor = new QSpinBox(parent);
+ editor->setMinimum(_min);
+ editor->setMaximum(_max);
+ editor->setWrapping(true);
+
+ connect(editor, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
+ &NumericValueChooserDelegate::onValueChanged);
+
+ return editor;
+}
+
+void NumericValueChooserDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+ if (index.isValid())
+ {
+ bool canConvert = false;
+ int val = index.data().toInt(&canConvert);
+ if (! canConvert)
+ val = _default;
+
+ QSpinBox * spinBox = qobject_cast<QSpinBox *>(editor);
+ spinBox->setValue(val);
+ }
+ else
+ QStyledItemDelegate::setEditorData(editor, index);
+}
+
+void NumericValueChooserDelegate::setModelData(QWidget *editor, QAbstractItemModel * model, const QModelIndex &index) const
+{
+ if (index.isValid()) {
+ QSpinBox * spinBox = qobject_cast<QSpinBox *>(editor);
+ model->setData(index, _default == spinBox->value() ? _defReturn : QVariant::fromValue(spinBox->value()));
+ } else {
+ QStyledItemDelegate::setModelData(editor, model, index);
+ }
+}
+
+void NumericValueChooserDelegate::onValueChanged(int)
+{
+ QSpinBox * spinBox = qobject_cast<QSpinBox *>(sender());
+ emit commitData(spinBox);
+}
diff --git a/ui/qt/models/numeric_value_chooser_delegate.h b/ui/qt/models/numeric_value_chooser_delegate.h
new file mode 100644
index 00000000..ceda8746
--- /dev/null
+++ b/ui/qt/models/numeric_value_chooser_delegate.h
@@ -0,0 +1,45 @@
+/** @file
+ *
+ * Delegate to select a numeric value for a treeview entry
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef NUMERIC_VALUE_CHOOSER_DELEGATE_H_
+#define NUMERIC_VALUE_CHOOSER_DELEGATE_H_
+
+
+#include <QStyledItemDelegate>
+
+class NumericValueChooserDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ NumericValueChooserDelegate(int min = 0, int max = 0, QObject *parent = 0);
+ ~NumericValueChooserDelegate();
+
+ void setMinMaxRange(int min, int max);
+ void setDefaultValue(int defValue, QVariant defaultReturn);
+
+protected:
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+ void setEditorData(QWidget *editor, const QModelIndex &index) const;
+ void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
+
+private:
+
+ int _min;
+ int _max;
+ int _default;
+ QVariant _defReturn;
+
+private slots:
+ void onValueChanged(int i);
+};
+
+#endif /* NUMERIC_VALUE_CHOOSER_DELEGATE_H_ */
diff --git a/ui/qt/models/packet_list_model.cpp b/ui/qt/models/packet_list_model.cpp
new file mode 100644
index 00000000..0ed61d74
--- /dev/null
+++ b/ui/qt/models/packet_list_model.cpp
@@ -0,0 +1,1014 @@
+/* packet_list_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <algorithm>
+#include <glib.h>
+#include <cmath>
+#include <stdexcept>
+
+#include "packet_list_model.h"
+
+#include "file.h"
+
+#include <wsutil/nstime.h>
+#include <epan/column.h>
+#include <epan/expert.h>
+#include <epan/prefs.h>
+
+#include "ui/packet_list_utils.h"
+#include "ui/recent.h"
+
+#include <epan/color_filters.h>
+#include "frame_tvbuff.h"
+
+#include <ui/qt/utils/color_utils.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+#include "main_application.h"
+#include <ui/qt/main_window.h>
+#include <ui/qt/main_status_bar.h>
+#include <ui/qt/widgets/wireless_timeline.h>
+
+#include <QColor>
+#include <QElapsedTimer>
+#include <QFontMetrics>
+#include <QModelIndex>
+#include <QElapsedTimer>
+
+// Print timing information
+//#define DEBUG_PACKET_LIST_MODEL 1
+
+#ifdef DEBUG_PACKET_LIST_MODEL
+#include <wsutil/time_util.h>
+#endif
+
+class SortAbort : public std::runtime_error
+{
+ using std::runtime_error::runtime_error;
+};
+
+static PacketListModel * glbl_plist_model = Q_NULLPTR;
+static const int reserved_packets_ = 100000;
+
+guint
+packet_list_append(column_info *, frame_data *fdata)
+{
+ if (!glbl_plist_model)
+ return 0;
+
+ /* fdata should be filled with the stuff we need
+ * strings are built at display time.
+ */
+ return glbl_plist_model->appendPacket(fdata);
+}
+
+void
+packet_list_recreate_visible_rows(void)
+{
+ if (glbl_plist_model)
+ glbl_plist_model->recreateVisibleRows();
+}
+
+PacketListModel::PacketListModel(QObject *parent, capture_file *cf) :
+ QAbstractItemModel(parent),
+ number_to_row_(QVector<int>()),
+ max_row_height_(0),
+ max_line_count_(1),
+ idle_dissection_row_(0)
+{
+ Q_ASSERT(glbl_plist_model == Q_NULLPTR);
+ glbl_plist_model = this;
+ setCaptureFile(cf);
+
+ physical_rows_.reserve(reserved_packets_);
+ visible_rows_.reserve(reserved_packets_);
+ new_visible_rows_.reserve(1000);
+ number_to_row_.reserve(reserved_packets_);
+
+ if (qobject_cast<MainWindow *>(mainApp->mainWindow()))
+ {
+ MainWindow *mw = qobject_cast<MainWindow *>(mainApp->mainWindow());
+ QWidget * wtWidget = mw->findChild<WirelessTimeline *>();
+ if (wtWidget && qobject_cast<WirelessTimeline *>(wtWidget))
+ {
+ WirelessTimeline * wt = qobject_cast<WirelessTimeline *>(wtWidget);
+ connect(this, &PacketListModel::bgColorizationProgress,
+ wt, &WirelessTimeline::bgColorizationProgress);
+ }
+
+ }
+
+ connect(this, &PacketListModel::maxLineCountChanged,
+ this, &PacketListModel::emitItemHeightChanged,
+ Qt::QueuedConnection);
+ idle_dissection_timer_ = new QElapsedTimer();
+}
+
+PacketListModel::~PacketListModel()
+{
+ delete idle_dissection_timer_;
+}
+
+void PacketListModel::setCaptureFile(capture_file *cf)
+{
+ cap_file_ = cf;
+}
+
+// Packet list records have no children (for now, at least).
+QModelIndex PacketListModel::index(int row, int column, const QModelIndex &) const
+{
+ if (row >= visible_rows_.count() || row < 0 || !cap_file_ || column >= prefs.num_cols)
+ return QModelIndex();
+
+ PacketListRecord *record = visible_rows_[row];
+
+ return createIndex(row, column, record);
+}
+
+// Everything is under the root.
+QModelIndex PacketListModel::parent(const QModelIndex &) const
+{
+ return QModelIndex();
+}
+
+int PacketListModel::packetNumberToRow(int packet_num) const
+{
+ // map 1-based values to 0-based row numbers. Invisible rows are stored as
+ // the default value (0) and should map to -1.
+ return number_to_row_.value(packet_num) - 1;
+}
+
+guint PacketListModel::recreateVisibleRows()
+{
+ beginResetModel();
+ visible_rows_.resize(0);
+ number_to_row_.fill(0);
+ endResetModel();
+
+ foreach (PacketListRecord *record, physical_rows_) {
+ frame_data *fdata = record->frameData();
+
+ if (fdata->passed_dfilter || fdata->ref_time) {
+ visible_rows_ << record;
+ if (static_cast<guint32>(number_to_row_.size()) <= fdata->num) {
+ number_to_row_.resize(fdata->num + 10000);
+ }
+ number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
+ }
+ }
+ if (!visible_rows_.isEmpty()) {
+ beginInsertRows(QModelIndex(), 0, static_cast<int>(visible_rows_.count()) - 1);
+ endInsertRows();
+ }
+ idle_dissection_row_ = 0;
+ return static_cast<guint>(visible_rows_.count());
+}
+
+void PacketListModel::clear() {
+ beginResetModel();
+ qDeleteAll(physical_rows_);
+ PacketListRecord::invalidateAllRecords();
+ physical_rows_.resize(0);
+ visible_rows_.resize(0);
+ new_visible_rows_.resize(0);
+ number_to_row_.resize(0);
+ endResetModel();
+ max_row_height_ = 0;
+ max_line_count_ = 1;
+ idle_dissection_timer_->invalidate();
+ idle_dissection_row_ = 0;
+}
+
+void PacketListModel::invalidateAllColumnStrings()
+{
+ PacketListRecord::invalidateAllRecords();
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
+ QVector<int>() << Qt::DisplayRole);
+}
+
+void PacketListModel::resetColumns()
+{
+ if (cap_file_) {
+ PacketListRecord::resetColumns(&cap_file_->cinfo);
+ }
+
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
+ emit headerDataChanged(Qt::Horizontal, 0, columnCount() - 1);
+}
+
+void PacketListModel::resetColorized()
+{
+ PacketListRecord::resetColorization();
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
+}
+
+void PacketListModel::toggleFrameMark(const QModelIndexList &indeces)
+{
+ if (!cap_file_ || indeces.count() <= 0)
+ return;
+
+ int sectionMax = columnCount() - 1;
+
+ foreach (QModelIndex index, indeces) {
+ if (! index.isValid())
+ continue;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record)
+ continue;
+
+ frame_data *fdata = record->frameData();
+ if (!fdata)
+ continue;
+
+ if (fdata->marked)
+ cf_unmark_frame(cap_file_, fdata);
+ else
+ cf_mark_frame(cap_file_, fdata);
+
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
+ }
+}
+
+void PacketListModel::setDisplayedFrameMark(gboolean set)
+{
+ foreach (PacketListRecord *record, visible_rows_) {
+ if (set) {
+ cf_mark_frame(cap_file_, record->frameData());
+ } else {
+ cf_unmark_frame(cap_file_, record->frameData());
+ }
+ }
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole);
+}
+
+void PacketListModel::toggleFrameIgnore(const QModelIndexList &indeces)
+{
+ if (!cap_file_ || indeces.count() <= 0)
+ return;
+
+ int sectionMax = columnCount() - 1;
+
+ foreach (QModelIndex index, indeces) {
+ if (! index.isValid())
+ continue;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record)
+ continue;
+
+ frame_data *fdata = record->frameData();
+ if (!fdata)
+ continue;
+
+ if (fdata->ignored)
+ cf_unignore_frame(cap_file_, fdata);
+ else
+ cf_ignore_frame(cap_file_, fdata);
+
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+ }
+}
+
+void PacketListModel::setDisplayedFrameIgnore(gboolean set)
+{
+ foreach (PacketListRecord *record, visible_rows_) {
+ if (set) {
+ cf_ignore_frame(cap_file_, record->frameData());
+ } else {
+ cf_unignore_frame(cap_file_, record->frameData());
+ }
+ }
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+}
+
+void PacketListModel::toggleFrameRefTime(const QModelIndex &rt_index)
+{
+ if (!cap_file_ || !rt_index.isValid()) return;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(rt_index.internalPointer());
+ if (!record) return;
+
+ frame_data *fdata = record->frameData();
+ if (!fdata) return;
+
+ if (fdata->ref_time) {
+ fdata->ref_time=0;
+ cap_file_->ref_time_count--;
+ } else {
+ fdata->ref_time=1;
+ cap_file_->ref_time_count++;
+ }
+ cf_reftime_packets(cap_file_);
+ if (!fdata->ref_time && !fdata->passed_dfilter) {
+ cap_file_->displayed_count--;
+ }
+ record->resetColumns(&cap_file_->cinfo);
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
+}
+
+void PacketListModel::unsetAllFrameRefTime()
+{
+ if (!cap_file_) return;
+
+ /* XXX: we might need a progressbar here */
+
+ foreach (PacketListRecord *record, physical_rows_) {
+ frame_data *fdata = record->frameData();
+ if (fdata->ref_time) {
+ fdata->ref_time = 0;
+ }
+ }
+ cap_file_->ref_time_count = 0;
+ cf_reftime_packets(cap_file_);
+ PacketListRecord::resetColumns(&cap_file_->cinfo);
+ emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1));
+}
+
+void PacketListModel::addFrameComment(const QModelIndexList &indices, const QByteArray &comment)
+{
+ int sectionMax = columnCount() - 1;
+ frame_data *fdata;
+ if (!cap_file_) return;
+
+ for (const auto &index : indices) {
+ if (!index.isValid()) continue;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record) continue;
+
+ fdata = record->frameData();
+ wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
+ wtap_block_add_string_option(pkt_block, OPT_COMMENT, comment.data(), comment.size());
+
+ if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
+ cap_file_->packet_comment_count++;
+ expert_update_comment_count(cap_file_->packet_comment_count);
+ }
+
+ // In case there are coloring rules or columns related to comments.
+ // (#12519)
+ //
+ // XXX: "Does any active coloring rule relate to frame data"
+ // could be an optimization. For columns, note that
+ // "col_based_on_frame_data" only applies to built in columns,
+ // not custom columns based on frame data. (Should we prevent
+ // custom columns based on frame data from being created,
+ // substituting them with the other columns?)
+ //
+ // Note that there are not currently any fields that depend on
+ // whether other frames have comments, unlike with time references
+ // and time shifts ("frame.time_relative", "frame.offset_shift", etc.)
+ // If there were, then we'd need to reset data for all frames instead
+ // of just the frames changed.
+ record->invalidateColorized();
+ record->invalidateRecord();
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+ }
+}
+
+void PacketListModel::setFrameComment(const QModelIndex &index, const QByteArray &comment, guint c_number)
+{
+ int sectionMax = columnCount() - 1;
+ frame_data *fdata;
+ if (!cap_file_) return;
+
+ if (!index.isValid()) return;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record) return;
+
+ fdata = record->frameData();
+
+ wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
+ if (comment.isEmpty()) {
+ wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, c_number);
+ if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
+ cap_file_->packet_comment_count--;
+ expert_update_comment_count(cap_file_->packet_comment_count);
+ }
+ } else {
+ wtap_block_set_nth_string_option_value(pkt_block, OPT_COMMENT, c_number, comment.data(), comment.size());
+ cf_set_modified_block(cap_file_, fdata, pkt_block);
+ }
+
+ record->invalidateColorized();
+ record->invalidateRecord();
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+}
+
+void PacketListModel::deleteFrameComments(const QModelIndexList &indices)
+{
+ int sectionMax = columnCount() - 1;
+ frame_data *fdata;
+ if (!cap_file_) return;
+
+ for (const auto &index : indices) {
+ if (!index.isValid()) continue;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record) continue;
+
+ fdata = record->frameData();
+ wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
+ guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
+
+ if (n_comments) {
+ for (guint i = 0; i < n_comments; i++) {
+ wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0);
+ }
+ if (!cf_set_modified_block(cap_file_, fdata, pkt_block)) {
+ cap_file_->packet_comment_count -= n_comments;
+ expert_update_comment_count(cap_file_->packet_comment_count);
+ }
+
+ record->invalidateColorized();
+ record->invalidateRecord();
+ emit dataChanged(index.sibling(index.row(), 0), index.sibling(index.row(), sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+ }
+ }
+}
+
+void PacketListModel::deleteAllFrameComments()
+{
+ int row;
+ int sectionMax = columnCount() - 1;
+ if (!cap_file_) return;
+
+ /* XXX: we might need a progressbar here */
+
+ foreach (PacketListRecord *record, physical_rows_) {
+ frame_data *fdata = record->frameData();
+ wtap_block_t pkt_block = cf_get_packet_block(cap_file_, fdata);
+ guint n_comments = wtap_block_count_option(pkt_block, OPT_COMMENT);
+
+ if (n_comments) {
+ for (guint i = 0; i < n_comments; i++) {
+ wtap_block_remove_nth_option_instance(pkt_block, OPT_COMMENT, 0);
+ }
+ cf_set_modified_block(cap_file_, fdata, pkt_block);
+
+ record->invalidateColorized();
+ record->invalidateRecord();
+ row = packetNumberToRow(fdata->num);
+ if (row > -1) {
+ emit dataChanged(index(row, 0), index(row, sectionMax),
+ QVector<int>() << Qt::BackgroundRole << Qt::ForegroundRole << Qt::DisplayRole);
+ }
+ }
+ }
+ cap_file_->packet_comment_count = 0;
+ expert_update_comment_count(cap_file_->packet_comment_count);
+}
+
+void PacketListModel::setMaximumRowHeight(int height)
+{
+ max_row_height_ = height;
+ // As the QTreeView uniformRowHeights documentation says,
+ // "The height is obtained from the first item in the view. It is
+ // updated when the data changes on that item."
+ emit dataChanged(index(0, 0), index(0, columnCount() - 1));
+}
+
+int PacketListModel::sort_column_;
+int PacketListModel::sort_column_is_numeric_;
+int PacketListModel::text_sort_column_;
+Qt::SortOrder PacketListModel::sort_order_;
+capture_file *PacketListModel::sort_cap_file_;
+gboolean PacketListModel::stop_flag_;
+ProgressFrame *PacketListModel::progress_frame_;
+double PacketListModel::comps_;
+double PacketListModel::exp_comps_;
+
+QElapsedTimer busy_timer_;
+const int busy_timeout_ = 65; // ms, approximately 15 fps
+void PacketListModel::sort(int column, Qt::SortOrder order)
+{
+ if (!cap_file_ || visible_rows_.count() < 1) return;
+ if (column < 0) return;
+
+ if (physical_rows_.count() < 1)
+ return;
+
+ sort_column_ = column;
+ text_sort_column_ = PacketListRecord::textColumn(column);
+ sort_order_ = order;
+ sort_cap_file_ = cap_file_;
+
+ QString col_title = get_column_title(column);
+
+ if (text_sort_column_ >= 0 && (guint)visible_rows_.count() > prefs.gui_packet_list_cached_rows_max) {
+ /* Column not based on frame data but by column text that requires
+ * dissection, so to sort in a reasonable amount of time the column
+ * text needs to be cached.
+ */
+ /* If the sort is being triggered because the columns were already
+ * sorted and the filter is being cleared (or changed to something
+ * else with more rows than fit in the cache), then the temporary
+ * message will be immediately overwritten with the standard capture
+ * statistics by the packets_bar_update() call after thawing the rows.
+ * It will still blink yellow, and the user will get the message if
+ * they then click on the header file (wondering why it didn't sort.)
+ */
+ if (col_title.isEmpty()) {
+ col_title = tr("Column");
+ }
+ QString temp_msg = tr("%1 can only be sorted with %2 or fewer visible rows; increase cache size in Layout preferences").arg(col_title).arg(prefs.gui_packet_list_cached_rows_max);
+ mainApp->pushStatus(MainApplication::TemporaryStatus, temp_msg);
+ return;
+ }
+
+ /* If we are currently in the middle of reading the capture file, don't
+ * sort. PacketList::captureFileReadFinished invalidates all the cached
+ * column strings and then tries to sort again.
+ * Similarly, claim the read lock because we don't want the file to
+ * change out from under us while sorting, which can segfault. (Previously
+ * we ignored user input, but now in order to cancel sorting we don't.)
+ */
+ if (sort_cap_file_->read_lock) {
+ ws_info("Refusing to sort because capture file is being read");
+ /* We shouldn't have to tell the user because we're just deferring
+ * the sort until PacketList::captureFileReadFinished
+ */
+ return;
+ }
+ sort_cap_file_->read_lock = TRUE;
+
+ QString busy_msg;
+ if (!col_title.isEmpty()) {
+ busy_msg = tr("Sorting \"%1\"…").arg(col_title);
+ } else {
+ busy_msg = tr("Sorting …");
+ }
+ stop_flag_ = FALSE;
+ comps_ = 0;
+ /* XXX: The expected number of comparisons is O(N log N), but this could
+ * be a pretty significant overestimate of the amount of time it takes,
+ * if there are lots of identical entries. (Especially with string
+ * comparisons, some comparisons are faster than others.) Better to
+ * overestimate?
+ */
+ exp_comps_ = log2(visible_rows_.count()) * visible_rows_.count();
+ progress_frame_ = nullptr;
+ if (qobject_cast<MainWindow *>(mainApp->mainWindow())) {
+ MainWindow *mw = qobject_cast<MainWindow *>(mainApp->mainWindow());
+ progress_frame_ = mw->findChild<ProgressFrame *>();
+ if (progress_frame_) {
+ progress_frame_->showProgress(busy_msg, true, false, &stop_flag_, 0);
+ connect(progress_frame_, &ProgressFrame::stopLoading,
+ this, &PacketListModel::stopSorting);
+ }
+ }
+
+ busy_timer_.start();
+ sort_column_is_numeric_ = isNumericColumn(sort_column_);
+ QVector<PacketListRecord *> sorted_visible_rows_ = visible_rows_;
+ try {
+ std::sort(sorted_visible_rows_.begin(), sorted_visible_rows_.end(), recordLessThan);
+
+ beginResetModel();
+ visible_rows_.resize(0);
+ number_to_row_.fill(0);
+ foreach (PacketListRecord *record, sorted_visible_rows_) {
+ frame_data *fdata = record->frameData();
+
+ if (fdata->passed_dfilter || fdata->ref_time) {
+ visible_rows_ << record;
+ if (number_to_row_.size() <= (int)fdata->num) {
+ number_to_row_.resize(fdata->num + 10000);
+ }
+ number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
+ }
+ }
+ endResetModel();
+ } catch (const SortAbort& e) {
+ mainApp->pushStatus(MainApplication::TemporaryStatus, e.what());
+ }
+
+ if (progress_frame_ != nullptr) {
+ progress_frame_->hide();
+ disconnect(progress_frame_, &ProgressFrame::stopLoading,
+ this, &PacketListModel::stopSorting);
+ }
+ sort_cap_file_->read_lock = FALSE;
+
+ if (cap_file_->current_frame) {
+ emit goToPacket(cap_file_->current_frame->num);
+ }
+}
+
+void PacketListModel::stopSorting()
+{
+ stop_flag_ = TRUE;
+}
+
+bool PacketListModel::isNumericColumn(int column)
+{
+ if (column < 0) {
+ return false;
+ }
+ switch (sort_cap_file_->cinfo.columns[column].col_fmt) {
+ case COL_CUMULATIVE_BYTES: /**< 3) Cumulative number of bytes */
+ case COL_DELTA_TIME: /**< 5) Delta time */
+ case COL_DELTA_TIME_DIS: /**< 8) Delta time displayed*/
+ case COL_UNRES_DST_PORT: /**< 10) Unresolved dest port */
+ case COL_FREQ_CHAN: /**< 15) IEEE 802.11 (and WiMax?) - Channel */
+ case COL_RSSI: /**< 22) IEEE 802.11 - received signal strength */
+ case COL_TX_RATE: /**< 23) IEEE 802.11 - TX rate in Mbps */
+ case COL_NUMBER: /**< 32) Packet list item number */
+ case COL_PACKET_LENGTH: /**< 33) Packet length in bytes */
+ case COL_UNRES_SRC_PORT: /**< 41) Unresolved source port */
+ return true;
+
+ /*
+ * Try to sort port numbers as number, if the numeric comparison fails (due
+ * to name resolution), it will fallback to string comparison.
+ * */
+ case COL_RES_DST_PORT: /**< 10) Resolved dest port */
+ case COL_DEF_DST_PORT: /**< 12) Destination port */
+ case COL_DEF_SRC_PORT: /**< 37) Source port */
+ case COL_RES_SRC_PORT: /**< 40) Resolved source port */
+ return true;
+
+ case COL_CUSTOM:
+ /* handle custom columns below. */
+ break;
+
+ default:
+ return false;
+ }
+
+ guint num_fields = g_slist_length(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids);
+ for (guint i = 0; i < num_fields; i++) {
+ guint *field_idx = (guint *) g_slist_nth_data(sort_cap_file_->cinfo.columns[column].col_custom_fields_ids, i);
+ header_field_info *hfi = proto_registrar_get_nth(*field_idx);
+
+ /*
+ * Reject a field when there is no numeric field type or when:
+ * - there are (value_string) "strings"
+ * (but do accept fields which have a unit suffix).
+ * - BASE_HEX or BASE_HEX_DEC (these have a constant width, string
+ * comparison is faster than conversion to double).
+ * - BASE_CUSTOM (these can be formatted in any way).
+ */
+ if (!hfi ||
+ (hfi->strings != NULL && !(hfi->display & BASE_UNIT_STRING)) ||
+ !(((FT_IS_INT(hfi->type) || FT_IS_UINT(hfi->type)) &&
+ ((FIELD_DISPLAY(hfi->display) == BASE_DEC) ||
+ (FIELD_DISPLAY(hfi->display) == BASE_OCT) ||
+ (FIELD_DISPLAY(hfi->display) == BASE_DEC_HEX))) ||
+ (hfi->type == FT_DOUBLE) || (hfi->type == FT_FLOAT) ||
+ (hfi->type == FT_BOOLEAN) || (hfi->type == FT_FRAMENUM) ||
+ (hfi->type == FT_RELATIVE_TIME))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool PacketListModel::recordLessThan(PacketListRecord *r1, PacketListRecord *r2)
+{
+ int cmp_val = 0;
+ comps_++;
+
+ // Wherein we try to cram the logic of packet_list_compare_records,
+ // _packet_list_compare_records, and packet_list_compare_custom from
+ // gtk/packet_list_store.c into one function
+
+ if (busy_timer_.elapsed() > busy_timeout_) {
+ if (progress_frame_) {
+ progress_frame_->setValue(static_cast<int>(comps_/exp_comps_ * 100));
+ }
+ // What's the least amount of processing that we can do which will draw
+ // the busy indicator?
+ mainApp->processEvents(QEventLoop::ExcludeSocketNotifiers, 1);
+ if (stop_flag_) {
+ throw SortAbort("Sorting aborted");
+ }
+ busy_timer_.restart();
+ }
+ if (sort_column_ < 0) {
+ // No column.
+ cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
+ } else if (text_sort_column_ < 0) {
+ // Column comes directly from frame data
+ cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), sort_cap_file_->cinfo.columns[sort_column_].col_fmt);
+ } else {
+ QString r1String = r1->columnString(sort_cap_file_, sort_column_);
+ QString r2String = r2->columnString(sort_cap_file_, sort_column_);
+ // XXX: The naive string comparison compares Unicode code points.
+ // Proper collation is more expensive
+ cmp_val = r1String.compare(r2String);
+ if (cmp_val != 0 && sort_column_is_numeric_) {
+ // Custom column with numeric data (or something like a port number).
+ // Attempt to convert to numbers.
+ // XXX This is slow. Can we avoid doing this? Perhaps the actual
+ // values used for sorting should be cached too as QVariant[List].
+ // If so, we could consider using QCollatorSortKeys or similar
+ // for strings as well.
+ bool ok_r1, ok_r2;
+ double num_r1 = parseNumericColumn(r1String, &ok_r1);
+ double num_r2 = parseNumericColumn(r2String, &ok_r2);
+
+ if (!ok_r1 && !ok_r2) {
+ cmp_val = 0;
+ } else if (!ok_r1 || (ok_r2 && num_r1 < num_r2)) {
+ // either r1 is invalid (and sort it before others) or both
+ // r1 and r2 are valid (sort normally)
+ cmp_val = -1;
+ } else if (!ok_r2 || (num_r1 > num_r2)) {
+ cmp_val = 1;
+ }
+ }
+
+ if (cmp_val == 0) {
+ // All else being equal, compare column numbers.
+ cmp_val = frame_data_compare(sort_cap_file_->epan, r1->frameData(), r2->frameData(), COL_NUMBER);
+ }
+ }
+
+ if (sort_order_ == Qt::AscendingOrder) {
+ return cmp_val < 0;
+ } else {
+ return cmp_val > 0;
+ }
+}
+
+// Parses a field as a double. Handle values with suffixes ("12ms"), negative
+// values ("-1.23") and fields with multiple occurrences ("1,2"). Marks values
+// that do not contain any numeric value ("Unknown") as invalid.
+double PacketListModel::parseNumericColumn(const QString &val, bool *ok)
+{
+ QByteArray ba = val.toUtf8();
+ const char *strval = ba.constData();
+ gchar *end = NULL;
+ double num = g_ascii_strtod(strval, &end);
+ *ok = strval != end;
+ return num;
+}
+
+// ::data is const so we have to make changes here.
+void PacketListModel::emitItemHeightChanged(const QModelIndex &ih_index)
+{
+ if (!ih_index.isValid()) return;
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(ih_index.internalPointer());
+ if (!record) return;
+
+ if (record->lineCount() > max_line_count_) {
+ max_line_count_ = record->lineCount();
+ emit itemHeightChanged(ih_index);
+ }
+}
+
+int PacketListModel::rowCount(const QModelIndex &) const
+{
+ return static_cast<int>(visible_rows_.count());
+}
+
+int PacketListModel::columnCount(const QModelIndex &) const
+{
+ return prefs.num_cols;
+}
+
+QVariant PacketListModel::data(const QModelIndex &d_index, int role) const
+{
+ if (!d_index.isValid())
+ return QVariant();
+
+ PacketListRecord *record = static_cast<PacketListRecord*>(d_index.internalPointer());
+ if (!record)
+ return QVariant();
+ const frame_data *fdata = record->frameData();
+ if (!fdata)
+ return QVariant();
+
+ switch (role) {
+ case Qt::TextAlignmentRole:
+ switch(recent_get_column_xalign(d_index.column())) {
+ case COLUMN_XALIGN_RIGHT:
+ return Qt::AlignRight;
+ break;
+ case COLUMN_XALIGN_CENTER:
+ return Qt::AlignCenter;
+ break;
+ case COLUMN_XALIGN_LEFT:
+ return Qt::AlignLeft;
+ break;
+ case COLUMN_XALIGN_DEFAULT:
+ default:
+ if (right_justify_column(d_index.column(), cap_file_)) {
+ return Qt::AlignRight;
+ }
+ break;
+ }
+ return Qt::AlignLeft;
+
+ case Qt::BackgroundRole:
+ const color_t *color;
+ if (fdata->ignored) {
+ color = &prefs.gui_ignored_bg;
+ } else if (fdata->marked) {
+ color = &prefs.gui_marked_bg;
+ } else if (fdata->color_filter && recent.packet_list_colorize) {
+ const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
+ color = &color_filter->bg_color;
+ } else {
+ return QVariant();
+ }
+ return ColorUtils::fromColorT(color);
+ case Qt::ForegroundRole:
+ if (fdata->ignored) {
+ color = &prefs.gui_ignored_fg;
+ } else if (fdata->marked) {
+ color = &prefs.gui_marked_fg;
+ } else if (fdata->color_filter && recent.packet_list_colorize) {
+ const color_filter_t *color_filter = (const color_filter_t *) fdata->color_filter;
+ color = &color_filter->fg_color;
+ } else {
+ return QVariant();
+ }
+ return ColorUtils::fromColorT(color);
+ case Qt::DisplayRole:
+ {
+ int column = d_index.column();
+ QString column_string = record->columnString(cap_file_, column, true);
+ // We don't know an item's sizeHint until we fetch its text here.
+ // Assume each line count is 1. If the line count changes, emit
+ // itemHeightChanged which triggers another redraw (including a
+ // fetch of SizeHintRole and DisplayRole) in the next event loop.
+ if (column == 0 && record->lineCountChanged() && record->lineCount() > max_line_count_) {
+ emit maxLineCountChanged(d_index);
+ }
+ return column_string;
+ }
+ case Qt::SizeHintRole:
+ {
+ // If this is the first row and column, return the maximum row height...
+ if (d_index.row() < 1 && d_index.column() < 1 && max_row_height_ > 0) {
+ QSize size = QSize(-1, max_row_height_);
+ return size;
+ }
+ // ...otherwise punt so that the item delegate can correctly calculate the item width.
+ return QVariant();
+ }
+ default:
+ return QVariant();
+ }
+}
+
+QVariant PacketListModel::headerData(int section, Qt::Orientation orientation,
+ int role) const
+{
+ if (!cap_file_) return QVariant();
+
+ if (orientation == Qt::Horizontal && section < prefs.num_cols) {
+ switch (role) {
+ case Qt::DisplayRole:
+ return QVariant::fromValue(QString(get_column_title(section)));
+ case Qt::ToolTipRole:
+ return QVariant::fromValue(gchar_free_to_qstring(get_column_tooltip(section)));
+ case PacketListModel::HEADER_CAN_RESOLVE:
+ return (bool)resolve_column(section, cap_file_);
+ default:
+ break;
+ }
+ }
+
+ return QVariant();
+}
+
+void PacketListModel::flushVisibleRows()
+{
+ int pos = static_cast<int>(visible_rows_.count());
+
+ if (new_visible_rows_.count() > 0) {
+ beginInsertRows(QModelIndex(), pos, pos + static_cast<int>(new_visible_rows_.count()));
+ foreach (PacketListRecord *record, new_visible_rows_) {
+ frame_data *fdata = record->frameData();
+
+ visible_rows_ << record;
+ if (static_cast<unsigned int>(number_to_row_.size()) <= fdata->num) {
+ number_to_row_.resize(fdata->num + 10000);
+ }
+ number_to_row_[fdata->num] = static_cast<int>(visible_rows_.count());
+ }
+ endInsertRows();
+ new_visible_rows_.resize(0);
+ }
+}
+
+// Fill our column string and colorization cache while the application is
+// idle. Try to be as conservative with the CPU and disk as possible.
+static const int idle_dissection_interval_ = 5; // ms
+void PacketListModel::dissectIdle(bool reset)
+{
+ if (reset) {
+// qDebug() << "=di reset" << idle_dissection_row_;
+ idle_dissection_row_ = 0;
+ } else if (!idle_dissection_timer_->isValid()) {
+ return;
+ }
+
+ idle_dissection_timer_->restart();
+
+ int first = idle_dissection_row_;
+ while (idle_dissection_timer_->elapsed() < idle_dissection_interval_
+ && idle_dissection_row_ < physical_rows_.count()) {
+ ensureRowColorized(idle_dissection_row_);
+ idle_dissection_row_++;
+// if (idle_dissection_row_ % 1000 == 0) qDebug() << "=di row" << idle_dissection_row_;
+ }
+
+ if (idle_dissection_row_ < physical_rows_.count()) {
+ QTimer::singleShot(0, this, [=]() { dissectIdle(); });
+ } else {
+ idle_dissection_timer_->invalidate();
+ }
+
+ // report colorization progress
+ emit bgColorizationProgress(first+1, idle_dissection_row_+1);
+}
+
+// XXX Pass in cinfo from packet_list_append so that we can fill in
+// line counts?
+gint PacketListModel::appendPacket(frame_data *fdata)
+{
+ PacketListRecord *record = new PacketListRecord(fdata);
+ qsizetype pos = -1;
+
+#ifdef DEBUG_PACKET_LIST_MODEL
+ if (fdata->num % 10000 == 1) {
+ log_resource_usage(fdata->num == 1, "%u packets", fdata->num);
+ }
+#endif
+
+ physical_rows_ << record;
+
+ if (fdata->passed_dfilter || fdata->ref_time) {
+ new_visible_rows_ << record;
+ if (new_visible_rows_.count() < 2) {
+ // This is the first queued packet. Schedule an insertion for
+ // the next UI update.
+ QTimer::singleShot(0, this, &PacketListModel::flushVisibleRows);
+ }
+ pos = static_cast<int>( visible_rows_.count() + new_visible_rows_.count() ) - 1;
+ }
+
+ return static_cast<gint>(pos);
+}
+
+frame_data *PacketListModel::getRowFdata(QModelIndex idx)
+{
+ if (!idx.isValid())
+ return Q_NULLPTR;
+ return getRowFdata(idx.row());
+}
+
+frame_data *PacketListModel::getRowFdata(int row) {
+ if (row < 0 || row >= visible_rows_.count())
+ return NULL;
+ PacketListRecord *record = visible_rows_[row];
+ if (!record)
+ return NULL;
+ return record->frameData();
+}
+
+void PacketListModel::ensureRowColorized(int row)
+{
+ if (row < 0 || row >= visible_rows_.count())
+ return;
+ PacketListRecord *record = visible_rows_[row];
+ if (!record)
+ return;
+ if (!record->colorized()) {
+ record->ensureColorized(cap_file_);
+ }
+}
+
+int PacketListModel::visibleIndexOf(frame_data *fdata) const
+{
+ int row = 0;
+ foreach (PacketListRecord *record, visible_rows_) {
+ if (record->frameData() == fdata) {
+ return row;
+ }
+ row++;
+ }
+
+ return -1;
+}
diff --git a/ui/qt/models/packet_list_model.h b/ui/qt/models/packet_list_model.h
new file mode 100644
index 00000000..807d8847
--- /dev/null
+++ b/ui/qt/models/packet_list_model.h
@@ -0,0 +1,130 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef PACKET_LIST_MODEL_H
+#define PACKET_LIST_MODEL_H
+
+#include <config.h>
+
+#include <stdio.h>
+
+#include <glib.h>
+
+#include <epan/packet.h>
+
+#include <QAbstractItemModel>
+#include <QFont>
+#include <QVector>
+
+#include <ui/qt/progress_frame.h>
+
+#include "packet_list_record.h"
+
+#include "cfile.h"
+
+class QElapsedTimer;
+
+class PacketListModel : public QAbstractItemModel
+{
+ Q_OBJECT
+public:
+
+ enum {
+ HEADER_CAN_RESOLVE = Qt::UserRole,
+ };
+
+ explicit PacketListModel(QObject *parent = 0, capture_file *cf = NULL);
+ ~PacketListModel();
+ void setCaptureFile(capture_file *cf);
+ QModelIndex index(int row, int column,
+ const QModelIndex & = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &) const;
+ int packetNumberToRow(int packet_num) const;
+ guint recreateVisibleRows();
+ void clear();
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex & = QModelIndex()) const;
+ QVariant data(const QModelIndex &d_index, int role) const;
+ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+
+ gint appendPacket(frame_data *fdata);
+ frame_data *getRowFdata(QModelIndex idx);
+ frame_data *getRowFdata(int row);
+ void ensureRowColorized(int row);
+ int visibleIndexOf(frame_data *fdata) const;
+ /**
+ * @brief Invalidate any cached column strings.
+ */
+ void invalidateAllColumnStrings();
+ /**
+ * @brief Rebuild columns from settings.
+ */
+ void resetColumns();
+ void resetColorized();
+ void toggleFrameMark(const QModelIndexList &indeces);
+ void setDisplayedFrameMark(gboolean set);
+ void toggleFrameIgnore(const QModelIndexList &indeces);
+ void setDisplayedFrameIgnore(gboolean set);
+ void toggleFrameRefTime(const QModelIndex &rt_index);
+ void unsetAllFrameRefTime();
+ void addFrameComment(const QModelIndexList &indices, const QByteArray &comment);
+ void setFrameComment(const QModelIndex &index, const QByteArray &comment, guint c_number);
+ void deleteFrameComments(const QModelIndexList &indices);
+ void deleteAllFrameComments();
+
+ void setMaximumRowHeight(int height);
+
+signals:
+ void goToPacket(int);
+ void maxLineCountChanged(const QModelIndex &ih_index) const;
+ void itemHeightChanged(const QModelIndex &ih_index);
+
+ void bgColorizationProgress(int first, int last);
+
+public slots:
+ void sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
+ void stopSorting();
+ void flushVisibleRows();
+ void dissectIdle(bool reset = false);
+
+private:
+ capture_file *cap_file_;
+ QList<QString> col_names_;
+ QVector<PacketListRecord *> physical_rows_;
+ QVector<PacketListRecord *> visible_rows_;
+ QVector<PacketListRecord *> new_visible_rows_;
+ QVector<int> number_to_row_;
+
+ int max_row_height_; // px
+ int max_line_count_;
+
+ static int sort_column_;
+ static int sort_column_is_numeric_;
+ static int text_sort_column_;
+ static Qt::SortOrder sort_order_;
+ static capture_file *sort_cap_file_;
+ static bool recordLessThan(PacketListRecord *r1, PacketListRecord *r2);
+ static double parseNumericColumn(const QString &val, bool *ok);
+
+ static gboolean stop_flag_;
+ static ProgressFrame *progress_frame_;
+ static double exp_comps_;
+ static double comps_;
+
+ QElapsedTimer *idle_dissection_timer_;
+ int idle_dissection_row_;
+
+ bool isNumericColumn(int column);
+
+private slots:
+ void emitItemHeightChanged(const QModelIndex &ih_index);
+};
+
+#endif // PACKET_LIST_MODEL_H
diff --git a/ui/qt/models/packet_list_record.cpp b/ui/qt/models/packet_list_record.cpp
new file mode 100644
index 00000000..9c2c66d1
--- /dev/null
+++ b/ui/qt/models/packet_list_record.cpp
@@ -0,0 +1,246 @@
+/* packet_list_record.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "packet_list_record.h"
+
+#include <file.h>
+
+#include <epan/epan_dissect.h>
+#include <epan/column.h>
+#include <epan/conversation.h>
+#include <epan/wmem_scopes.h>
+
+#include <epan/color_filters.h>
+
+#include "frame_tvbuff.h"
+
+#include <ui/qt/utils/qt_ui_utils.h>
+
+#include <QStringList>
+
+QCache<guint32, QStringList> PacketListRecord::col_text_cache_(500);
+QMap<int, int> PacketListRecord::cinfo_column_;
+unsigned PacketListRecord::rows_color_ver_ = 1;
+
+PacketListRecord::PacketListRecord(frame_data *frameData) :
+ fdata_(frameData),
+ lines_(1),
+ line_count_changed_(false),
+ color_ver_(0),
+ colorized_(false),
+ conv_index_(0),
+ read_failed_(false)
+{
+}
+
+PacketListRecord::~PacketListRecord()
+{
+}
+
+void PacketListRecord::ensureColorized(capture_file *cap_file)
+{
+ // packet_list_store.c:packet_list_get_value
+ Q_ASSERT(fdata_);
+
+ if (!cap_file) {
+ return;
+ }
+
+ bool dissect_color = !colorized_ || ( color_ver_ != rows_color_ver_ );
+ if (dissect_color) {
+ /* Dissect columns only if it won't evict anything from cache */
+ bool dissect_columns = col_text_cache_.totalCost() < col_text_cache_.maxCost();
+ dissect(cap_file, dissect_columns, dissect_color);
+ }
+}
+
+// We might want to return a const char * instead. This would keep us from
+// creating excessive QByteArrays, e.g. in PacketListModel::recordLessThan.
+const QString PacketListRecord::columnString(capture_file *cap_file, int column, bool colorized)
+{
+ // packet_list_store.c:packet_list_get_value
+ Q_ASSERT(fdata_);
+
+ if (!cap_file || column < 0 || column >= cap_file->cinfo.num_cols) {
+ return QString();
+ }
+
+ //
+ // XXX - do we still need to check the colorization, given that we now
+ // have the ensureColorized() method to ensure that the record is
+ // properly colorized?
+ //
+ bool dissect_color = ( colorized && !colorized_ ) || ( color_ver_ != rows_color_ver_ );
+ QStringList *col_text = nullptr;
+ if (!dissect_color) {
+ col_text = col_text_cache_.object(fdata_->num);
+ }
+ if (col_text == nullptr || column >= col_text->count() || col_text->at(column).isNull()) {
+ dissect(cap_file, true, dissect_color);
+ col_text = col_text_cache_.object(fdata_->num);
+ }
+
+ return col_text ? col_text->at(column) : QString();
+}
+
+void PacketListRecord::resetColumns(column_info *cinfo)
+{
+ invalidateAllRecords();
+
+ if (!cinfo) {
+ return;
+ }
+
+ cinfo_column_.clear();
+ int i, j;
+ for (i = 0, j = 0; i < cinfo->num_cols; i++) {
+ if (!col_based_on_frame_data(cinfo, i)) {
+ cinfo_column_[i] = j;
+ j++;
+ }
+ }
+}
+
+void PacketListRecord::dissect(capture_file *cap_file, bool dissect_columns, bool dissect_color)
+{
+ // packet_list_store.c:packet_list_dissect_and_cache_record
+ epan_dissect_t edt;
+ column_info *cinfo = NULL;
+ gboolean create_proto_tree;
+ wtap_rec rec; /* Record metadata */
+ Buffer buf; /* Record data */
+
+ if (!cap_file) {
+ return;
+ }
+
+ if (dissect_columns) {
+ cinfo = &cap_file->cinfo;
+ }
+
+ wtap_rec_init(&rec);
+ ws_buffer_init(&buf, 1514);
+ if (read_failed_) {
+ read_failed_ = !cf_read_record_no_alert(cap_file, fdata_, &rec, &buf);
+ } else {
+ read_failed_ = !cf_read_record(cap_file, fdata_, &rec, &buf);
+ }
+
+ if (read_failed_) {
+ /*
+ * Error reading the record.
+ *
+ * Don't set the color filter for now (we might want
+ * to colorize it in some fashion to warn that the
+ * row couldn't be filled in or colorized), and
+ * set the columns to placeholder values, except
+ * for the Info column, where we'll put in an
+ * error message.
+ */
+ if (dissect_columns) {
+ col_fill_in_error(cinfo, fdata_, FALSE, FALSE /* fill_fd_columns */);
+
+ cacheColumnStrings(cinfo);
+ }
+ if (dissect_color) {
+ fdata_->color_filter = NULL;
+ colorized_ = true;
+ }
+ ws_buffer_free(&buf);
+ wtap_rec_cleanup(&rec);
+ return; /* error reading the record */
+ }
+
+ /*
+ * Determine whether we need to create a protocol tree.
+ * We do if:
+ *
+ * we're going to apply a color filter to this packet;
+ *
+ * we're need to fill in the columns and we have custom columns
+ * (which require field values, which currently requires that
+ * we build a protocol tree).
+ *
+ * XXX - field extractors? (Not done for GTK+....)
+ */
+ create_proto_tree = ((dissect_color && color_filters_used()) ||
+ (dissect_columns && (have_custom_cols(cinfo) ||
+ have_field_extractors())));
+
+ epan_dissect_init(&edt, cap_file->epan,
+ create_proto_tree,
+ FALSE /* proto_tree_visible */);
+
+ /* Re-color when the coloring rules are changed via the UI. */
+ if (dissect_color) {
+ color_filters_prime_edt(&edt);
+ fdata_->need_colorize = 1;
+ }
+ if (dissect_columns)
+ col_custom_prime_edt(&edt, cinfo);
+
+ /*
+ * XXX - need to catch an OutOfMemoryError exception and
+ * attempt to recover from it.
+ */
+ epan_dissect_run(&edt, cap_file->cd_t, &rec,
+ frame_tvbuff_new_buffer(&cap_file->provider, fdata_, &buf),
+ fdata_, cinfo);
+
+ if (dissect_columns) {
+ /* "Stringify" non frame_data vals */
+ epan_dissect_fill_in_columns(&edt, FALSE, FALSE /* fill_fd_columns */);
+ cacheColumnStrings(cinfo);
+ }
+
+ if (dissect_color) {
+ colorized_ = true;
+ color_ver_ = rows_color_ver_;
+ }
+
+ struct conversation * conv = find_conversation_pinfo(&edt.pi, 0);
+ conv_index_ = ! conv ? 0 : conv->conv_index;
+
+ epan_dissect_cleanup(&edt);
+ ws_buffer_free(&buf);
+ wtap_rec_cleanup(&rec);
+}
+
+void PacketListRecord::cacheColumnStrings(column_info *cinfo)
+{
+ // packet_list_store.c:packet_list_change_record(PacketList *packet_list, PacketListRecord *record, gint col, column_info *cinfo)
+ if (!cinfo) {
+ return;
+ }
+
+ QStringList *col_text = new QStringList();
+
+ lines_ = 1;
+ line_count_changed_ = false;
+
+ for (int column = 0; column < cinfo->num_cols; ++column) {
+ int col_lines = 1;
+
+ QString col_str;
+ int text_col = cinfo_column_.value(column, -1);
+ if (text_col < 0) {
+ col_fill_in_frame_data(fdata_, cinfo, column, FALSE);
+ }
+
+ col_str = QString(get_column_text(cinfo, column));
+ *col_text << col_str;
+ col_lines = static_cast<int>(col_str.count('\n'));
+ if (col_lines > lines_) {
+ lines_ = col_lines;
+ line_count_changed_ = true;
+ }
+ }
+
+ col_text_cache_.insert(fdata_->num, col_text);
+}
diff --git a/ui/qt/models/packet_list_record.h b/ui/qt/models/packet_list_record.h
new file mode 100644
index 00000000..47aa5621
--- /dev/null
+++ b/ui/qt/models/packet_list_record.h
@@ -0,0 +1,84 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef PACKET_LIST_RECORD_H
+#define PACKET_LIST_RECORD_H
+
+#include <config.h>
+
+#include <glib.h>
+
+#include "cfile.h"
+
+#include <epan/column.h>
+#include <epan/packet.h>
+
+#include <QByteArray>
+#include <QCache>
+#include <QList>
+#include <QVariant>
+
+struct conversation;
+struct _GStringChunk;
+
+class PacketListRecord
+{
+public:
+ PacketListRecord(frame_data *frameData);
+ virtual ~PacketListRecord();
+
+ // Ensure that the record is colorized.
+ void ensureColorized(capture_file *cap_file);
+ // Return the string value for a column. Data is cached if possible.
+ const QString columnString(capture_file *cap_file, int column, bool colorized = false);
+ frame_data *frameData() const { return fdata_; }
+ // packet_list->col_to_text in gtk/packet_list_store.c
+ static int textColumn(int column) { return cinfo_column_.value(column, -1); }
+ bool colorized() { return colorized_ && (color_ver_ == rows_color_ver_); }
+ unsigned int conversation() { return conv_index_; }
+
+ int columnTextSize(const char *str);
+
+ void invalidateColorized() { colorized_ = false; }
+ void invalidateRecord() { col_text_cache_.remove(fdata_->num); }
+ static void invalidateAllRecords() { col_text_cache_.clear(); }
+ /* In Qt 6, QCache maxCost is a qsizetype, but the QAbstractItemModel
+ * number of rows is still an int, so we're limited to INT_MAX anyway.
+ */
+ static void setMaxCache(int cost) { col_text_cache_.setMaxCost(cost); }
+ static void resetColumns(column_info *cinfo);
+ static void resetColorization() { rows_color_ver_++; }
+
+ inline int lineCount() { return lines_; }
+ inline int lineCountChanged() { return line_count_changed_; }
+
+private:
+ /** The column text for some columns */
+ static QCache<guint32, QStringList> col_text_cache_;
+
+ frame_data *fdata_;
+ int lines_;
+ bool line_count_changed_;
+ static QMap<int, int> cinfo_column_;
+
+ /** Has this record been colorized? */
+ static unsigned int rows_color_ver_;
+ unsigned int color_ver_;
+ bool colorized_;
+
+ /** Conversation. Used by RelatedPacketDelegate */
+ unsigned int conv_index_;
+
+ bool read_failed_;
+
+ void dissect(capture_file *cap_file, bool dissect_columns, bool dissect_color = false);
+ void cacheColumnStrings(column_info *cinfo);
+};
+
+#endif // PACKET_LIST_RECORD_H
diff --git a/ui/qt/models/path_selection_delegate.cpp b/ui/qt/models/path_selection_delegate.cpp
new file mode 100644
index 00000000..df614ef3
--- /dev/null
+++ b/ui/qt/models/path_selection_delegate.cpp
@@ -0,0 +1,63 @@
+/* path_chooser_delegate.cpp
+ * Delegate to select a file path for a treeview entry
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/path_selection_delegate.h>
+#include <ui/qt/widgets/path_selection_edit.h>
+
+PathSelectionDelegate::PathSelectionDelegate(QObject *parent)
+ : QStyledItemDelegate(parent)
+{
+}
+
+QWidget* PathSelectionDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &, const QModelIndex &) const
+{
+ PathSelectionEdit * editor = new PathSelectionEdit(tr("Open a pipe"), QString(), true, parent);
+
+ connect(editor, &PathSelectionEdit::pathChanged, this, &PathSelectionDelegate::pathHasChanged);
+
+ return editor;
+}
+
+void PathSelectionDelegate::pathHasChanged(QString)
+{
+ PathSelectionEdit * editor = qobject_cast<PathSelectionEdit *>(sender());
+ if (editor)
+ emit commitData(editor);
+}
+
+void PathSelectionDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const
+{
+ editor->setGeometry(option.rect);
+}
+
+void PathSelectionDelegate::setEditorData(QWidget *editor, const QModelIndex &idx) const
+{
+ if (idx.isValid() && qobject_cast<PathSelectionEdit *>(editor) != nullptr)
+ {
+ PathSelectionEdit * edit = qobject_cast<PathSelectionEdit *>(editor);
+ edit->setPath(idx.data().toString());
+ }
+ else
+ QStyledItemDelegate::setEditorData(editor, idx);
+}
+
+void PathSelectionDelegate::setModelData(QWidget *editor, QAbstractItemModel * model, const QModelIndex &idx) const
+{
+ if (idx.isValid() && qobject_cast<PathSelectionEdit *>(editor) != nullptr)
+ {
+ PathSelectionEdit * edit = qobject_cast<PathSelectionEdit *>(editor);
+ model->setData(idx, edit->path());
+ }
+ else
+ {
+ QStyledItemDelegate::setModelData(editor, model, idx);
+ }
+}
+
diff --git a/ui/qt/models/path_selection_delegate.h b/ui/qt/models/path_selection_delegate.h
new file mode 100644
index 00000000..6d33f116
--- /dev/null
+++ b/ui/qt/models/path_selection_delegate.h
@@ -0,0 +1,35 @@
+/** @file
+ *
+ * Delegate to select a file path for a treeview entry
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef PATH_SELECTION_DELEGATE_H_
+#define PATH_SELECTION_DELEGATE_H_
+
+#include <QStyledItemDelegate>
+
+class PathSelectionDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ PathSelectionDelegate(QObject *parent = 0);
+
+protected:
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &idx) const override;
+ void updateEditorGeometry (QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & idx) const override;
+ void setEditorData(QWidget *editor, const QModelIndex &idx) const override;
+ void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &idx) const override;
+
+protected slots:
+ void pathHasChanged(QString newPath);
+
+};
+
+#endif /* PATH_SELECTION_DELEGATE_H_ */
diff --git a/ui/qt/models/percent_bar_delegate.cpp b/ui/qt/models/percent_bar_delegate.cpp
new file mode 100644
index 00000000..091c5fb0
--- /dev/null
+++ b/ui/qt/models/percent_bar_delegate.cpp
@@ -0,0 +1,91 @@
+/* percent_bar_delegate.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/percent_bar_delegate.h>
+
+#include <ui/qt/utils/color_utils.h>
+
+#include <QApplication>
+#include <QPainter>
+
+static const int bar_em_width_ = 8;
+static const double bar_blend_ = 0.15;
+
+void PercentBarDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ QStyleOptionViewItem option_vi = option;
+ QStyledItemDelegate::initStyleOption(&option_vi, index);
+
+ // Paint our rect with no text using the current style, then draw our
+ // bar and text over it.
+ QStyledItemDelegate::paint(painter, option, index);
+
+ bool ok = false;
+ double value = index.data(Qt::UserRole).toDouble(&ok);
+
+ if (!ok || !index.data(Qt::DisplayRole).toString().isEmpty()) {
+ // We don't have a valid value or the item has visible text.
+ return;
+ }
+
+ // If our value is out range our caller has a bug. Clamp the graph and
+ // Print the numeric value so that the bug is obvious.
+ QString pct_str = QString::number(value, 'f', 1);
+ if (value < 0) {
+ value = 0;
+ }
+ if (value > 100.0) {
+ value = 100.0;
+ }
+
+ if (QApplication::style()->objectName().contains("vista")) {
+ // QWindowsVistaStyle::drawControl does this internally. Unfortunately there
+ // doesn't appear to be a more general way to do this.
+ option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText,
+ option_vi.palette.color(QPalette::Active, QPalette::Text));
+ }
+
+ QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled
+ ? QPalette::Normal : QPalette::Disabled;
+ QColor text_color = option_vi.palette.color(cg, QPalette::Text);
+ QColor bar_color = ColorUtils::alphaBlend(option_vi.palette.windowText(),
+ option_vi.palette.window(), bar_blend_);
+
+ if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+ if (option_vi.state & QStyle::State_Selected) {
+ text_color = option_vi.palette.color(cg, QPalette::HighlightedText);
+ bar_color = ColorUtils::alphaBlend(option_vi.palette.color(cg, QPalette::Window),
+ option_vi.palette.color(cg, QPalette::Highlight),
+ bar_blend_);
+ }
+
+ painter->save();
+ int border_radius = 3; // We use 3 px elsewhere, e.g. filter combos.
+ QRect pct_rect = option.rect;
+ pct_rect.adjust(1, 1, -1, -1);
+ pct_rect.setWidth(((pct_rect.width() * value) / 100.0) + 0.5);
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(bar_color);
+ painter->drawRoundedRect(pct_rect, border_radius, border_radius);
+ painter->restore();
+
+ painter->save();
+ painter->setPen(text_color);
+ painter->drawText(option.rect, Qt::AlignCenter, pct_str);
+ painter->restore();
+}
+
+QSize PercentBarDelegate::sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ return QSize(option.fontMetrics.height() * bar_em_width_,
+ QStyledItemDelegate::sizeHint(option, index).height());
+}
diff --git a/ui/qt/models/percent_bar_delegate.h b/ui/qt/models/percent_bar_delegate.h
new file mode 100644
index 00000000..4f0d53df
--- /dev/null
+++ b/ui/qt/models/percent_bar_delegate.h
@@ -0,0 +1,52 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef PERCENTBARDELEGATE_H
+#define PERCENTBARDELEGATE_H
+
+/*
+ * @file Percent bar delegate.
+ *
+ * QStyledItemDelegate subclass that will draw a percentage value and a
+ * single-item bar chart for the specified value.
+ *
+ * This is intended to be used in QTreeWidgets to show percentage values.
+ * To use it, first call setItemDelegate:
+ *
+ * myTreeWidget()->setItemDelegateForColumn(col_pct_, new PercentBarDelegate());
+ *
+ * Then, for each QTreeWidgetItem, set a double value using setData:
+ *
+ * setData(col_pct_, Qt::UserRole, QVariant::fromValue<double>(packets_ * 100.0 / num_packets));
+ *
+ * If the item data cannot be converted to a valid double value or if its
+ * text string is non-empty then it will be rendered normally (i.e. the
+ * percent text and bar will not be drawn). This lets you mix normal and
+ * percent bar rendering between rows.
+ */
+
+#include <QStyledItemDelegate>
+
+class PercentBarDelegate : public QStyledItemDelegate
+{
+public:
+ PercentBarDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) { }
+
+ // Make sure QStyledItemDelegate::paint doesn't draw any text.
+ virtual QString displayText(const QVariant &, const QLocale &) const { return QString(); }
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+ QSize sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+
+};
+
+#endif // PERCENTBARDELEGATE_H
diff --git a/ui/qt/models/pref_delegate.cpp b/ui/qt/models/pref_delegate.cpp
new file mode 100644
index 00000000..e33bb13f
--- /dev/null
+++ b/ui/qt/models/pref_delegate.cpp
@@ -0,0 +1,85 @@
+/* pref_delegate.cpp
+ * Delegates for editing prefereneces.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/pref_delegate.h>
+#include <epan/prefs-int.h>
+
+#include <ui/qt/manager/preference_manager.h>
+#include <ui/qt/manager/wireshark_preference.h>
+
+AdvancedPrefDelegate::AdvancedPrefDelegate(QObject *parent) : QStyledItemDelegate(parent)
+{
+}
+
+PrefsItem* AdvancedPrefDelegate::indexToPref(const QModelIndex &index) const
+{
+ const QVariant v = index.model()->data(index, Qt::UserRole);
+ return VariantPointer<PrefsItem>::asPtr(v);
+}
+
+QWidget *AdvancedPrefDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ PrefsItem* pref;
+ QString filename;
+
+ switch(index.column())
+ {
+ case AdvancedPrefsModel::colName:
+ case AdvancedPrefsModel::colStatus:
+ case AdvancedPrefsModel::colType:
+ //If user clicks on any of these columns, reset preference back to default
+ //There is no need to launch an editor
+ const_cast<QAbstractItemModel*>(index.model())->setData(index, QVariant(), Qt::EditRole);
+ break;
+ case AdvancedPrefsModel::colValue:
+ pref = indexToPref(index);
+ WiresharkPreference * wspref = PreferenceManager::instance()->getPreference(pref);
+ if (wspref) {
+ QWidget *editor = wspref->editor(parent, option, index);
+ if (editor) {
+ editor->setAutoFillBackground(true);
+ }
+ return editor;
+ }
+ break;
+ }
+
+ return Q_NULLPTR;
+}
+
+void AdvancedPrefDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+ PrefsItem* pref = indexToPref(index);
+
+ WiresharkPreference * wspref = PreferenceManager::instance()->getPreference(pref);
+ if (wspref)
+ {
+ wspref->setData(editor, index);
+ return;
+ }
+
+ Q_ASSERT(FALSE);
+}
+
+void AdvancedPrefDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const
+{
+ PrefsItem* pref = indexToPref(index);
+
+ WiresharkPreference * wspref = PreferenceManager::instance()->getPreference(pref);
+ if (wspref)
+ {
+ wspref->setModelData(editor, model, index);
+ return;
+ }
+
+ Q_ASSERT(FALSE);
+}
diff --git a/ui/qt/models/pref_delegate.h b/ui/qt/models/pref_delegate.h
new file mode 100644
index 00000000..91054e9a
--- /dev/null
+++ b/ui/qt/models/pref_delegate.h
@@ -0,0 +1,37 @@
+/** @file
+ *
+ * Delegates for editing prefereneces.
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef PREF_DELEGATE_H
+#define PREF_DELEGATE_H
+
+#include <config.h>
+
+#include <ui/qt/models/pref_models.h>
+
+#include <QStyledItemDelegate>
+#include <QModelIndex>
+
+class AdvancedPrefDelegate : public QStyledItemDelegate
+{
+public:
+ AdvancedPrefDelegate(QObject *parent = 0);
+
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+ void setEditorData(QWidget *editor, const QModelIndex &index) const;
+ void setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const;
+
+private:
+ PrefsItem* indexToPref(const QModelIndex &index) const;
+};
+
+#endif // PREF_DELEGATE_H
diff --git a/ui/qt/models/pref_models.cpp b/ui/qt/models/pref_models.cpp
new file mode 100644
index 00000000..b86230f0
--- /dev/null
+++ b/ui/qt/models/pref_models.cpp
@@ -0,0 +1,774 @@
+/* pref_models.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/pref_models.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <epan/prefs-int.h>
+
+#ifdef HAVE_LIBPCAP
+#ifdef _WIN32
+#include "capture/capture-wpcap.h"
+#endif /* _WIN32 */
+#endif /* HAVE_LIBPCAP */
+
+#include <QFont>
+#include <QColor>
+#include <QRegularExpression>
+#include <QApplication>
+
+// XXX Should we move this to ui/preference_utils?
+static GHashTable * pref_ptr_to_pref_ = NULL;
+pref_t *prefFromPrefPtr(void *pref_ptr)
+{
+ return (pref_t *)g_hash_table_lookup(pref_ptr_to_pref_, (gpointer) pref_ptr);
+}
+
+static void prefInsertPrefPtr(void * pref_ptr, pref_t * pref)
+{
+ if (! pref_ptr_to_pref_)
+ pref_ptr_to_pref_ = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, NULL);
+
+ gpointer key = (gpointer) pref_ptr;
+ gpointer val = (gpointer) pref;
+
+ /* Already existing entries will be ignored */
+ if ((void *)g_hash_table_lookup(pref_ptr_to_pref_, key) == NULL)
+ g_hash_table_insert(pref_ptr_to_pref_, key, val);
+}
+
+PrefsItem::PrefsItem(module_t *module, pref_t *pref, PrefsItem* parent)
+ : ModelHelperTreeItem<PrefsItem>(parent),
+ pref_(pref),
+ module_(module),
+ name_(module->name ? module->name : module->parent->name),
+ changed_(false)
+{
+ if (pref_ != NULL) {
+ name_ += QString(".%1").arg(prefs_get_name(pref_));
+ }
+}
+
+PrefsItem::PrefsItem(const QString name, PrefsItem* parent)
+ : ModelHelperTreeItem<PrefsItem>(parent),
+ pref_(NULL),
+ module_(NULL),
+ name_(name),
+ changed_(false)
+{
+
+}
+
+PrefsItem::~PrefsItem()
+{
+}
+
+int PrefsItem::getPrefType() const
+{
+ if (pref_ == NULL)
+ return 0;
+
+ return prefs_get_type(pref_);
+}
+
+bool PrefsItem::isPrefDefault() const
+{
+ if (pref_ == NULL)
+ return true;
+
+ if (changed_ == false)
+ return prefs_pref_is_default(pref_) ? true : false;
+
+ return false;
+}
+
+QString PrefsItem::getPrefTypeName() const
+{
+ if (pref_ == NULL)
+ return "";
+
+ return QString(prefs_pref_type_name(pref_));
+}
+
+QString PrefsItem::getModuleName() const
+{
+ if (module_ == NULL)
+ return name_;
+
+ return QString(module_->name);
+}
+
+QString PrefsItem::getModuleTitle() const
+{
+ if ((module_ == NULL) && (pref_ == NULL))
+ return name_;
+
+ Q_ASSERT(module_);
+
+ return QString(module_->title);
+}
+
+void PrefsItem::setChanged(bool changed)
+{
+ changed_ = changed;
+}
+
+PrefsModel::PrefsModel(QObject *parent) :
+ QAbstractItemModel(parent),
+ root_(new PrefsItem(QString("ROOT"), NULL))
+{
+ populate();
+}
+
+PrefsModel::~PrefsModel()
+{
+ delete root_;
+}
+
+int PrefsModel::rowCount(const QModelIndex &parent) const
+{
+ PrefsItem *parent_item;
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<PrefsItem*>(parent.internalPointer());
+
+ if (parent_item == NULL)
+ return 0;
+
+ return static_cast<int>(parent_item->childCount());
+}
+
+int PrefsModel::columnCount(const QModelIndex&) const
+{
+ return colLast;
+}
+
+
+QModelIndex PrefsModel::parent(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ PrefsItem* item = static_cast<PrefsItem*>(index.internalPointer());
+ if (item != NULL) {
+ PrefsItem* parent_item = item->parentItem();
+ if (parent_item != NULL) {
+ if (parent_item == root_)
+ return QModelIndex();
+
+ return createIndex(parent_item->row(), 0, parent_item);
+ }
+ }
+
+ return QModelIndex();
+}
+
+QModelIndex PrefsModel::index(int row, int column, const QModelIndex& parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+
+ PrefsItem *parent_item, *child_item;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<PrefsItem*>(parent.internalPointer());
+
+ Q_ASSERT(parent_item);
+
+ child_item = parent_item->child(row);
+ if (child_item) {
+ return createIndex(row, column, child_item);
+ }
+
+ return QModelIndex();
+}
+
+QVariant PrefsModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || (role != Qt::DisplayRole && role != Qt::UserRole))
+ return QVariant();
+
+ PrefsItem* item = static_cast<PrefsItem*>(index.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ if (role == Qt::UserRole)
+ return VariantPointer<PrefsItem>::asQVariant(item);
+
+ switch ((enum PrefsModelColumn)index.column()) {
+ case colName:
+ return item->getName();
+
+ case colStatus:
+ if (item->getPrefType() == PREF_UAT || item->getPrefType() == PREF_CUSTOM)
+ return QObject::tr("Unknown");
+
+ if (item->isPrefDefault())
+ return QObject::tr("Default");
+
+ return QObject::tr("Changed");
+ case colType:
+ return item->getPrefTypeName();
+ case colValue:
+ if (item->getPref() == NULL)
+ return QVariant();
+
+ return QString(gchar_free_to_qstring(prefs_pref_to_str(item->getPref(), pref_stashed)).remove(QRegularExpression("\n\t")));
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+static guint
+fill_prefs(module_t *module, gpointer root_ptr)
+{
+ PrefsItem* root_item = static_cast<PrefsItem*>(root_ptr);
+
+ if ((module == NULL) || (root_item == NULL))
+ return 1;
+
+ if (module->numprefs < 1 && !prefs_module_has_submodules(module))
+ return 0;
+
+ PrefsItem* module_item = new PrefsItem(module, NULL, root_item);
+ root_item->prependChild(module_item);
+
+ for (GList *pref_l = module->prefs; pref_l && pref_l->data; pref_l = gxx_list_next(pref_l)) {
+ pref_t *pref = gxx_list_data(pref_t *, pref_l);
+
+ if (prefs_get_type(pref) == PREF_OBSOLETE || prefs_get_type(pref) == PREF_STATIC_TEXT)
+ continue;
+
+ const char *type_name = prefs_pref_type_name(pref);
+ if (!type_name)
+ continue;
+
+ pref_stash(pref, NULL);
+
+ PrefsItem* item = new PrefsItem(module, pref, module_item);
+ module_item->prependChild(item);
+
+ // .uat is a void * so it wins the "useful key value" prize.
+ if (prefs_get_uat_value(pref)) {
+ prefInsertPrefPtr(prefs_get_uat_value(pref), pref);
+ }
+ }
+
+ if (prefs_module_has_submodules(module))
+ return prefs_modules_foreach_submodules(module, fill_prefs, module_item);
+
+ return 0;
+}
+
+void PrefsModel::populate()
+{
+ prefs_modules_foreach_submodules(NULL, fill_prefs, (gpointer)root_);
+
+ //Add the "specially handled" preferences
+ PrefsItem *appearance_item, *appearance_subitem, *special_item;
+
+ appearance_item = new PrefsItem(typeToString(PrefsModel::Appearance), root_);
+ root_->prependChild(appearance_item);
+
+ appearance_subitem = new PrefsItem(typeToString(PrefsModel::Layout), appearance_item);
+ appearance_item->prependChild(appearance_subitem);
+ appearance_subitem = new PrefsItem(typeToString(PrefsModel::Columns), appearance_item);
+ appearance_item->prependChild(appearance_subitem);
+ appearance_subitem = new PrefsItem(typeToString(PrefsModel::FontAndColors), appearance_item);
+ appearance_item->prependChild(appearance_subitem);
+
+ special_item = new PrefsItem(typeToString(PrefsModel::Capture), root_);
+ root_->prependChild(special_item);
+ special_item = new PrefsItem(typeToString(PrefsModel::Expert), root_);
+ root_->prependChild(special_item);
+ special_item = new PrefsItem(typeToString(PrefsModel::FilterButtons), root_);
+ root_->prependChild(special_item);
+#ifdef HAVE_LIBGNUTLS
+ special_item = new PrefsItem(typeToString(PrefsModel::RSAKeys), root_);
+ root_->prependChild(special_item);
+#endif
+ special_item = new PrefsItem(typeToString(PrefsModel::Advanced), root_);
+ root_->prependChild(special_item);
+}
+
+QString PrefsModel::typeToString(int type)
+{
+ QString typeStr;
+
+ switch(type)
+ {
+ case Advanced: typeStr = tr("Advanced"); break;
+ case Appearance: typeStr = tr("Appearance"); break;
+ case Layout: typeStr = tr("Layout"); break;
+ case Columns: typeStr = tr("Columns"); break;
+ case FontAndColors: typeStr = tr("Font and Colors"); break;
+ case Capture: typeStr = tr("Capture"); break;
+ case Expert: typeStr = tr("Expert"); break;
+ case FilterButtons: typeStr = tr("Filter Buttons"); break;
+ case RSAKeys: typeStr = tr("RSA Keys"); break;
+ }
+
+ return typeStr;
+}
+
+AdvancedPrefsModel::AdvancedPrefsModel(QObject * parent)
+: QSortFilterProxyModel(parent),
+filter_(),
+show_changed_values_(false),
+passwordChar_(QApplication::style()->styleHint(QStyle::SH_LineEdit_PasswordCharacter))
+{
+}
+
+QVariant AdvancedPrefsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+
+ switch (section) {
+ case colName:
+ return tr("Name");
+ case colStatus:
+ return tr("Status");
+ case colType:
+ return tr("Type");
+ case colValue:
+ return tr("Value");
+ default:
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QVariant AdvancedPrefsModel::data(const QModelIndex &dataindex, int role) const
+{
+ if (!dataindex.isValid())
+ return QVariant();
+
+ QModelIndex modelIndex = mapToSource(dataindex);
+
+ PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch ((AdvancedPrefsModelColumn)dataindex.column())
+ {
+ case colName:
+ if (item->getPref() == NULL)
+ return item->getModule()->title;
+
+ return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colName, modelIndex.parent()), role);
+ case colStatus:
+ if (item->getPref() == NULL)
+ return QVariant();
+
+ return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colStatus, modelIndex.parent()), role);
+ case colType:
+ if (item->getPref() == NULL)
+ return QVariant();
+
+ return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colType, modelIndex.parent()), role);
+ case colValue:
+ if (item->getPref() == NULL)
+ return QVariant();
+
+ if (PREF_PASSWORD == item->getPrefType())
+ {
+ return QString(sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colValue, modelIndex.parent()), role).toString().size(), passwordChar_);
+ } else {
+ return sourceModel()->data(sourceModel()->index(modelIndex.row(), PrefsModel::colValue, modelIndex.parent()), role);
+ }
+ default:
+ break;
+ }
+ break;
+ case Qt::ToolTipRole:
+ switch ((AdvancedPrefsModelColumn)dataindex.column())
+ {
+ case colName:
+ if (item->getPref() == NULL)
+ return QString("<span>%1</span>").arg(item->getModule()->description);
+
+ return QString("<span>%1</span>").arg(prefs_get_description(item->getPref()));
+ case colStatus:
+ if (item->getPref() == NULL)
+ return QVariant();
+
+ return QObject::tr("Has this preference been changed?");
+ case colType:
+ if (item->getPref() == NULL) {
+ return QVariant();
+ } else {
+ QString type_desc = gchar_free_to_qstring(prefs_pref_type_description(item->getPref()));
+ return QString("<span>%1</span>").arg(type_desc);
+ }
+ break;
+ case colValue:
+ if (item->getPref() == NULL) {
+ return QVariant();
+ } else {
+ QString default_value = gchar_free_to_qstring(prefs_pref_to_str(item->getPref(), pref_stashed));
+ return QString("<span>%1</span>").arg(
+ default_value.isEmpty() ? default_value : QObject::tr("Default value is empty"));
+ }
+ default:
+ break;
+ }
+ break;
+ case Qt::FontRole:
+ if (item->getPref() == NULL)
+ return QVariant();
+
+ if (!item->isPrefDefault() &&
+ /* UATs and custom preferences are "unknown", that shouldn't mean that they are always bolded */
+ item->getPrefType() != PREF_UAT && item->getPrefType() != PREF_CUSTOM) {
+ QFont font;
+ font.setBold(true);
+ return font;
+ }
+ break;
+ case Qt::UserRole:
+ return sourceModel()->data(modelIndex, role);
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+bool AdvancedPrefsModel::setData(const QModelIndex &dataindex, const QVariant &value, int role)
+{
+ if ((!dataindex.isValid()) || (role != Qt::EditRole))
+ return false;
+
+ QModelIndex modelIndex = mapToSource(dataindex);
+
+ PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
+ if (item == NULL)
+ return false;
+
+ if (value.isNull()) {
+ //reset preference to default
+ reset_stashed_pref(item->getPref());
+ item->setChanged(false);
+ } else {
+ item->setChanged(true);
+ switch (item->getPrefType())
+ {
+ case PREF_DECODE_AS_UINT:
+ case PREF_UINT:
+ {
+ bool ok;
+ guint new_val = value.toString().toUInt(&ok, prefs_get_uint_base(item->getPref()));
+
+ if (ok)
+ prefs_set_uint_value(item->getPref(), new_val, pref_stashed);
+ }
+ break;
+ case PREF_BOOL:
+ prefs_invert_bool_value(item->getPref(), pref_stashed);
+ break;
+ case PREF_ENUM:
+ prefs_set_enum_value(item->getPref(), value.toInt(), pref_stashed);
+ break;
+ case PREF_STRING:
+ prefs_set_string_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed);
+ break;
+ case PREF_PASSWORD:
+ prefs_set_password_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed);
+ break;
+ case PREF_DECODE_AS_RANGE:
+ case PREF_RANGE:
+ prefs_set_stashed_range_value(item->getPref(), value.toString().toUtf8().constData());
+ break;
+ case PREF_SAVE_FILENAME:
+ case PREF_OPEN_FILENAME:
+ case PREF_DIRNAME:
+ prefs_set_string_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed);
+ break;
+ case PREF_COLOR:
+ {
+ QColor qc(value.toString());
+ color_t color;
+
+ color.red = qc.red() << 8 | qc.red();
+ color.green = qc.green() << 8 | qc.green();
+ color.blue = qc.blue() << 8 | qc.blue();
+
+ prefs_set_color_value(item->getPref(), color, pref_stashed);
+ break;
+ }
+ case PREF_CUSTOM:
+ prefs_set_custom_value(item->getPref(), value.toString().toStdString().c_str(), pref_stashed);
+ break;
+ }
+ }
+
+ QVector<int> roles;
+ roles << role;
+
+ // The status field may change as well as the value, so mark them for update
+ emit dataChanged(index(dataindex.row(), 0, dataindex.parent()),
+ index(dataindex.row(), columnCount() - 1, dataindex.parent()),
+ roles);
+
+ return true;
+}
+
+Qt::ItemFlags AdvancedPrefsModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::ItemFlags();
+
+ QModelIndex modelIndex = mapToSource(index);
+
+ PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
+ if (item == NULL)
+ return Qt::ItemFlags();
+
+ Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+ if (item->getPref() == NULL) {
+ /* Base modules aren't changable */
+ flags &= ~(Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+ } else {
+ flags |= Qt::ItemIsEditable;
+ }
+
+ return flags;
+}
+
+
+int AdvancedPrefsModel::columnCount(const QModelIndex&) const
+{
+ return colLast;
+}
+
+void AdvancedPrefsModel::setFirstColumnSpanned(QTreeView* tree, const QModelIndex& mIndex)
+{
+ int childCount, row;
+ PrefsItem* item;
+ if (mIndex.isValid()) {
+ item = VariantPointer<PrefsItem>::asPtr(data(mIndex, Qt::UserRole));
+ if (item != NULL) {
+ childCount = item->childCount();
+ if (childCount > 0) {
+ tree->setFirstColumnSpanned(mIndex.row(), mIndex.parent(), true);
+ for (row = 0; row < childCount; row++) {
+ setFirstColumnSpanned(tree, index(row, 0, mIndex));
+ }
+ }
+ }
+ } else {
+ for (row = 0; row < rowCount(); row++) {
+ setFirstColumnSpanned(tree, index(row, 0));
+ }
+ }
+}
+
+bool AdvancedPrefsModel::filterAcceptItem(PrefsItem& item) const
+{
+ if (filter_.isEmpty() && !show_changed_values_)
+ return true;
+
+ QString name, tooltip;
+ if (item.getPref() == NULL) {
+ name = item.getModule()->title;
+ tooltip = item.getModule()->description;
+ } else {
+ name = QString(item.getModule()->name ? item.getModule()->name : item.getModule()->parent->name);
+ name += QString(".%1").arg(prefs_get_name(item.getPref()));
+ tooltip = prefs_get_description(item.getPref());
+ }
+
+ if (show_changed_values_ && item.getPref()) {
+ // UATs and custom preferences are "unknown", do not show when show_changed_only.
+ if (item.isPrefDefault() || item.getPrefType() == PREF_UAT || item.getPrefType() == PREF_CUSTOM) {
+ return false;
+ } else if (filter_.isEmpty()) {
+ return true;
+ }
+ }
+
+ // Do not match module title or description when having show_changed_only.
+ if (!(filter_.isEmpty() || (show_changed_values_ && !item.getPref())) &&
+ (name.contains(filter_, Qt::CaseInsensitive) || tooltip.contains(filter_, Qt::CaseInsensitive)))
+ return true;
+
+ PrefsItem *child_item;
+ for (int child_row = 0; child_row < item.childCount(); child_row++)
+ {
+ child_item = item.child(child_row);
+ if ((child_item != NULL) && (filterAcceptItem(*child_item)))
+ return true;
+ }
+
+ return false;
+}
+
+bool AdvancedPrefsModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex nameIdx = sourceModel()->index(sourceRow, PrefsModel::colName, sourceParent);
+ PrefsItem* item = static_cast<PrefsItem*>(nameIdx.internalPointer());
+ if (item == NULL)
+ return true;
+
+ //filter out the "special" preferences
+ if ((item->getModule() == NULL) && (item->getPref() == NULL))
+ return false;
+
+ if (filterAcceptItem(*item))
+ return true;
+
+ return false;
+}
+
+void AdvancedPrefsModel::setFilter(const QString& filter)
+{
+ filter_ = filter;
+ invalidateFilter();
+}
+
+void AdvancedPrefsModel::setShowChangedValues(bool show_changed_values)
+{
+ show_changed_values_ = show_changed_values;
+ invalidateFilter();
+}
+
+
+
+ModulePrefsModel::ModulePrefsModel(QObject* parent)
+ : QSortFilterProxyModel(parent)
+ , advancedPrefName_(PrefsModel::typeToString(PrefsModel::Advanced))
+{
+}
+
+QVariant ModulePrefsModel::data(const QModelIndex &dataindex, int role) const
+{
+ if (!dataindex.isValid())
+ return QVariant();
+
+ QModelIndex modelIndex = mapToSource(dataindex);
+
+ PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ switch ((ModulePrefsModelColumn)dataindex.column())
+ {
+ case colName:
+ return item->getModuleTitle();
+ default:
+ break;
+ }
+ break;
+ case Qt::UserRole:
+ return sourceModel()->data(modelIndex, role);
+ case ModuleName:
+ return item->getModuleName();
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+Qt::ItemFlags ModulePrefsModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::ItemFlags();
+
+ bool disable_capture = true;
+#ifdef HAVE_LIBPCAP
+#ifdef _WIN32
+ /* Is WPcap loaded? */
+ if (has_wpcap) {
+#endif /* _WIN32 */
+ disable_capture = false;
+#ifdef _WIN32
+ }
+#endif /* _WIN32 */
+#endif /* HAVE_LIBPCAP */
+
+ Qt::ItemFlags flags = QAbstractItemModel::flags(index);
+ if (disable_capture) {
+ QModelIndex modelIndex = mapToSource(index);
+
+ PrefsItem* item = static_cast<PrefsItem*>(modelIndex.internalPointer());
+ if (item == NULL)
+ return flags;
+
+ if (item->getName().compare(PrefsModel::typeToString(PrefsModel::Capture)) == 0) {
+ flags &= (~Qt::ItemIsEnabled);
+ }
+ }
+
+ return flags;
+}
+
+int ModulePrefsModel::columnCount(const QModelIndex&) const
+{
+ return colLast;
+}
+
+bool ModulePrefsModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
+{
+ PrefsItem* left_item = static_cast<PrefsItem*>(source_left.internalPointer());
+ PrefsItem* right_item = static_cast<PrefsItem*>(source_right.internalPointer());
+
+ if ((left_item != NULL) && (right_item != NULL)) {
+ QString left_name = left_item->getModuleTitle(),
+ right_name = right_item->getModuleTitle();
+
+ //Force "Advanced" preferences to be at bottom of model
+ if (source_left.isValid() && !source_left.parent().isValid() &&
+ source_right.isValid() && !source_right.parent().isValid()) {
+ if (left_name.compare(advancedPrefName_) == 0) {
+ return false;
+ }
+ if (right_name.compare(advancedPrefName_) == 0) {
+ return true;
+ }
+ }
+
+ if (left_name.compare(right_name, Qt::CaseInsensitive) < 0)
+ return true;
+ }
+
+ return false;
+}
+
+bool ModulePrefsModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex nameIdx = sourceModel()->index(sourceRow, PrefsModel::colName, sourceParent);
+ PrefsItem* item = static_cast<PrefsItem*>(nameIdx.internalPointer());
+ if (item == NULL)
+ return true;
+
+ if (item->getPref() != NULL)
+ return false;
+
+ if (item->getModule() != NULL) {
+ if (!item->getModule()->use_gui) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/ui/qt/models/pref_models.h b/ui/qt/models/pref_models.h
new file mode 100644
index 00000000..775da0d0
--- /dev/null
+++ b/ui/qt/models/pref_models.h
@@ -0,0 +1,165 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef PREF_MODELS_H
+#define PREF_MODELS_H
+
+#include <config.h>
+
+#include <ui/qt/models/tree_model_helpers.h>
+
+#include <epan/prefs.h>
+
+#include <QSortFilterProxyModel>
+#include <QTreeView>
+
+class PrefsItem : public ModelHelperTreeItem<PrefsItem>
+{
+public:
+ PrefsItem(module_t *module, pref_t *pref, PrefsItem* parent);
+ PrefsItem(const QString name, PrefsItem* parent);
+ virtual ~PrefsItem();
+
+ QString getName() const {return name_;}
+ pref_t* getPref() const {return pref_;}
+ int getPrefType() const;
+ bool isPrefDefault() const;
+ QString getPrefTypeName() const;
+ module_t* getModule() const {return module_;}
+ QString getModuleName() const;
+ QString getModuleTitle() const;
+ void setChanged(bool changed = true);
+
+private:
+ pref_t *pref_;
+ module_t *module_;
+ QString name_;
+ //set to true if changed during module manipulation
+ //Used to determine proper "default" for comparison
+ bool changed_;
+};
+
+
+class PrefsModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit PrefsModel(QObject * parent = Q_NULLPTR);
+ virtual ~PrefsModel();
+
+ enum PrefsModelType {
+ Advanced = Qt::UserRole,
+ Appearance,
+ Layout,
+ Columns,
+ FontAndColors,
+ Capture,
+ Expert,
+ FilterButtons,
+ RSAKeys
+ };
+
+ enum PrefsModelColumn {
+ colName = 0,
+ colStatus,
+ colType,
+ colValue,
+ colLast
+ };
+
+ QModelIndex index(int row, int column,
+ const QModelIndex & = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &) const;
+ QVariant data(const QModelIndex &index, int role) const;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ static QString typeToString(int type);
+
+private:
+ void populate();
+
+ PrefsItem* root_;
+};
+
+class AdvancedPrefsModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ explicit AdvancedPrefsModel(QObject * parent = Q_NULLPTR);
+
+ enum AdvancedPrefsModelColumn {
+ colName = 0,
+ colStatus,
+ colType,
+ colValue,
+ colLast
+ };
+
+ virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
+
+ void setFilter(const QString& filter);
+ void setShowChangedValues(bool show_changed_values);
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ QVariant data(const QModelIndex &index, int role) const;
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ //Keep the internals of model hidden from tree
+ void setFirstColumnSpanned(QTreeView* tree, const QModelIndex &index = QModelIndex());
+
+protected:
+ bool filterAcceptItem(PrefsItem& item) const;
+
+private:
+
+ QString filter_;
+ bool show_changed_values_;
+ const QChar passwordChar_;
+};
+
+class ModulePrefsModel : public QSortFilterProxyModel
+{
+public:
+
+ explicit ModulePrefsModel(QObject * parent = Q_NULLPTR);
+
+ enum ModulePrefsModelColumn {
+ colName = 0,
+ colLast
+ };
+
+ enum ModulePrefsRoles {
+ ModuleName = Qt::UserRole + 1
+ };
+
+ QVariant data(const QModelIndex &index, int role) const;
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
+
+protected:
+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
+
+private:
+ //cache of the translated "Advanced" preference name
+ QString advancedPrefName_;
+};
+
+extern pref_t *prefFromPrefPtr(void *pref_ptr);
+
+#endif // PREF_MODELS_H
diff --git a/ui/qt/models/profile_model.cpp b/ui/qt/models/profile_model.cpp
new file mode 100644
index 00000000..206ef389
--- /dev/null
+++ b/ui/qt/models/profile_model.cpp
@@ -0,0 +1,1299 @@
+/* profile_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "config.h"
+
+#include <errno.h>
+
+#include "glib.h"
+#include "ui/profile.h"
+#include "ui/recent.h"
+#include "wsutil/filesystem.h"
+#include "epan/prefs.h"
+
+#include <ui/qt/models/profile_model.h>
+
+#include <ui/qt/utils/color_utils.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+#include <ui/qt/utils/wireshark_zip_helper.h>
+
+#include <QDir>
+#include <QFont>
+#include <QTemporaryDir>
+#include <QRegularExpression>
+
+Q_LOGGING_CATEGORY(profileLogger, "wireshark.profiles")
+
+ProfileSortModel::ProfileSortModel(QObject * parent):
+ QSortFilterProxyModel (parent),
+ ft_(ProfileSortModel::AllProfiles),
+ ftext_(QString())
+{}
+
+bool ProfileSortModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
+{
+ QModelIndex left = source_left;
+ if (source_left.column() != ProfileModel::COL_NAME)
+ left = source_left.sibling(source_left.row(), ProfileModel::COL_NAME);
+
+ QModelIndex right = source_right;
+ if (source_right.column() != ProfileModel::COL_NAME)
+ right = source_right.sibling(source_right.row(), ProfileModel::COL_NAME);
+
+ bool igL = left.data(ProfileModel::DATA_IS_GLOBAL).toBool();
+ bool igR = right.data(ProfileModel::DATA_IS_GLOBAL).toBool();
+
+ if (left.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT)
+ igL = true;
+ if (right.data(ProfileModel::DATA_STATUS).toInt() == PROF_STAT_DEFAULT)
+ igR = true;
+
+ if (igL && ! igR)
+ return true;
+ else if (! igL && igR)
+ return false;
+ else if (igL && igR)
+ {
+ if (left.data(ProfileModel::DATA_STATUS) == PROF_STAT_DEFAULT)
+ return true;
+ }
+
+ if (left.data().toString().compare(right.data().toString()) <= 0)
+ return true;
+
+ return false;
+}
+
+void ProfileSortModel::setFilterType(FilterType ft)
+{
+ ft_ = ft;
+ invalidateFilter();
+}
+
+void ProfileSortModel::setFilterString(QString txt)
+{
+ ftext_ = ! txt.isEmpty() ? txt : "";
+ invalidateFilter();
+}
+
+QStringList ProfileSortModel::filterTypes()
+{
+ QMap<int, QString> filter_types_;
+ filter_types_.insert(ProfileSortModel::AllProfiles, tr("All profiles"));
+ filter_types_.insert(ProfileSortModel::PersonalProfiles, tr("Personal profiles"));
+ filter_types_.insert(ProfileSortModel::GlobalProfiles, tr("Global profiles"));
+
+ return filter_types_.values();
+}
+
+bool ProfileSortModel::filterAcceptsRow(int source_row, const QModelIndex &) const
+{
+ bool accept = true;
+ QModelIndex idx = sourceModel()->index(source_row, ProfileModel::COL_NAME);
+
+ if (ft_ != ProfileSortModel::AllProfiles)
+ {
+ bool gl = idx.data(ProfileModel::DATA_IS_GLOBAL).toBool();
+ if (ft_ == ProfileSortModel::PersonalProfiles && gl)
+ accept = false;
+ else if (ft_ == ProfileSortModel::GlobalProfiles && ! gl)
+ accept = false;
+ }
+
+ if (ftext_.length() > 0)
+ {
+ QString name = idx.data().toString();
+ if (! name.contains(ftext_, Qt::CaseInsensitive))
+ accept = false;
+ }
+
+ return accept;
+}
+
+ProfileModel::ProfileModel(QObject * parent) :
+ QAbstractTableModel(parent)
+{
+ /* Store preset profile name */
+ set_profile_ = get_profile_name();
+
+ reset_default_ = false;
+ profiles_imported_ = false;
+
+ last_set_row_ = 0;
+
+ /* Set filenames for profiles */
+ GList *files, *file;
+ files = g_hash_table_get_keys(const_cast<GHashTable *>(allowed_profile_filenames()));
+ file = g_list_first(files);
+ while (file) {
+ profile_files_ << static_cast<char *>(file->data);
+ file = gxx_list_next(file);
+ }
+ g_list_free(files);
+
+ loadProfiles();
+}
+
+void ProfileModel::loadProfiles()
+{
+ beginResetModel();
+
+ bool refresh = profiles_.count() > 0;
+
+ if (refresh)
+ profiles_.clear();
+ else
+ init_profile_list();
+
+ GList *fl_entry = edited_profile_list();
+ while (fl_entry && fl_entry->data)
+ {
+ profiles_ << reinterpret_cast<profile_def *>(fl_entry->data);
+ fl_entry = gxx_list_next(fl_entry);
+ }
+
+ endResetModel();
+}
+
+GList * ProfileModel::entry(profile_def *ref) const
+{
+ GList *fl_entry = edited_profile_list();
+ while (fl_entry && fl_entry->data)
+ {
+ profile_def *profile = reinterpret_cast<profile_def *>(fl_entry->data);
+ if (QString(ref->name).compare(profile->name) == 0 &&
+ QString(ref->reference).compare(profile->reference) == 0 &&
+ ref->is_global == profile->is_global &&
+ ref->status == profile->status)
+ {
+ return fl_entry;
+ }
+
+ fl_entry = gxx_list_next(fl_entry);
+ }
+
+ return Q_NULLPTR;
+}
+
+GList *ProfileModel::at(int row) const
+{
+ if (row < 0 || row >= profiles_.count())
+ return Q_NULLPTR;
+
+ profile_def * prof = profiles_.at(row);
+ return entry(prof);
+}
+
+bool ProfileModel::changesPending() const
+{
+ if (reset_default_)
+ return true;
+
+ if (g_list_length(edited_profile_list()) != g_list_length(current_profile_list()))
+ return true;
+
+ bool pending = false;
+ GList *fl_entry = edited_profile_list();
+ while (fl_entry && fl_entry->data && ! pending) {
+ profile_def *profile = reinterpret_cast<profile_def *>(fl_entry->data);
+ pending = (profile->status == PROF_STAT_NEW || profile->status == PROF_STAT_CHANGED || profile->status == PROF_STAT_COPY);
+ fl_entry = gxx_list_next(fl_entry);
+ }
+
+ return pending;
+}
+
+bool ProfileModel::importPending() const
+{
+ return profiles_imported_;
+}
+
+bool ProfileModel::userProfilesExist() const
+{
+ bool user_exists = false;
+ for (int cnt = 0; cnt < rowCount() && ! user_exists; cnt++)
+ {
+ QModelIndex idx = index(cnt, ProfileModel::COL_NAME);
+ if (! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool() && ! idx.data(ProfileModel::DATA_IS_DEFAULT).toBool())
+ user_exists = true;
+ }
+
+ return user_exists;
+}
+
+int ProfileModel::rowCount(const QModelIndex &) const
+{
+ return static_cast<int>(profiles_.count());
+}
+
+int ProfileModel::columnCount(const QModelIndex &) const
+{
+ return static_cast<int>(_LAST_ENTRY);
+}
+
+profile_def * ProfileModel::guard(const QModelIndex &index) const
+{
+ if (! index.isValid())
+ return Q_NULLPTR;
+
+ return guard(index.row());
+}
+
+profile_def * ProfileModel::guard(int row) const
+{
+ if (row < 0 || profiles_.count() <= row)
+ return Q_NULLPTR;
+
+ if (! edited_profile_list())
+ {
+ static_cast<QList<profile_def *>>(profiles_).clear();
+ return Q_NULLPTR;
+ }
+
+ return profiles_.value(row, Q_NULLPTR);
+}
+
+QVariant ProfileModel::dataDisplay(const QModelIndex &index) const
+{
+ profile_def * prof = guard(index);
+ if (! prof)
+ return QVariant();
+
+ switch (index.column())
+ {
+ case COL_NAME:
+ return QString(prof->name);
+ case COL_TYPE:
+ if (prof->status == PROF_STAT_DEFAULT)
+ return tr("Default");
+ else if (prof->is_global)
+ return tr("Global");
+ else
+ return tr("Personal");
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+QVariant ProfileModel::dataFontRole(const QModelIndex &index) const
+{
+ if (! index.isValid() || profiles_.count() <= index.row())
+ return QVariant();
+
+ profile_def * prof = guard(index.row());
+ if (! prof)
+ return QVariant();
+
+ QFont font;
+
+ if (prof->is_global)
+ font.setItalic(true);
+
+ if (! prof->is_global && ! checkDuplicate(index))
+ {
+ if ((set_profile_.compare(prof->name) == 0 && prof->status == PROF_STAT_EXISTS) ||
+ (set_profile_.compare(prof->reference) == 0 && prof->status == PROF_STAT_CHANGED) )
+ font.setBold(true);
+ }
+
+ if (prof->status == PROF_STAT_DEFAULT && reset_default_)
+ font.setStrikeOut(true);
+
+ return font;
+}
+
+bool ProfileModel::checkIfDeleted(int row) const
+{
+ QModelIndex idx = index(row, ProfileModel::COL_NAME);
+ return checkIfDeleted(idx);
+}
+
+bool ProfileModel::checkIfDeleted(const QModelIndex &index) const
+{
+ profile_def * prof = guard(index);
+ if (! prof)
+ return false;
+
+ QStringList deletedNames;
+
+ GList * current = current_profile_list();
+
+ /* search the current list as long as we have not found anything */
+ while (current)
+ {
+ bool found = false;
+ GList * edited = edited_profile_list();
+ profile_def * profcurr = static_cast<profile_def *>(current->data);
+
+ if (! profcurr->is_global && profcurr->status != PROF_STAT_DEFAULT)
+ {
+ while (edited && ! found)
+ {
+ profile_def * profed = static_cast<profile_def *>(edited->data);
+ if (! profed->is_global && profed->status != PROF_STAT_DEFAULT)
+ {
+ if (g_strcmp0(profcurr->name, profed->name) == 0 || g_strcmp0(profcurr->name, profed->reference) == 0)
+ {
+ if (profed->status == profcurr->status && prof->status != PROF_STAT_NEW && prof->status != PROF_STAT_COPY)
+ found = true;
+ }
+ }
+
+ edited = gxx_list_next(edited);
+ }
+
+ /* profile has been deleted, check if it has the name we ask for */
+ if (! found)
+ deletedNames << profcurr->name;
+ }
+
+ if (profcurr->is_global && deletedNames.contains(profcurr->name))
+ deletedNames.removeAll(profcurr->name);
+
+ current = gxx_list_next(current);
+ }
+
+ if (deletedNames.contains(prof->name))
+ return true;
+
+ return false;
+}
+
+bool ProfileModel::checkInvalid(const QModelIndex &index) const
+{
+ profile_def * prof = guard(index);
+ if (! prof)
+ return false;
+
+ int ref = this->findAsReference(prof->name);
+ if (ref == index.row())
+ return false;
+
+ profile_def * pg = guard(ref);
+ if (pg && pg->status == PROF_STAT_CHANGED && g_strcmp0(pg->name, pg->reference) != 0 && ! prof->is_global)
+ return true;
+
+ return false;
+}
+
+bool ProfileModel::checkDuplicate(const QModelIndex &index, bool isOriginalToDuplicate) const
+{
+ profile_def * prof = guard(index);
+ if (! prof || (! isOriginalToDuplicate && prof->status == PROF_STAT_EXISTS) )
+ return false;
+
+ QList<int> rows = this->findAllByNameAndVisibility(prof->name, prof->is_global, false);
+ int found = 0;
+ profile_def * check = Q_NULLPTR;
+ for (int cnt = 0; cnt < rows.count(); cnt++)
+ {
+ int row = rows.at(cnt);
+
+ if (row == index.row())
+ continue;
+
+ check = guard(row);
+ if (! check || (isOriginalToDuplicate && check->status == PROF_STAT_EXISTS) )
+ continue;
+
+ found++;
+ }
+
+ if (found > 0)
+ return true;
+ return false;
+}
+
+QVariant ProfileModel::dataBackgroundRole(const QModelIndex &index) const
+{
+ if (! index.isValid() || profiles_.count() <= index.row())
+ return QVariant();
+
+ profile_def * prof = guard(index.row());
+ if (! prof)
+ return QVariant();
+
+ if (prof->status == PROF_STAT_DEFAULT && reset_default_)
+ return ColorUtils::fromColorT(&prefs.gui_text_deprecated);
+
+ if (prof->status != PROF_STAT_DEFAULT && ! prof->is_global)
+ {
+ /* Highlights errorneous line */
+ if (checkInvalid(index) || checkIfDeleted(index) || checkDuplicate(index) || ! checkNameValidity(prof->name))
+ return ColorUtils::fromColorT(&prefs.gui_text_invalid);
+
+ /* Highlights line, which has been duplicated by another index */
+ if (checkDuplicate(index, true))
+ return ColorUtils::fromColorT(&prefs.gui_text_valid);
+ }
+
+ return QVariant();
+}
+
+QVariant ProfileModel::dataToolTipRole(const QModelIndex &idx) const
+{
+ if (! idx.isValid() || profiles_.count() <= idx.row())
+ return QVariant();
+
+ profile_def * prof = guard(idx.row());
+ if (! prof)
+ return QVariant();
+
+ if (prof->is_global)
+ return tr("This is a system provided profile");
+ else
+ return dataPath(idx);
+}
+
+QVariant ProfileModel::dataPath(const QModelIndex &index) const
+{
+ if (! index.isValid() || profiles_.count() <= index.row())
+ return QVariant();
+
+ profile_def * prof = guard(index.row());
+ if (! prof)
+ return QVariant();
+
+ if (checkInvalid(index))
+ {
+ int ref = this->findAsReference(prof->name);
+ if (ref != index.row() && ref >= 0)
+ {
+ profile_def * prof = guard(ref);
+ QString msg = tr("A profile change for this name is pending");
+ if (prof)
+ msg.append(tr(" (See: %1)").arg(prof->name));
+ return msg;
+ }
+
+ return tr("This is an invalid profile definition");
+ }
+
+ if ((prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_CHANGED || prof->status == PROF_STAT_COPY) && checkDuplicate(index))
+ return tr("A profile already exists with this name");
+
+ if (checkIfDeleted(index))
+ {
+ return tr("A profile with this name is being deleted");
+ }
+
+ if (prof->is_import)
+ return tr("Imported profile");
+
+ switch (prof->status)
+ {
+ case PROF_STAT_DEFAULT:
+ if (!reset_default_)
+ return gchar_free_to_qstring(get_persconffile_path("", FALSE));
+ else
+ return tr("Resetting to default");
+ case PROF_STAT_EXISTS:
+ {
+ QString profile_path;
+ if (prof->is_global) {
+ profile_path = gchar_free_to_qstring(get_global_profiles_dir());
+ } else {
+ profile_path = gchar_free_to_qstring(get_profiles_dir());
+ }
+ profile_path.append("/").append(prof->name);
+ return QDir::toNativeSeparators(profile_path);
+ }
+ case PROF_STAT_NEW:
+ {
+ QString errMsg;
+
+ if (! checkNameValidity(prof->name, &errMsg))
+ return errMsg;
+ else
+ return tr("Created from default settings");
+ }
+ case PROF_STAT_CHANGED:
+ {
+ QString msg;
+ if (! ProfileModel::checkNameValidity(QString(prof->name), &msg))
+ return msg;
+
+ if (prof->reference)
+ return tr("Renamed from: %1").arg(prof->reference);
+
+ return QVariant();
+ }
+ case PROF_STAT_COPY:
+ {
+ QString msg;
+
+ /* this should always be the case, but just as a precaution it is checked */
+ if (prof->reference)
+ {
+ msg = tr("Copied from: %1").arg(prof->reference);
+ QString appendix;
+
+ /* A global profile is neither deleted or removed, only system provided is allowed as appendix */
+ if (profile_exists(prof->reference, TRUE) && prof->from_global)
+ appendix = tr("system provided");
+ /* A default model as reference can neither be deleted or renamed, so skip if the reference was one */
+ else if (! index.data(ProfileModel::DATA_IS_DEFAULT).toBool())
+ {
+ /* find a non-global, non-default profile which could be referenced by this one. Those are the only
+ * ones which could be renamed or deleted */
+ int row = this->findByNameAndVisibility(prof->reference, false, true);
+ profile_def * ref = guard(row);
+
+ /* The reference is itself a copy of the original, therefore it is not accepted */
+ if (ref && (ref->status == PROF_STAT_COPY || ref->status == PROF_STAT_NEW) && QString(ref->name).compare(prof->reference) != 0)
+ ref = Q_NULLPTR;
+
+ /* found no other profile, original one had to be deleted */
+ if (! ref || row == index.row() || checkIfDeleted(row))
+ {
+ appendix = tr("deleted");
+ }
+ /* found another profile, so the reference had been renamed, it the status is changed */
+ else if (ref && ref->status == PROF_STAT_CHANGED)
+ {
+ appendix = tr("renamed to %1").arg(ref->name);
+ }
+ }
+
+ if (appendix.length() > 0)
+ msg.append(QString(" (%1)").arg(appendix));
+ }
+
+ return msg;
+ }
+ }
+
+ return QVariant();
+}
+
+QVariant ProfileModel::data(const QModelIndex &index, int role) const
+{
+ profile_def * prof = guard(index);
+ if (! prof)
+ return QVariant();
+
+ switch (role)
+ {
+ case Qt::DisplayRole:
+ return dataDisplay(index);
+ case Qt::FontRole:
+ return dataFontRole(index);
+ case Qt::BackgroundRole:
+ return dataBackgroundRole(index);
+ case Qt::ToolTipRole:
+ return dataToolTipRole(index);
+ case ProfileModel::DATA_STATUS:
+ return QVariant::fromValue(prof->status);
+ case ProfileModel::DATA_IS_DEFAULT:
+ return QVariant::fromValue(prof->status == PROF_STAT_DEFAULT);
+ case ProfileModel::DATA_IS_GLOBAL:
+ return QVariant::fromValue(prof->is_global);
+ case ProfileModel::DATA_IS_SELECTED:
+ {
+ QModelIndex selected = activeProfile();
+ profile_def * selprof = guard(selected);
+ if (selprof)
+ {
+ if (selprof && selprof->is_global != prof->is_global)
+ return QVariant::fromValue(false);
+
+ if (selprof && strcmp(selprof->name, prof->name) == 0)
+ return QVariant::fromValue(true);
+ }
+ return QVariant::fromValue(false);
+ }
+ case ProfileModel::DATA_PATH:
+ return dataPath(index);
+ case ProfileModel::DATA_INDEX_VALUE_IS_URL:
+ if (index.column() <= ProfileModel::COL_TYPE)
+ return QVariant::fromValue(false);
+ return QVariant::fromValue(true);
+ case ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION:
+ if (prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_COPY
+ || (prof->status == PROF_STAT_DEFAULT && reset_default_)
+ || prof->status == PROF_STAT_CHANGED || prof->is_import)
+ return QVariant::fromValue(false);
+ else
+ return QVariant::fromValue(true);
+
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+QVariant ProfileModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
+ {
+ switch (section)
+ {
+ case COL_NAME:
+ return tr("Profile");
+ case COL_TYPE:
+ return tr("Type");
+ default:
+ break;
+ }
+ }
+
+ return QVariant();
+}
+
+Qt::ItemFlags ProfileModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags fl = QAbstractTableModel::flags(index);
+
+ profile_def * prof = guard(index);
+ if (! prof)
+ return fl;
+
+ if (index.column() == ProfileModel::COL_NAME && prof->status != PROF_STAT_DEFAULT && ! prof->is_global)
+ fl |= Qt::ItemIsEditable;
+
+ return fl;
+}
+
+int ProfileModel::findByName(QString name)
+{
+ int row = findByNameAndVisibility(name, false);
+ if (row < 0)
+ row = findByNameAndVisibility(name, true);
+
+ return row;
+}
+
+int ProfileModel::findAsReference(QString reference) const
+{
+ int found = -1;
+ if (reference.length() <= 0)
+ return found;
+
+ for (int cnt = 0; cnt < profiles_.count() && found < 0; cnt++)
+ {
+ profile_def * prof = guard(cnt);
+ if (prof && reference.compare(prof->reference) == 0)
+ found = cnt;
+ }
+
+ return found;
+}
+
+int ProfileModel::findByNameAndVisibility(QString name, bool isGlobal, bool searchReference) const
+{
+ QList<int> result = findAllByNameAndVisibility(name, isGlobal, searchReference);
+ return result.count() == 0 ? -1 : result.at(0);
+}
+
+QList<int> ProfileModel::findAllByNameAndVisibility(QString name, bool isGlobal, bool searchReference) const
+{
+ QList<int> result;
+
+ for (int cnt = 0; cnt < profiles_.count(); cnt++)
+ {
+ profile_def * prof = guard(cnt);
+ if (prof && static_cast<bool>(prof->is_global) == isGlobal)
+ {
+ if (name.compare(prof->name) == 0 || (searchReference && name.compare(prof->reference) == 0) )
+ result << cnt;
+ }
+ }
+
+ return result;
+
+}
+
+QModelIndex ProfileModel::addNewProfile(QString name)
+{
+ int cnt = 1;
+ QString newName = name;
+ while (findByNameAndVisibility(newName) >= 0)
+ {
+ newName = QString("%1 %2").arg(name).arg(QString::number(cnt));
+ cnt++;
+ }
+
+ add_to_profile_list(newName.toUtf8().constData(), newName.toUtf8().constData(), PROF_STAT_NEW, FALSE, FALSE, FALSE);
+ loadProfiles();
+
+ return index(findByName(newName), COL_NAME);
+}
+
+QModelIndex ProfileModel::duplicateEntry(QModelIndex idx, int new_status)
+{
+ profile_def * prof = guard(idx);
+ if (! prof)
+ return QModelIndex();
+
+ /* only new and copied stati can be set */
+ if (new_status != PROF_STAT_NEW && new_status != PROF_STAT_COPY)
+ new_status = PROF_STAT_COPY;
+
+ /* this is a copy from a personal profile, check if the original has been a
+ * new profile or a preexisting one. In the case of a new profile, restart
+ * with the state PROF_STAT_NEW */
+ if (prof->status == PROF_STAT_COPY && ! prof->from_global)
+ {
+ int row = findByNameAndVisibility(prof->reference, false);
+ profile_def * copyParent = guard(row);
+ if (copyParent && copyParent->status == PROF_STAT_NEW)
+ return duplicateEntry(index(row, ProfileModel::COL_NAME), PROF_STAT_NEW);
+ }
+
+ /* Rules for figuring out the name to copy from:
+ *
+ * General, use copy name
+ * If status of copy is new or changed => use copy reference
+ * If copy is non global and status of copy is != changed, use original parent name
+ */
+ QString parent = prof->name;
+ if (prof->status == PROF_STAT_CHANGED)
+ parent = prof->reference;
+ else if (! prof->is_global && prof->status != PROF_STAT_NEW && prof->status != PROF_STAT_CHANGED)
+ parent = get_profile_parent (prof->name);
+
+ if (parent.length() == 0)
+ return QModelIndex();
+
+ /* parent references the parent profile to be used, parentName is the base for the new name */
+ QString parentName = parent;
+ /* the user has changed the profile name, therefore this is also the name to be used */
+ if (prof->status != PROF_STAT_EXISTS)
+ parentName = prof->name;
+
+ /* check to ensure we do not end up with (copy) (copy) (copy) ... */
+ QRegularExpression rx("\\s+(\\(\\s*" + tr("copy", "noun") + "\\s*\\d*\\))");
+ parentName.replace(rx, "");
+
+ QString new_name;
+ /* if copy is global and name has not been used before, use that, else create first copy */
+ if (prof->is_global && findByNameAndVisibility(parentName) < 0)
+ new_name = QString(prof->name);
+ else
+ new_name = QString("%1 (%2)").arg(parentName).arg(tr("copy", "noun"));
+
+ /* check if copy already exists and iterate, until an unused version is found */
+ int cnt = 1;
+ while (findByNameAndVisibility(new_name) >= 0)
+ {
+ new_name = QString("%1 (%2 %3)").arg(parentName).arg(tr("copy", "noun")).arg(QString::number(cnt));
+ cnt++;
+ }
+
+ /* if this would be a copy, but the original is already a new one, this is a copy as well */
+ if (new_status == PROF_STAT_COPY && prof->status == PROF_STAT_NEW)
+ new_status = PROF_STAT_NEW;
+
+ /* add element */
+ add_to_profile_list(new_name.toUtf8().constData(), parent.toUtf8().constData(), new_status, FALSE, prof->from_global ? prof->from_global : prof->is_global, FALSE);
+
+ /* reload profile list in model */
+ loadProfiles();
+
+ int row = findByNameAndVisibility(new_name, false);
+ /* sanity check, if adding the profile went correctly */
+ if (row < 0 || row == idx.row())
+ return QModelIndex();
+
+ /* return the index of the profile */
+ return index(row, COL_NAME);
+}
+
+void ProfileModel::deleteEntry(QModelIndex idx)
+{
+ if (! idx.isValid())
+ return;
+
+ QModelIndexList temp;
+ temp << idx;
+ deleteEntries(temp);
+}
+
+void ProfileModel::deleteEntries(QModelIndexList idcs)
+{
+ bool changes = false;
+
+ QList<int> indeces;
+ foreach (QModelIndex idx, idcs)
+ {
+ if (! indeces.contains(idx.row()) && ! idx.data(ProfileModel::DATA_IS_GLOBAL).toBool())
+ indeces << idx.row();
+ }
+ /* Security blanket. This ensures, that we start deleting from the end and do not get any issues iterating the list */
+ std::sort(indeces.begin(), indeces.end(), std::less<int>());
+
+ foreach (int row, indeces)
+ {
+ profile_def * prof = guard(row);
+ if (! prof)
+ continue;
+
+ if (prof->is_global)
+ continue;
+
+ if (prof->status == PROF_STAT_DEFAULT)
+ {
+ reset_default_ = ! reset_default_;
+ }
+ else
+ {
+ GList * fl_entry = entry(prof);
+ if (fl_entry)
+ {
+ changes = true;
+ remove_from_profile_list(fl_entry);
+ }
+ }
+ }
+
+ if (changes)
+ loadProfiles();
+
+ if (reset_default_)
+ {
+ emit layoutAboutToBeChanged();
+ emit dataChanged(index(0, 0), index(rowCount(), columnCount()));
+ emit layoutChanged();
+ }
+}
+
+bool ProfileModel::resetDefault() const
+{
+ return reset_default_;
+}
+
+void ProfileModel::doResetModel(bool reset_import)
+{
+ reset_default_ = false;
+ if (reset_import)
+ profiles_imported_ = false;
+
+ loadProfiles();
+}
+
+QModelIndex ProfileModel::activeProfile() const
+{
+ QList<int> rows = this->findAllByNameAndVisibility(set_profile_, false, true);
+ foreach (int row, rows)
+ {
+ profile_def * prof = profiles_.at(row);
+ if (prof->is_global || checkDuplicate(index(row, ProfileModel::COL_NAME)) )
+ return QModelIndex();
+
+ if ((set_profile_.compare(prof->name) == 0 && (prof->status == PROF_STAT_EXISTS || prof->status == PROF_STAT_DEFAULT) ) ||
+ (set_profile_.compare(prof->reference) == 0 && prof->status == PROF_STAT_CHANGED) )
+ return index(row, ProfileModel::COL_NAME);
+ }
+
+ return QModelIndex();
+}
+
+bool ProfileModel::setData(const QModelIndex &idx, const QVariant &value, int role)
+{
+ last_set_row_ = -1;
+
+ if (role != Qt::EditRole || ! value.isValid() || value.toString().isEmpty())
+ return false;
+
+ QString newValue = value.toString();
+ profile_def * prof = guard(idx);
+ if (! prof || prof->status == PROF_STAT_DEFAULT)
+ return false;
+
+ last_set_row_ = idx.row();
+
+ QString current(prof->name);
+ if (current.compare(newValue) != 0)
+ {
+ g_free(prof->name);
+ prof->name = qstring_strdup(newValue);
+
+ if (prof->reference && g_strcmp0(prof->name, prof->reference) == 0 && ! (prof->status == PROF_STAT_NEW || prof->status == PROF_STAT_COPY)) {
+ prof->status = PROF_STAT_EXISTS;
+ } else if (prof->status == PROF_STAT_EXISTS) {
+ prof->status = PROF_STAT_CHANGED;
+ }
+ emit itemChanged(idx);
+ }
+
+ loadProfiles();
+
+ return true;
+}
+
+int ProfileModel::lastSetRow() const
+{
+ return last_set_row_;
+}
+
+bool ProfileModel::copyTempToProfile(QString tempPath, QString profilePath, bool * wasEmpty)
+{
+ bool was_empty = true;
+
+ QDir profileDir(profilePath);
+ if (! profileDir.mkpath(profilePath) || ! QFile::exists(tempPath))
+ return false;
+
+ QDir tempProfile(tempPath);
+ tempProfile.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);
+ QFileInfoList files = tempProfile.entryInfoList();
+ if (files.count() <= 0)
+ return false;
+
+ int created = 0;
+ foreach (QFileInfo finfo, files)
+ {
+ QString tempFile = finfo.absoluteFilePath();
+ QString profileFile = profilePath + "/" + finfo.fileName();
+
+ if (! profile_files_.contains(finfo.fileName()))
+ {
+ was_empty = false;
+ continue;
+ }
+
+ if (! QFile::exists(tempFile) || QFile::exists(profileFile))
+ continue;
+
+ if (QFile::copy(tempFile, profileFile))
+ created++;
+ }
+
+ if (wasEmpty)
+ *wasEmpty = was_empty;
+
+ if (created > 0)
+ return true;
+
+ return false;
+}
+
+QFileInfoList ProfileModel::uniquePaths(QFileInfoList lst)
+{
+ QStringList files;
+ QFileInfoList newLst;
+
+ foreach (QFileInfo entry, lst)
+ {
+ if (! files.contains(entry.absoluteFilePath()))
+ {
+ if (entry.exists() && entry.isDir())
+ {
+ newLst << QFileInfo(entry.absoluteFilePath());
+ files << entry.absoluteFilePath();
+ }
+ }
+ }
+
+ return newLst;
+}
+
+QFileInfoList ProfileModel::filterProfilePath(QString path, QFileInfoList ent, bool fromZip)
+{
+ QFileInfoList result = ent;
+ QDir temp(path);
+ temp.setSorting(QDir::Name);
+ temp.setFilter(QDir::Dirs | QDir::NoSymLinks | QDir::NoDotAndDotDot);
+ QFileInfoList entries = temp.entryInfoList();
+ if (! fromZip)
+ entries << QFileInfo(path);
+ foreach (QFileInfo entry, entries)
+ {
+ QDir fPath(entry.absoluteFilePath());
+ fPath.setSorting(QDir::Name);
+ fPath.setFilter(QDir::Files | QDir::NoSymLinks);
+ QFileInfoList fEntries = fPath.entryInfoList();
+ bool found = false;
+ for (int cnt = 0; cnt < fEntries.count() && ! found; cnt++)
+ {
+ if (config_file_exists_with_entries(fEntries[cnt].absoluteFilePath().toUtf8().constData(), '#'))
+ found = true;
+ }
+
+ if (found)
+ {
+ result.append(entry);
+ }
+ else
+ {
+ if (path.compare(entry.absoluteFilePath()) != 0)
+ result.append(filterProfilePath(entry.absoluteFilePath(), result, fromZip));
+ }
+ }
+
+ return result;
+}
+
+#ifdef HAVE_MINIZIP
+QStringList ProfileModel::exportFileList(QModelIndexList items)
+{
+ QStringList result;
+
+ foreach(QModelIndex idx, items)
+ {
+ profile_def * prof = guard(idx);
+ if (! prof || prof->is_global || QString(prof->name).compare(DEFAULT_PROFILE) == 0)
+ continue;
+
+ if (! idx.data(ProfileModel::DATA_PATH_IS_NOT_DESCRIPTION).toBool())
+ continue;
+
+ QString path = idx.data(ProfileModel::DATA_PATH).toString();
+ QDir temp(path);
+ temp.setSorting(QDir::Name);
+ temp.setFilter(QDir::Files | QDir::NoSymLinks | QDir::NoDotAndDotDot);
+ QFileInfoList entries = temp.entryInfoList();
+ foreach (QFileInfo fi, entries)
+ result << fi.absoluteFilePath();
+ }
+
+ return result;
+}
+
+bool ProfileModel::exportProfiles(QString filename, QModelIndexList items, QString *err)
+{
+ if (changesPending())
+ {
+ if (err)
+ err->append(tr("Exporting profiles while changes are pending is not allowed"));
+ return false;
+ }
+
+ /* Write recent file for current profile before exporting */
+ write_profile_recent();
+
+ QStringList files = exportFileList(items);
+ if (files.count() == 0)
+ {
+ if (err)
+ err->append((tr("No profiles found to export")));
+ return false;
+ }
+
+ if (WiresharkZipHelper::zip(filename, files, gchar_free_to_qstring(get_profiles_dir()) + "/") )
+ return true;
+
+ return false;
+}
+
+/* This check runs BEFORE the file has been unzipped! */
+bool ProfileModel::acceptFile(QString fileName, int fileSize)
+{
+ if (fileName.toLower().endsWith(".zip"))
+ return false;
+
+ /* Arbitrary maximum config file size accepted: 256MB */
+ if (fileSize > 1024 * 1024 * 256)
+ return false;
+
+ return true;
+}
+
+QString ProfileModel::cleanName(QString fileName)
+{
+ QStringList parts = fileName.split("/");
+ QString temp = parts[parts.count() - 1].replace(QRegularExpression("[" + QRegularExpression::escape(illegalCharacters()) + "]"), QString("_") );
+ temp = parts.join("/");
+ return temp;
+}
+
+int ProfileModel::importProfilesFromZip(QString filename, int * skippedCnt, QStringList *result)
+{
+ QTemporaryDir dir;
+#if 0
+ dir.setAutoRemove(false);
+ g_printerr("Temp dir for unzip: %s\n", dir.path().toUtf8().constData());
+#endif
+
+ int cnt = 0;
+ if (dir.isValid())
+ {
+ WiresharkZipHelper::unzip(filename, dir.path(), &ProfileModel::acceptFile, &ProfileModel::cleanName);
+ cnt = importProfilesFromDir(dir.path(), skippedCnt, true, result);
+ }
+
+ return cnt;
+}
+#endif
+
+int ProfileModel::importProfilesFromDir(QString dirname, int * skippedCnt, bool fromZip, QStringList *result)
+{
+ int count = 0;
+ int skipped = 0;
+ QDir profileDir(gchar_free_to_qstring(get_profiles_dir()));
+ QDir dir(dirname);
+
+ if (skippedCnt)
+ *skippedCnt = 0;
+
+ if (dir.exists())
+ {
+ QFileInfoList entries = uniquePaths(filterProfilePath(dirname, QFileInfoList(), fromZip));
+
+ foreach (QFileInfo fentry, entries)
+ {
+ if (fentry.fileName().length() <= 0)
+ continue;
+
+ bool wasEmpty = true;
+ bool success = false;
+
+ QString profilePath = profileDir.absolutePath() + "/" + fentry.fileName();
+ QString tempPath = fentry.absoluteFilePath();
+
+ if (fentry.fileName().compare(DEFAULT_PROFILE, Qt::CaseInsensitive) == 0 || QFile::exists(profilePath))
+ {
+ skipped++;
+ continue;
+ }
+
+ if (result)
+ *result << fentry.fileName();
+
+ success = copyTempToProfile(tempPath, profilePath, &wasEmpty);
+ if (success)
+ {
+ count++;
+ add_to_profile_list(fentry.fileName().toUtf8().constData(), fentry.fileName().toUtf8().constData(), PROF_STAT_NEW, FALSE, FALSE, TRUE);
+ }
+ else if (! wasEmpty && QFile::exists(profilePath))
+ {
+ QDir dh(profilePath);
+ dh.rmdir(profilePath);
+ }
+ }
+
+ }
+
+ if (count > 0)
+ {
+ profiles_imported_ = true;
+ loadProfiles();
+ }
+
+ if (skippedCnt)
+ *skippedCnt = skipped;
+
+ return count;
+}
+
+void ProfileModel::markAsImported(QStringList importedItems)
+{
+ if (importedItems.count() <= 0)
+ return;
+
+ profiles_imported_ = true;
+
+ foreach (QString item, importedItems)
+ {
+ int row = findByNameAndVisibility(item, false);
+ profile_def * prof = guard(row);
+ if (! prof)
+ continue;
+
+ prof->is_import = true;
+ }
+}
+
+bool ProfileModel::clearImported(QString *msg)
+{
+ QList<int> rows;
+ bool result = true;
+ for (int cnt = 0; cnt < rowCount(); cnt++)
+ {
+ profile_def * prof = guard(cnt);
+ if (prof && prof->is_import && ! rows.contains(cnt))
+ rows << cnt;
+ }
+ /* Security blanket. This ensures, that we start deleting from the end and do not get any issues iterating the list */
+ std::sort(rows.begin(), rows.end(), std::less<int>());
+
+ char * ret_path = Q_NULLPTR;
+ for (int cnt = 0; cnt < rows.count() && result; cnt++)
+ {
+ int row = rows.at(cnt);
+ if (delete_persconffile_profile (index(row, ProfileModel::COL_NAME).data().toString().toUtf8().constData(), &ret_path) != 0)
+ {
+ if (msg)
+ {
+ QString errmsg = QString("%1\n\"%2\":\n%3").arg(tr("Can't delete profile directory")).arg(ret_path).arg(g_strerror(errno));
+ msg->append(errmsg);
+ }
+
+ result = false;
+ }
+ }
+
+ return result;
+}
+
+QString ProfileModel::illegalCharacters()
+{
+#ifdef _WIN32
+ /* According to https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file#naming-conventions */
+ return QString("<>:\"/\\|?*");
+#else
+ return QDir::separator();
+#endif
+
+}
+
+bool ProfileModel::checkNameValidity(QString name, QString *msg)
+{
+ QString message;
+ bool invalid = false;
+ QString msgChars;
+
+ QString invalid_dir_chars = illegalCharacters();
+
+ for (int cnt = 0; cnt < invalid_dir_chars.length() && ! invalid; cnt++)
+ {
+ msgChars += invalid_dir_chars[cnt];
+ msgChars += ' ';
+ if (name.contains(invalid_dir_chars[cnt]))
+ invalid = true;
+ }
+#ifdef _WIN32
+ if (invalid)
+ {
+ message = tr("A profile name cannot contain the following characters: %1").arg(msgChars);
+ }
+
+ if (message.isEmpty() && (name.startsWith('.') || name.endsWith('.')) )
+ message = tr("A profile cannot start or end with a period (.)");
+#else
+ if (invalid)
+ message = tr("A profile name cannot contain the '/' character");
+#endif
+
+ if (! message.isEmpty()) {
+ if (msg)
+ msg->append(message);
+ return false;
+ }
+
+ return true;
+}
+
+QString ProfileModel::activeProfileName()
+{
+ ProfileModel model;
+ QModelIndex idx = model.activeProfile();
+ return idx.data(ProfileModel::COL_NAME).toString();
+}
+
+QString ProfileModel::activeProfilePath()
+{
+ ProfileModel model;
+ QModelIndex idx = model.activeProfile();
+ return idx.data(ProfileModel::DATA_PATH).toString();
+}
diff --git a/ui/qt/models/profile_model.h b/ui/qt/models/profile_model.h
new file mode 100644
index 00000000..16febd70
--- /dev/null
+++ b/ui/qt/models/profile_model.h
@@ -0,0 +1,165 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef PROFILE_MODEL_H
+#define PROFILE_MODEL_H
+
+#include "config.h"
+#include "glib.h"
+
+#include <ui/profile.h>
+
+#include <QAbstractTableModel>
+#include <QSortFilterProxyModel>
+#include <QLoggingCategory>
+#include <QFileInfoList>
+
+Q_DECLARE_LOGGING_CATEGORY(profileLogger)
+
+class ProfileSortModel : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+ ProfileSortModel(QObject *parent = Q_NULLPTR);
+
+ enum FilterType {
+ AllProfiles = 0,
+ PersonalProfiles,
+ GlobalProfiles
+ };
+
+ void setFilterType(FilterType ft);
+ void setFilterString(QString txt = QString());
+
+ static QStringList filterTypes();
+
+protected:
+ virtual bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
+ virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
+
+private:
+ FilterType ft_;
+ QString ftext_;
+};
+
+class ProfileModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ explicit ProfileModel(QObject * parent = Q_NULLPTR);
+
+ enum {
+ COL_NAME,
+ COL_TYPE,
+ _LAST_ENTRY
+ } columns_;
+
+ enum {
+ DATA_STATUS = Qt::UserRole,
+ DATA_IS_DEFAULT,
+ DATA_IS_GLOBAL,
+ DATA_IS_SELECTED,
+ DATA_PATH,
+ DATA_PATH_IS_NOT_DESCRIPTION,
+ DATA_INDEX_VALUE_IS_URL
+ } data_values_;
+
+ // QAbstractItemModel interface
+ virtual int rowCount(const QModelIndex & parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex & parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex & idx, int role = Qt::DisplayRole) const;
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role);
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
+
+ void deleteEntry(QModelIndex idx);
+ void deleteEntries(QModelIndexList idcs);
+
+ int findByName(QString name);
+ QModelIndex addNewProfile(QString name);
+ QModelIndex duplicateEntry(QModelIndex idx, int new_status = PROF_STAT_COPY);
+
+ void doResetModel(bool reset_import = false);
+ bool resetDefault() const;
+
+ QModelIndex activeProfile() const;
+ static QString activeProfileName();
+ static QString activeProfilePath();
+
+ GList * at(int row) const;
+
+ bool changesPending() const;
+ bool importPending() const;
+
+ bool userProfilesExist() const;
+
+#ifdef HAVE_MINIZIP
+ bool exportProfiles(QString filename, QModelIndexList items, QString * err = Q_NULLPTR);
+ int importProfilesFromZip(QString filename, int *skippedCnt = Q_NULLPTR, QStringList *result = Q_NULLPTR);
+#endif
+ int importProfilesFromDir(QString filename, int *skippedCnt = Q_NULLPTR, bool fromZip = false, QStringList *result = Q_NULLPTR);
+
+ static bool checkNameValidity(QString name, QString *msg = Q_NULLPTR);
+ QList<int> findAllByNameAndVisibility(QString name, bool isGlobal = false, bool searchReference = false) const;
+ void markAsImported(QStringList importedItems);
+ bool clearImported(QString *msg = Q_NULLPTR);
+
+ int lastSetRow() const;
+
+ bool checkInvalid(const QModelIndex &index) const;
+ bool checkIfDeleted(const QModelIndex &index) const;
+ bool checkIfDeleted(int row) const;
+ bool checkDuplicate(const QModelIndex &index, bool isOriginalToDuplicate = false) const;
+
+signals:
+ void itemChanged(const QModelIndex &idx);
+
+protected:
+ static QString illegalCharacters();
+
+private:
+ QList<profile_def *> profiles_;
+ QStringList profile_files_;
+ QString set_profile_;
+ bool reset_default_;
+ bool profiles_imported_;
+
+ int last_set_row_;
+
+ void loadProfiles();
+ profile_def * guard(const QModelIndex &index) const;
+ profile_def * guard(int row) const;
+ GList * entry(profile_def *) const;
+
+ int findByNameAndVisibility(QString name, bool isGlobal = false, bool searchReference = false) const;
+ int findAsReference(QString reference) const;
+
+#ifdef HAVE_MINIZIP
+ static bool acceptFile(QString fileName, int fileSize);
+ static QString cleanName(QString fileName);
+#endif
+
+ QVariant dataDisplay(const QModelIndex & idx) const;
+ QVariant dataFontRole(const QModelIndex & idx) const;
+ QVariant dataBackgroundRole(const QModelIndex & idx) const;
+ QVariant dataToolTipRole(const QModelIndex & idx) const;
+ QVariant dataPath(const QModelIndex & idx) const;
+
+#ifdef HAVE_MINIZIP
+ QStringList exportFileList(QModelIndexList items);
+#endif
+ bool copyTempToProfile(QString tempPath, QString profilePath, bool *wasEmpty = Q_NULLPTR);
+ QFileInfoList filterProfilePath(QString, QFileInfoList ent, bool fromZip);
+ QFileInfoList uniquePaths(QFileInfoList lst);
+
+};
+
+#endif
diff --git a/ui/qt/models/proto_tree_model.cpp b/ui/qt/models/proto_tree_model.cpp
new file mode 100644
index 00000000..3ededffd
--- /dev/null
+++ b/ui/qt/models/proto_tree_model.cpp
@@ -0,0 +1,252 @@
+/* proto_tree_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/proto_tree_model.h>
+
+#include <epan/prefs.h>
+#include <wsutil/wslog.h>
+
+#include <ui/qt/utils/color_utils.h>
+
+#include <QApplication>
+#include <QPalette>
+#include <QFont>
+
+// To do:
+// - Add ProtoTreeDelegate
+// - Add ProtoTreeModel to CaptureFile
+
+ProtoTreeModel::ProtoTreeModel(QObject * parent) :
+ QAbstractItemModel(parent)
+{
+ root_node_ = new ProtoNode(NULL);
+}
+
+ProtoTreeModel::~ProtoTreeModel()
+{
+ delete root_node_;
+}
+
+Qt::ItemFlags ProtoTreeModel::flags(const QModelIndex &index) const
+{
+ Qt::ItemFlags item_flags = QAbstractItemModel::flags(index);
+ if (rowCount(index) < 1) {
+ item_flags |= Qt::ItemNeverHasChildren;
+ }
+
+ return item_flags;
+}
+
+QModelIndex ProtoTreeModel::index(int row, int, const QModelIndex &parent) const
+{
+ ProtoNode *parent_node = root_node_;
+
+ if (parent.isValid()) {
+ // index is not a top level item.
+ parent_node = protoNodeFromIndex(parent);
+ }
+
+ if (! parent_node->isValid())
+ return QModelIndex();
+
+ ProtoNode *child = parent_node->child(row);
+ if (! child) {
+ return QModelIndex();
+ }
+
+ return createIndex(row, 0, static_cast<void *>(child));
+}
+
+QModelIndex ProtoTreeModel::parent(const QModelIndex &index) const
+{
+ 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 root_node_->childrenCount();
+}
+
+// The QItemDelegate documentation says
+// "When displaying items from a custom model in a standard view, it is
+// often sufficient to simply ensure that the model returns appropriate
+// data for each of the roles that determine the appearance of items in
+// views."
+// We might want to move this to a delegate regardless.
+QVariant ProtoTreeModel::data(const QModelIndex &index, int role) const
+{
+ 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();
+ case Qt::BackgroundRole:
+ {
+ switch(finfo.flag(PI_SEVERITY_MASK)) {
+ case(0):
+ break;
+ case(PI_COMMENT):
+ return ColorUtils::expert_color_comment;
+ case(PI_CHAT):
+ return ColorUtils::expert_color_chat;
+ case(PI_NOTE):
+ return ColorUtils::expert_color_note;
+ case(PI_WARN):
+ return ColorUtils::expert_color_warn;
+ case(PI_ERROR):
+ return ColorUtils::expert_color_error;
+ default:
+ ws_warning("Unhandled severity flag: %u", finfo.flag(PI_SEVERITY_MASK));
+ }
+ if (finfo.headerInfo().type == FT_PROTOCOL) {
+ return QApplication::palette().window();
+ }
+ return QApplication::palette().base();
+ }
+ case Qt::ForegroundRole:
+ {
+ if (finfo.flag(PI_SEVERITY_MASK)) {
+ return ColorUtils::expert_color_foreground;
+ }
+ if (finfo.isLink()) {
+ return ColorUtils::themeLinkBrush();
+ }
+ if (finfo.headerInfo().type == FT_PROTOCOL) {
+ return QApplication::palette().windowText();
+ }
+ return QApplication::palette().text();
+ }
+ case Qt::FontRole:
+ if (finfo.isLink()) {
+ QFont font;
+ font.setUnderline(true);
+ return font;
+ }
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+void ProtoTreeModel::setRootNode(proto_node *root_node)
+{
+ beginResetModel();
+ delete root_node_;
+ root_node_ = new ProtoNode(root_node);
+ endResetModel();
+ if (!root_node) return;
+
+ 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
+{
+ return static_cast<ProtoNode*>(index.internalPointer());
+}
+
+QModelIndex ProtoTreeModel::indexFromProtoNode(ProtoNode *index_node) const
+{
+ if (!index_node) {
+ return QModelIndex();
+ }
+
+ 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;
+};
+
+bool ProtoTreeModel::foreachFindHfid(ProtoNode *node, gpointer find_hfid_ptr)
+{
+ struct find_hfid_ *find_hfid = (struct find_hfid_ *) find_hfid_ptr;
+ if (PNODE_FINFO(node->protoNode()) && PNODE_FINFO(node->protoNode())->hfinfo->id == find_hfid->hfid) {
+ find_hfid->node = node;
+ return true;
+ }
+ 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)
+{
+ if (!root_node_ || hf_id < 0) return QModelIndex();
+
+ struct find_hfid_ find_hfid;
+ find_hfid.hfid = hf_id;
+
+ if (foreachFindHfid(root_node_, &find_hfid) && find_hfid.node->isValid()) {
+ return indexFromProtoNode(find_hfid.node);
+ }
+ return QModelIndex();
+}
+
+struct find_field_info_ {
+ field_info *fi;
+ ProtoNode *node;
+};
+
+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->protoNode()) == find_finfo->fi) {
+ find_finfo->node = node;
+ return true;
+ }
+ for (int i = 0; i < node->childrenCount(); i++) {
+ if (foreachFindField(node->child(i), find_finfo)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+QModelIndex ProtoTreeModel::findFieldInformation(FieldInformation *finfo)
+{
+ if (!root_node_ || !finfo) return QModelIndex();
+ field_info * fi = finfo->fieldInfo();
+ if (!fi) return QModelIndex();
+
+ struct find_field_info_ find_finfo;
+ find_finfo.fi = fi;
+
+ if (foreachFindField(root_node_, &find_finfo) && find_finfo.node->isValid()) {
+ return indexFromProtoNode(find_finfo.node);
+ }
+ return QModelIndex();
+}
diff --git a/ui/qt/models/proto_tree_model.h b/ui/qt/models/proto_tree_model.h
new file mode 100644
index 00000000..df7cbba8
--- /dev/null
+++ b/ui/qt/models/proto_tree_model.h
@@ -0,0 +1,48 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#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>
+#include <QModelIndex>
+
+class ProtoTreeModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+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;
+ virtual QModelIndex parent(const QModelIndex &index) const;
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex &) const { return 1; }
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+
+ // root_node can be NULL.
+ void setRootNode(proto_node *root_node);
+ ProtoNode* protoNodeFromIndex(const QModelIndex &index) const;
+ QModelIndex indexFromProtoNode(ProtoNode *index_node) const;
+
+ QModelIndex findFirstHfid(int hf_id);
+ QModelIndex findFieldInformation(FieldInformation *finfo);
+
+private:
+ 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
diff --git a/ui/qt/models/related_packet_delegate.cpp b/ui/qt/models/related_packet_delegate.cpp
new file mode 100644
index 00000000..885160f4
--- /dev/null
+++ b/ui/qt/models/related_packet_delegate.cpp
@@ -0,0 +1,373 @@
+/* related_packet_delegate.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/related_packet_delegate.h>
+#include "packet_list_record.h"
+
+#include <ui/qt/main_application.h>
+
+#include <ui/qt/utils/color_utils.h>
+
+#include <ui/qt/main_window.h>
+
+#include <QApplication>
+#include <QPainter>
+
+typedef enum {
+ CT_NONE, // Not within the selected conversation.
+ CT_STARTING, // First packet in a conversation.
+ CT_CONTINUING, // Part of the selected conversation.
+ CT_BYPASSING, // *Not* part of the selected conversation.
+ CT_ENDING, // Last packet in a conversation.
+ CT_NUM_TYPES,
+} ct_conversation_trace_type_t;
+
+// To do:
+// - Add other frame types and symbols. If `tshark -G fields | grep FT_FRAMENUM`
+// is any indication, we should add "reassembly" and "reassembly error"
+// fields.
+// - Don't add *too* many frame types and symbols. The goal is context, not
+// clutter.
+// - Add tooltips. It looks like this needs to be done in ::helpEvent
+// or PacketListModel::data.
+// - Add "Go -> Next Related" and "Go -> Previous Related"?
+// - Apply as filter?
+
+RelatedPacketDelegate::RelatedPacketDelegate(QWidget *parent) :
+ QStyledItemDelegate(parent),
+ conv_(NULL),
+ current_frame_(0)
+{
+ clear();
+}
+
+void RelatedPacketDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+
+ /* This prevents the drawing of related objects, if multiple lines are being selected */
+ if (mainApp && mainApp->mainWindow())
+ {
+ MainWindow * mw = qobject_cast<MainWindow *>(mainApp->mainWindow());
+ if (mw && mw->hasSelection())
+ {
+ QStyledItemDelegate::paint(painter, option, index);
+ return;
+ }
+ }
+
+ QStyleOptionViewItem option_vi = option;
+ QStyledItemDelegate::initStyleOption(&option_vi, index);
+ int em_w = option_vi.fontMetrics.height();
+ int en_w = (em_w + 1) / 2;
+ int line_w = (option_vi.fontMetrics.lineWidth());
+
+ option_vi.features |= QStyleOptionViewItem::HasDecoration;
+ option_vi.decorationSize.setHeight(1);
+ option_vi.decorationSize.setWidth(em_w);
+ QStyledItemDelegate::paint(painter, option_vi, index);
+
+ guint32 setup_frame = 0, last_frame = 0;
+ if (conv_) {
+ setup_frame = (int) conv_->setup_frame;
+ last_frame = (int) conv_->last_frame;
+ }
+
+ const frame_data *fd;
+ PacketListRecord *record = static_cast<PacketListRecord*>(index.internalPointer());
+ if (!record || (fd = record->frameData()) == NULL) {
+ return;
+ }
+
+ ct_conversation_trace_type_t conversation_trace_type = CT_NONE;
+ ft_framenum_type_t related_frame_type =
+ related_frames_.contains(fd->num) ? related_frames_[fd->num] : FT_FRAMENUM_NUM_TYPES;
+
+ if (setup_frame > 0 && last_frame > 0 && setup_frame != last_frame) {
+ if (fd->num == setup_frame) {
+ conversation_trace_type = CT_STARTING;
+ } else if (fd->num > setup_frame && fd->num < last_frame) {
+ conversation_trace_type =
+ conv_->conv_index == record->conversation() ? CT_CONTINUING : CT_BYPASSING;
+ } else if (fd->num == last_frame) {
+ conversation_trace_type = CT_ENDING;
+ }
+ }
+
+ painter->save();
+
+ if (QApplication::style()->objectName().contains("vista")) {
+ // QWindowsVistaStyle::drawControl does this internally. Unfortunately there
+ // doesn't appear to be a more general way to do this.
+ option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText, option_vi.palette.color(QPalette::Active, QPalette::Text));
+ }
+
+ QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled
+ ? QPalette::Normal : QPalette::Disabled;
+ QColor fg;
+ if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+#if !defined(Q_OS_WIN)
+ if (option_vi.state & QStyle::State_MouseOver) {
+ fg = QApplication::palette().text().color();
+ } else
+#endif
+ if (option_vi.state & QStyle::State_Selected) {
+ fg = option_vi.palette.color(cg, QPalette::HighlightedText);
+ } else {
+ fg = option_vi.palette.color(cg, QPalette::Text);
+ }
+
+ fg = ColorUtils::alphaBlend(fg, option_vi.palette.color(cg, QPalette::Base), 0.5);
+ QPen line_pen(fg);
+ line_pen.setWidth(line_w);
+ line_pen.setJoinStyle(Qt::RoundJoin);
+
+ painter->setPen(line_pen);
+ painter->translate(option_vi.rect.x(), option_vi.rect.y());
+ painter->translate(en_w + 0.5, 0.5);
+ painter->setRenderHint(QPainter::Antialiasing, true);
+ int height = option_vi.rect.height();
+
+ // Uncomment to make the boundary visible.
+// painter->save();
+// painter->setPen(Qt::darkRed);
+// painter->drawRect(QRectF(0.5, 0.5, en_w - 1, height - 1));
+// painter->restore();
+
+ // The current decorations are based on what looked good and were easy
+ // to code.
+
+ // It might be useful to have a JACKPOT_MODE define that shows each
+ // decoration in sequence in order to make it easier to create
+ // screenshots for the User's Guide.
+
+ // Vertical line. Lower and upper half for the start and end of the
+ // conversation respectively, solid for conversation member, dashed
+ // for other packets in the start-end range.
+ switch (conversation_trace_type) {
+ case CT_STARTING:
+ {
+ QPoint start_line[] = {
+ QPoint(en_w - 1, height / 2),
+ QPoint(0, height / 2),
+ QPoint(0, height)
+ };
+ painter->drawPolyline(start_line, 3);
+ break;
+ }
+ case CT_CONTINUING:
+ case CT_BYPASSING:
+ {
+ painter->save();
+ if (conversation_trace_type == CT_BYPASSING) {
+ // Dashed line as we bypass packets not part of the conv.
+ QPen other_pen(line_pen);
+ other_pen.setStyle(Qt::DashLine);
+ painter->setPen(other_pen);
+ }
+
+ // analysis overriding mark (three horizontal lines)
+ if(fd->tcp_snd_manual_analysis) {
+ int wbound = (en_w - 1) / 2;
+
+ painter->drawLine(-wbound, 1, wbound, 1);
+ painter->drawLine(-wbound, height / 2, wbound, height / 2);
+ painter->drawLine(-wbound, height - 2, wbound, height - 2);
+ }
+
+ painter->drawLine(0, 0, 0, height);
+ painter->restore();
+ break;
+ }
+ case CT_ENDING:
+ {
+ QPoint end_line[] = {
+ QPoint(en_w - 1, height / 2),
+ QPoint(0, height / 2),
+ QPoint(0, 0)
+ };
+ painter->drawPolyline(end_line, 3);
+ /* analysis overriding on the last packet of the conversation,
+ * we mark it with an additional horizontal line only.
+ * See issue 10725 for example.
+ */
+ // analysis overriding mark (three horizontal lines)
+ if(fd->tcp_snd_manual_analysis) {
+ int wbound = (en_w - 1) / 2;
+
+ painter->drawLine(-wbound, 1, wbound, 1);
+ painter->drawLine(-wbound, height / 2, wbound, height / 2);
+ }
+
+ break;
+ }
+ default:
+ break;
+ }
+
+ // Related packet indicator. Rightward arrow for requests, leftward
+ // arrow for responses, circle for others.
+ // XXX These are comically oversized when we have multi-line rows.
+ if (related_frame_type != FT_FRAMENUM_NUM_TYPES) {
+ painter->setBrush(fg);
+ switch (related_frame_type) {
+ // Request and response arrows are moved forward one pixel in order to
+ // maximize white space between the heads and the conversation line.
+ case FT_FRAMENUM_REQUEST:
+ {
+ int hh = height / 2;
+ QPoint tail(2 - en_w, hh);
+ QPoint head(en_w, hh);
+ drawArrow(painter, tail, head, hh / 2);
+ break;
+ }
+ case FT_FRAMENUM_RESPONSE:
+ {
+ int hh = height / 2;
+ QPoint tail(en_w - 1, hh);
+ QPoint head(1 - en_w, hh);
+ drawArrow(painter, tail, head, hh / 2);
+ break;
+ }
+ case FT_FRAMENUM_ACK:
+ {
+ QRect bbox (2 - en_w, height / 3, em_w - 2, height / 2);
+ drawCheckMark(painter, bbox);
+ break;
+ }
+ case FT_FRAMENUM_DUP_ACK:
+ {
+ QRect bbox (2 - en_w, (height / 3) - (line_w * 2), em_w - 2, height / 2);
+ drawCheckMark(painter, bbox);
+ bbox.moveTop(bbox.top() + (line_w * 3));
+ drawCheckMark(painter, bbox);
+ break;
+ }
+ case FT_FRAMENUM_RETRANS_PREV:
+ {
+ int hh = height / 2;
+ QPoint tail(2 - en_w, hh);
+ QPoint head(en_w, hh);
+ drawChevrons(painter, tail, head, hh / 2);
+ break;
+ }
+ case FT_FRAMENUM_RETRANS_NEXT:
+ {
+ int hh = height / 2;
+ QPoint tail(en_w - 1, hh);
+ QPoint head(1 - en_w, hh);
+ drawChevrons(painter, tail, head, hh / 2);
+ break;
+ }
+ case FT_FRAMENUM_NONE:
+ default:
+ painter->drawEllipse(QPointF(0.0, option_vi.rect.height() / 2), 2, 2);
+ }
+ }
+
+ painter->restore();
+}
+
+QSize RelatedPacketDelegate::sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ /* This prevents the sizeHint for the delegate, if multiple lines are being selected */
+ if (mainApp && mainApp->mainWindow())
+ {
+ MainWindow * mw = qobject_cast<MainWindow *>(mainApp->mainWindow());
+ if (mw && mw->selectedRows().count() > 1)
+ return QStyledItemDelegate::sizeHint(option, index);
+ }
+
+ return QSize(option.fontMetrics.height() + QStyledItemDelegate::sizeHint(option, index).width(),
+ QStyledItemDelegate::sizeHint(option, index).height());
+}
+
+void RelatedPacketDelegate::drawArrow(QPainter *painter, const QPoint tail, const QPoint head, int head_size) const
+{
+ int x_mul = head.x() > tail.x() ? -1 : 1;
+ QPoint head_points[] = {
+ head,
+ QPoint(head.x() + (head_size * x_mul), head.y() + (head_size / 2)),
+ QPoint(head.x() + (head_size * x_mul), head.y() - (head_size / 2)),
+ };
+
+ painter->drawLine(tail.x(), tail.y(), head.x() + (head_size * x_mul), head.y());
+ painter->drawPolygon(head_points, 3);
+}
+
+void RelatedPacketDelegate::drawChevrons(QPainter *painter, const QPoint tail, const QPoint head, int head_size) const
+{
+ int x_mul = head.x() > tail.x() ? -1 : 1;
+ QPoint head_points1[] = {
+ head,
+ QPoint(head.x() + (head_size * x_mul), head.y() + (head_size / 2)),
+ QPoint(head.x() + (head_size * x_mul), head.y() - (head_size / 2)),
+ };
+ QPoint head2(head.x() + (head_size * x_mul), head.y());
+ QPoint head_points2[] = {
+ head2,
+ QPoint(head2.x() + (head_size * x_mul), head2.y() + (head_size / 2)),
+ QPoint(head2.x() + (head_size * x_mul), head2.y() - (head_size / 2)),
+ };
+
+ painter->drawPolygon(head_points1, 3);
+ painter->drawPolygon(head_points2, 3);
+}
+
+void RelatedPacketDelegate::drawCheckMark(QPainter *painter, const QRect bbox) const
+{
+ QPoint cm_points[] = {
+ QPoint(bbox.x(), bbox.y() + (bbox.height() / 2)),
+ QPoint(bbox.x() + (bbox.width() / 4), bbox.y() + (bbox.height() * 3 / 4)),
+ bbox.topRight()
+ };
+ painter->drawPolyline(cm_points, 3);
+}
+
+void RelatedPacketDelegate::clear()
+{
+ related_frames_.clear();
+ current_frame_ = 0;
+ conv_ = NULL;
+}
+
+void RelatedPacketDelegate::setCurrentFrame(guint32 current_frame)
+ {
+ current_frame_ = current_frame;
+ foreach (ft_framenum_type_t framenum_type, related_frames_) {
+ addRelatedFrame(-1, framenum_type); /* No need to check if this element belongs to the hash... */
+ }
+ }
+
+void RelatedPacketDelegate::addRelatedFrame(int frame_num, ft_framenum_type_t framenum_type)
+{
+ if (frame_num != -1 && !related_frames_.contains(frame_num))
+ related_frames_[frame_num] = framenum_type;
+
+ // Last match wins. Last match might not make sense, however.
+ if (current_frame_ > 0) {
+ switch (framenum_type) {
+ case FT_FRAMENUM_REQUEST:
+ related_frames_[current_frame_] = FT_FRAMENUM_RESPONSE;
+ break;
+ case FT_FRAMENUM_RESPONSE:
+ related_frames_[current_frame_] = FT_FRAMENUM_REQUEST;
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void RelatedPacketDelegate::setConversation(conversation *conv)
+{
+ conv_ = conv;
+}
diff --git a/ui/qt/models/related_packet_delegate.h b/ui/qt/models/related_packet_delegate.h
new file mode 100644
index 00000000..927129a0
--- /dev/null
+++ b/ui/qt/models/related_packet_delegate.h
@@ -0,0 +1,51 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef RELATED_PACKET_DELEGATE_H
+#define RELATED_PACKET_DELEGATE_H
+
+#include <config.h>
+
+#include "epan/conversation.h"
+
+#include <QHash>
+#include <QStyledItemDelegate>
+
+class QPainter;
+struct conversation;
+
+class RelatedPacketDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+public:
+ RelatedPacketDelegate(QWidget *parent = 0);
+ void clear();
+ void setCurrentFrame(guint32 current_frame);
+ void setConversation(struct conversation *conv);
+
+public slots:
+ void addRelatedFrame(int frame_num, ft_framenum_type_t framenum_type = FT_FRAMENUM_NONE);
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+ QSize sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+
+private:
+ QHash<int, ft_framenum_type_t> related_frames_;
+ struct conversation *conv_;
+ guint32 current_frame_;
+
+ void drawArrow(QPainter *painter, const QPoint tail, const QPoint head, int head_size) const;
+ void drawChevrons(QPainter *painter, const QPoint tail, const QPoint head, int head_size) const;
+ void drawCheckMark(QPainter *painter, const QRect bbox) const;
+};
+
+#endif // RELATED_PACKET_DELEGATE_H
diff --git a/ui/qt/models/resolved_addresses_models.cpp b/ui/qt/models/resolved_addresses_models.cpp
new file mode 100644
index 00000000..48dd2f09
--- /dev/null
+++ b/ui/qt/models/resolved_addresses_models.cpp
@@ -0,0 +1,210 @@
+/* resolved_addresses_models.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/resolved_addresses_models.h>
+
+#include <glib.h>
+
+#include "file.h"
+
+#include "epan/addr_resolv.h"
+#include <wiretap/wtap.h>
+
+extern "C"
+{
+
+static void
+serv_port_hash_to_qstringlist(gpointer key, gpointer value, gpointer member_ptr)
+{
+ PortsModel *model = static_cast<PortsModel *>(member_ptr);
+ serv_port_t *serv_port = (serv_port_t *)value;
+ guint port = GPOINTER_TO_UINT(key);
+
+ if (serv_port->tcp_name) {
+ QStringList entries;
+
+ entries << serv_port->tcp_name;
+ entries << QString::number(port);
+ entries << "tcp";
+ model->appendRow(entries);
+ }
+ if (serv_port->udp_name) {
+ QStringList entries;
+
+ entries << serv_port->udp_name;
+ entries << QString::number(port);
+ entries << "udp";
+ model->appendRow(entries);
+ }
+ if (serv_port->sctp_name) {
+ QStringList entries;
+
+ entries << serv_port->sctp_name;
+ entries << QString::number(port);
+ entries << "sctp";
+ model->appendRow(entries);
+ }
+ if (serv_port->dccp_name) {
+ QStringList entries;
+
+ entries << serv_port->dccp_name;
+ entries << QString::number(port);
+ entries << "dccp";
+ model->appendRow(entries);
+ }
+}
+
+static void
+ipv4_hash_table_resolved_to_list(gpointer, gpointer value, gpointer sl_ptr)
+{
+ QList<QStringList> *hosts = (QList<QStringList> *) sl_ptr;
+ hashipv4_t *ipv4_hash_table_entry = (hashipv4_t *) value;
+
+ if ((ipv4_hash_table_entry->flags & NAME_RESOLVED)) {
+ *hosts << (QStringList() << QString(ipv4_hash_table_entry->ip) << QString(ipv4_hash_table_entry->name));
+ }
+}
+
+static void
+ipv6_hash_table_resolved_to_list(gpointer, gpointer value, gpointer sl_ptr)
+{
+ QList<QStringList> *hosts = (QList<QStringList> *) sl_ptr;
+ hashipv6_t *ipv6_hash_table_entry = (hashipv6_t *) value;
+
+ if ((ipv6_hash_table_entry->flags & NAME_RESOLVED)) {
+ *hosts << (QStringList() << QString(ipv6_hash_table_entry->ip6) << QString(ipv6_hash_table_entry->name));
+ }
+}
+
+static void
+eth_hash_to_qstringlist(gpointer, gpointer value, gpointer sl_ptr)
+{
+ QList<QStringList> *values = (QList<QStringList> *) sl_ptr;
+ hashether_t* tp = (hashether_t*)value;
+
+ *values << (QStringList() << QString(get_hash_ether_hexaddr(tp)) << QString(get_hash_ether_resolved_name(tp)));
+}
+
+static void
+manuf_hash_to_qstringlist(gpointer key, gpointer value, gpointer sl_ptr)
+{
+ QList<QStringList> *values = (QList<QStringList> *) sl_ptr;
+ hashmanuf_t *manuf = (hashmanuf_t*)value;
+ guint eth_as_guint = GPOINTER_TO_UINT(key);
+
+ QString entry = QString("%1:%2:%3")
+ .arg((eth_as_guint >> 16 & 0xff), 2, 16, QChar('0'))
+ .arg((eth_as_guint >> 8 & 0xff), 2, 16, QChar('0'))
+ .arg((eth_as_guint & 0xff), 2, 16, QChar('0'));
+
+ *values << (QStringList() << entry << QString(get_hash_manuf_resolved_name(manuf)));
+}
+
+static void
+wka_hash_to_qstringlist(gpointer key, gpointer value, gpointer sl_ptr)
+{
+ QList<QStringList> *values = (QList<QStringList> *) sl_ptr;
+ gchar *name = (gchar *)value;
+ guint8 *eth_addr = (guint8*)key;
+
+ QString entry = QString("%1:%2:%3:%4:%5:%6")
+ .arg(eth_addr[0], 2, 16, QChar('0'))
+ .arg(eth_addr[1], 2, 16, QChar('0'))
+ .arg(eth_addr[2], 2, 16, QChar('0'))
+ .arg(eth_addr[3], 2, 16, QChar('0'))
+ .arg(eth_addr[4], 2, 16, QChar('0'))
+ .arg(eth_addr[5], 2, 16, QChar('0'));
+
+ // We should filter on only those actually resolved, not display
+ // everything in wka
+ *values << (QStringList() << entry << QString(name));
+}
+
+}
+
+EthernetAddressModel::EthernetAddressModel(QObject * parent):
+ AStringListListModel(parent)
+{
+ populate();
+}
+
+QStringList EthernetAddressModel::headerColumns() const
+{
+ return QStringList() << tr("Type") << tr("Address") << tr("Name");
+}
+
+QStringList EthernetAddressModel::filterValues() const
+{
+ return QStringList()
+ << tr("All entries")
+ << tr("Hosts")
+ << tr("Ethernet Addresses") << tr("Ethernet Manufacturers")
+ << tr("Ethernet Well-Known Addresses");
+}
+
+void EthernetAddressModel::populate()
+{
+ QList<QStringList> hosts; // List of (address, names)
+ if (wmem_map_t *ipv4_hash_table = get_ipv4_hash_table()) {
+ wmem_map_foreach(ipv4_hash_table, ipv4_hash_table_resolved_to_list, &hosts);
+ }
+ if (wmem_map_t *ipv6_hash_table = get_ipv6_hash_table()) {
+ wmem_map_foreach(ipv6_hash_table, ipv6_hash_table_resolved_to_list, &hosts);
+ }
+ const QString &hosts_label = tr("Hosts");
+ foreach (const QStringList &addr_name, hosts)
+ appendRow(QStringList() << hosts_label << addr_name);
+
+ QList<QStringList> values;
+ if (wmem_map_t *eth_hashtable = get_eth_hashtable()) {
+ wmem_map_foreach(eth_hashtable, eth_hash_to_qstringlist, &values);
+ }
+ const QString &eth_label = tr("Ethernet Addresses");
+ foreach (const QStringList &line, values)
+ appendRow(QStringList() << eth_label << line);
+ values.clear();
+ if (wmem_map_t *eth_hashtable = get_manuf_hashtable()) {
+ wmem_map_foreach(eth_hashtable, manuf_hash_to_qstringlist, &values);
+ }
+ const QString &manuf_label = tr("Ethernet Manufacturers");
+ foreach (const QStringList &line, values)
+ appendRow(QStringList() << manuf_label << line);
+ values.clear();
+ if (wmem_map_t *eth_hashtable = get_wka_hashtable()) {
+ wmem_map_foreach(eth_hashtable, wka_hash_to_qstringlist, &values);
+ }
+ const QString &wka_label = tr("Ethernet Well-Known Addresses");
+ foreach (const QStringList &line, values)
+ appendRow(QStringList() << wka_label << line);
+}
+
+PortsModel::PortsModel(QObject * parent):
+ AStringListListModel(parent)
+{
+ populate();
+}
+
+QStringList PortsModel::filterValues() const
+{
+ return QStringList()
+ << tr("All entries") << tr("tcp") << tr("udp") << tr("sctp") << tr("dccp");
+}
+
+QStringList PortsModel::headerColumns() const
+{
+ return QStringList() << tr("Name") << tr("Port") << tr("Type");
+}
+
+void PortsModel::populate()
+{
+ wmem_map_t *serv_port_hashtable = get_serv_port_hashtable();
+ if (serv_port_hashtable) {
+ wmem_map_foreach(serv_port_hashtable, serv_port_hash_to_qstringlist, this);
+ }
+}
diff --git a/ui/qt/models/resolved_addresses_models.h b/ui/qt/models/resolved_addresses_models.h
new file mode 100644
index 00000000..32ca1b15
--- /dev/null
+++ b/ui/qt/models/resolved_addresses_models.h
@@ -0,0 +1,48 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef RESOLVED_ADDRESSES_MODELS_H
+#define RESOLVED_ADDRESSES_MODELS_H
+
+#include <ui/qt/models/astringlist_list_model.h>
+
+#include <QAbstractListModel>
+#include <QSortFilterProxyModel>
+
+class EthernetAddressModel : public AStringListListModel
+{
+ Q_OBJECT
+
+public:
+ EthernetAddressModel(QObject * parent = Q_NULLPTR);
+
+ QStringList filterValues() const;
+
+protected:
+ QStringList headerColumns() const override;
+ void populate();
+
+};
+
+class PortsModel : public AStringListListModel
+{
+ Q_OBJECT
+
+public:
+ PortsModel(QObject * parent = Q_NULLPTR);
+
+ QStringList filterValues() const;
+
+protected:
+ QStringList headerColumns() const override;
+ void populate();
+
+};
+
+#endif // RESOLVED_ADDRESSES_MODELS_H
diff --git a/ui/qt/models/sparkline_delegate.cpp b/ui/qt/models/sparkline_delegate.cpp
new file mode 100644
index 00000000..457c8295
--- /dev/null
+++ b/ui/qt/models/sparkline_delegate.cpp
@@ -0,0 +1,109 @@
+/* sparkline_delegate.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/sparkline_delegate.h>
+
+#include <QPainter>
+#include <QApplication>
+
+#define SPARKLINE_MIN_EM_WIDTH 10
+
+void SparkLineDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ QList<int> points = qvariant_cast<QList<int> >(index.data(Qt::UserRole));
+ int max = 1;
+ // We typically draw a sparkline alongside some text. Size our
+ // drawing area based on an Em width. and a bit of eyballing on
+ // Linux, macOS, and Windows.
+ int em_w = option.fontMetrics.height();
+ int content_w = option.rect.width() - (em_w / 4);
+ int content_h = option.fontMetrics.ascent() - 1;
+ int val;
+ qreal idx = 0.0;
+ qreal step_w = em_w / 10.0;
+ qreal steps = content_w / step_w;
+ QVector<QPointF> fpoints;
+
+ QStyledItemDelegate::paint(painter, option, index);
+
+ if (points.isEmpty() || steps < 1.0 || content_h <= 0) {
+ return;
+ }
+
+ while ((qreal) points.length() > steps) {
+ points.removeFirst();
+ }
+
+ foreach (val, points) {
+ if (val > max) max = val;
+ }
+
+ foreach (val, points) {
+ fpoints.append(QPointF(idx, (qreal) content_h - (val * content_h / max)));
+ idx = idx + step_w;
+ }
+
+ QStyleOptionViewItem option_vi = option;
+ QStyledItemDelegate::initStyleOption(&option_vi, index);
+
+ painter->save();
+
+ if (QApplication::style()->objectName().contains("vista")) {
+ // QWindowsVistaStyle::drawControl does this internally. Unfortunately there
+ // doesn't appear to be a more general way to do this.
+ option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText, option_vi.palette.color(QPalette::Active, QPalette::Text));
+ }
+
+ QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled
+ ? QPalette::Normal : QPalette::Disabled;
+ if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+#if defined(Q_OS_WIN)
+ if (option_vi.state & QStyle::State_Selected) {
+#else
+ if ((option_vi.state & QStyle::State_Selected) && !(option_vi.state & QStyle::State_MouseOver)) {
+#endif
+ painter->setPen(option_vi.palette.color(cg, QPalette::HighlightedText));
+ } else {
+ painter->setPen(option_vi.palette.color(cg, QPalette::Text));
+ }
+
+ // As a general rule, aliased painting renders to pixels and
+ // antialiased painting renders to mathematical coordinates:
+ // https://doc.qt.io/qt-5/coordsys.html
+ // Shift our coordinates by 0.5 pixels, otherwise our lines end
+ // up blurry.
+ painter->setRenderHint(QPainter::Antialiasing, true);
+ painter->translate(
+ option.rect.x() + (em_w / 8) + 0.5,
+ option.rect.y() + ((option.rect.height() - option.fontMetrics.height()) / 2) + 1 + 0.5);
+ painter->drawPolyline(QPolygonF(fpoints));
+
+ // Some sparklines are decorated with dots at the beginning and end.
+ // Ours look better without in my (gcc) opinion.
+// painter->setPen(Qt::NoPen);
+// painter->setBrush(option.palette.foreground());
+// painter->drawEllipse(fpoints.first(), 2, 2);
+
+// painter->setBrush(Qt::red);
+// painter->drawEllipse(fpoints.last(), 2, 2);
+
+ painter->restore();
+}
+
+QSize SparkLineDelegate::sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const {
+ return QSize(option.fontMetrics.height() * SPARKLINE_MIN_EM_WIDTH, QStyledItemDelegate::sizeHint(option, index).height());
+}
+
+QWidget *SparkLineDelegate::createEditor(QWidget *, const QStyleOptionViewItem &, const QModelIndex &) const
+{
+ return NULL;
+}
diff --git a/ui/qt/models/sparkline_delegate.h b/ui/qt/models/sparkline_delegate.h
new file mode 100644
index 00000000..ddbe947a
--- /dev/null
+++ b/ui/qt/models/sparkline_delegate.h
@@ -0,0 +1,35 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef SPARKLINE_DELEGATE_H
+#define SPARKLINE_DELEGATE_H
+
+#include <QStyledItemDelegate>
+
+class SparkLineDelegate : public QStyledItemDelegate
+{
+public:
+ SparkLineDelegate(QWidget *parent = 0) : QStyledItemDelegate(parent) {}
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+ QSize sizeHint(const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+
+signals:
+
+public slots:
+
+};
+
+Q_DECLARE_METATYPE(QList<int>)
+
+#endif // SPARKLINE_DELEGATE_H
diff --git a/ui/qt/models/supported_protocols_model.cpp b/ui/qt/models/supported_protocols_model.cpp
new file mode 100644
index 00000000..04e62572
--- /dev/null
+++ b/ui/qt/models/supported_protocols_model.cpp
@@ -0,0 +1,261 @@
+/* supported_protocols_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <QSortFilterProxyModel>
+#include <QStringList>
+#include <QPalette>
+#include <QApplication>
+#include <QBrush>
+#include <QRegularExpression>
+
+#include <ui/qt/models/supported_protocols_model.h>
+
+SupportedProtocolsItem::SupportedProtocolsItem(protocol_t* proto, const char *name, const char* filter, ftenum_t ftype, const char* descr, SupportedProtocolsItem* parent)
+ : ModelHelperTreeItem<SupportedProtocolsItem>(parent),
+ proto_(proto),
+ name_(name),
+ filter_(filter),
+ ftype_(ftype),
+ descr_(descr)
+{
+}
+
+SupportedProtocolsItem::~SupportedProtocolsItem()
+{
+}
+
+
+SupportedProtocolsModel::SupportedProtocolsModel(QObject *parent) :
+ QAbstractItemModel(parent),
+ root_(new SupportedProtocolsItem(NULL, NULL, NULL, FT_NONE, NULL, NULL)),
+ field_count_(0)
+{
+}
+
+SupportedProtocolsModel::~SupportedProtocolsModel()
+{
+ delete root_;
+}
+
+int SupportedProtocolsModel::rowCount(const QModelIndex &parent) const
+{
+ SupportedProtocolsItem *parent_item;
+ if (parent.column() > 0)
+ return 0;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<SupportedProtocolsItem*>(parent.internalPointer());
+
+ if (parent_item == NULL)
+ return 0;
+
+ return parent_item->childCount();
+}
+
+int SupportedProtocolsModel::columnCount(const QModelIndex&) const
+{
+ return colLast;
+}
+
+QVariant SupportedProtocolsModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+
+ switch ((enum SupportedProtocolsColumn)section) {
+ case colName:
+ return tr("Name");
+ case colFilter:
+ return tr("Filter");
+ case colType:
+ return tr("Type");
+ case colDescription:
+ return tr("Description");
+ default:
+ break;
+ }
+ }
+ return QVariant();
+}
+
+QModelIndex SupportedProtocolsModel::parent(const QModelIndex& index) const
+{
+ if (!index.isValid())
+ return QModelIndex();
+
+ SupportedProtocolsItem* item = static_cast<SupportedProtocolsItem*>(index.internalPointer());
+ if (item != NULL) {
+ SupportedProtocolsItem* parent_item = item->parentItem();
+ if (parent_item != NULL) {
+ if (parent_item == root_)
+ return QModelIndex();
+
+ return createIndex(parent_item->row(), 0, parent_item);
+ }
+ }
+
+ return QModelIndex();
+}
+
+QModelIndex SupportedProtocolsModel::index(int row, int column, const QModelIndex& parent) const
+{
+ if (!hasIndex(row, column, parent))
+ return QModelIndex();
+
+ SupportedProtocolsItem *parent_item, *child_item;
+
+ if (!parent.isValid())
+ parent_item = root_;
+ else
+ parent_item = static_cast<SupportedProtocolsItem*>(parent.internalPointer());
+
+ Q_ASSERT(parent_item);
+
+ child_item = parent_item->child(row);
+ if (child_item) {
+ return createIndex(row, column, child_item);
+ }
+
+ return QModelIndex();
+}
+
+QVariant SupportedProtocolsModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || role != Qt::DisplayRole)
+ return QVariant();
+
+ SupportedProtocolsItem* item = static_cast<SupportedProtocolsItem*>(index.internalPointer());
+ if (item == NULL)
+ return QVariant();
+
+ switch ((enum SupportedProtocolsColumn)index.column()) {
+ case colName:
+ return item->name();
+ case colFilter:
+ return item->filter();
+ case colType:
+ if (index.parent().isValid())
+ return QString(ftype_pretty_name(item->type()));
+
+ return QVariant();
+ case colDescription:
+ return item->description();
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+void SupportedProtocolsModel::populate()
+{
+ void *proto_cookie;
+ void *field_cookie;
+
+ beginResetModel();
+
+ SupportedProtocolsItem *protoItem, *fieldItem;
+ protocol_t *protocol;
+
+ for (int proto_id = proto_get_first_protocol(&proto_cookie); proto_id != -1;
+ proto_id = proto_get_next_protocol(&proto_cookie)) {
+
+ protocol = find_protocol_by_id(proto_id);
+ protoItem = new SupportedProtocolsItem(protocol, proto_get_protocol_short_name(protocol), proto_get_protocol_filter_name(proto_id), FT_PROTOCOL, proto_get_protocol_long_name(protocol), root_);
+ root_->prependChild(protoItem);
+
+ for (header_field_info *hfinfo = proto_get_first_protocol_field(proto_id, &field_cookie); hfinfo != NULL;
+ hfinfo = proto_get_next_protocol_field(proto_id, &field_cookie)) {
+ if (hfinfo->same_name_prev_id != -1)
+ continue;
+
+ fieldItem = new SupportedProtocolsItem(protocol, hfinfo->name, hfinfo->abbrev, hfinfo->type, hfinfo->blurb, protoItem);
+ protoItem->prependChild(fieldItem);
+ field_count_++;
+ }
+ }
+
+ endResetModel();
+}
+
+
+
+SupportedProtocolsProxyModel::SupportedProtocolsProxyModel(QObject * parent)
+: QSortFilterProxyModel(parent),
+filter_()
+{
+}
+
+bool SupportedProtocolsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
+{
+ //Use SupportedProtocolsItem directly for better performance
+ SupportedProtocolsItem* left_item = static_cast<SupportedProtocolsItem*>(left.internalPointer());
+ SupportedProtocolsItem* right_item = static_cast<SupportedProtocolsItem*>(right.internalPointer());
+
+ if ((left_item != NULL) && (right_item != NULL)) {
+ int compare_ret = left_item->name().compare(right_item->name());
+ if (compare_ret < 0)
+ return true;
+ }
+
+ return false;
+}
+
+bool SupportedProtocolsProxyModel::filterAcceptItem(SupportedProtocolsItem& item) const
+{
+ QRegularExpression regex(filter_, QRegularExpression::CaseInsensitiveOption);
+ if (! regex.isValid())
+ return false;
+
+ if (item.name().contains(regex))
+ return true;
+
+ if (item.filter().contains(regex))
+ return true;
+
+ if (item.description().contains(regex))
+ return true;
+
+ return false;
+}
+
+bool SupportedProtocolsProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
+{
+ QModelIndex nameIdx = sourceModel()->index(sourceRow, SupportedProtocolsModel::colName, sourceParent);
+ SupportedProtocolsItem* item = static_cast<SupportedProtocolsItem*>(nameIdx.internalPointer());
+ if (item == NULL)
+ return true;
+
+ if (!filter_.isEmpty()) {
+ if (filterAcceptItem(*item))
+ return true;
+
+ if (!nameIdx.parent().isValid())
+ {
+ SupportedProtocolsItem* child_item;
+ for (int row = 0; row < item->childCount(); row++)
+ {
+ child_item = item->child(row);
+ if ((child_item != NULL) && (filterAcceptItem(*child_item)))
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return true;
+}
+
+void SupportedProtocolsProxyModel::setFilter(const QString& filter)
+{
+ filter_ = filter;
+ invalidateFilter();
+}
diff --git a/ui/qt/models/supported_protocols_model.h b/ui/qt/models/supported_protocols_model.h
new file mode 100644
index 00000000..d964ef68
--- /dev/null
+++ b/ui/qt/models/supported_protocols_model.h
@@ -0,0 +1,97 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef SUPPORTED_PROTOCOLS_MODEL_H
+#define SUPPORTED_PROTOCOLS_MODEL_H
+
+#include <config.h>
+
+#include <ui/qt/models/tree_model_helpers.h>
+
+#include <epan/proto.h>
+
+#include <QSortFilterProxyModel>
+
+class SupportedProtocolsItem : public ModelHelperTreeItem<SupportedProtocolsItem>
+{
+public:
+ SupportedProtocolsItem(protocol_t* proto, const char *name, const char* filter, ftenum_t ftype, const char* descr, SupportedProtocolsItem* parent);
+ virtual ~SupportedProtocolsItem();
+
+ protocol_t* protocol() const {return proto_; }
+ QString name() const { return name_; }
+ ftenum_t type() const {return ftype_; }
+ QString filter() const { return filter_; }
+ QString description() const { return descr_; }
+
+private:
+ protocol_t* proto_;
+ QString name_;
+ QString filter_;
+ ftenum_t ftype_;
+ QString descr_;
+};
+
+
+class SupportedProtocolsModel : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+ explicit SupportedProtocolsModel(QObject * parent = Q_NULLPTR);
+ virtual ~SupportedProtocolsModel();
+
+ enum SupportedProtocolsColumn {
+ colName = 0,
+ colFilter,
+ colType,
+ colDescription,
+ colLast
+ };
+
+ int fieldCount() {return field_count_;}
+
+ QModelIndex index(int row, int column,
+ const QModelIndex & = QModelIndex()) const;
+ QModelIndex parent(const QModelIndex &) const;
+ QVariant data(const QModelIndex &index, int role) const;
+
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ void populate();
+
+private:
+ SupportedProtocolsItem* root_;
+ int field_count_;
+};
+
+class SupportedProtocolsProxyModel : public QSortFilterProxyModel
+{
+public:
+
+ explicit SupportedProtocolsProxyModel(QObject * parent = Q_NULLPTR);
+
+ virtual bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const;
+
+ void setFilter(const QString& filter);
+
+protected:
+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
+ bool filterAcceptItem(SupportedProtocolsItem& item) const;
+
+private:
+
+ QString filter_;
+};
+
+#endif // SUPPORTED_PROTOCOLS_MODEL_H
diff --git a/ui/qt/models/timeline_delegate.cpp b/ui/qt/models/timeline_delegate.cpp
new file mode 100644
index 00000000..07a7409c
--- /dev/null
+++ b/ui/qt/models/timeline_delegate.cpp
@@ -0,0 +1,128 @@
+/* timeline_delegate.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/timeline_delegate.h>
+#include <ui/qt/models/atap_data_model.h>
+
+#include <ui/qt/utils/color_utils.h>
+
+#include <QApplication>
+#include <QPainter>
+#include <QTreeView>
+#include <QAbstractProxyModel>
+
+// XXX We might want to move this to conversation_dialog.cpp.
+
+// PercentBarDelegate uses a stronger blend value, but its bars are also
+// more of a prominent feature. Make the blend weaker here so that we don't
+// obscure our text.
+static const double bar_blend_ = 0.08;
+
+TimelineDelegate::TimelineDelegate(QWidget *parent) :
+ QStyledItemDelegate(parent)
+{
+ _dataRole = Qt::UserRole;
+}
+
+void TimelineDelegate::setDataRole(int dataRole)
+{
+ _dataRole = dataRole;
+}
+
+void TimelineDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ QStyleOptionViewItem option_vi = option;
+ QStyledItemDelegate::initStyleOption(&option_vi, index);
+
+ bool drawBar = false;
+ struct timeline_span span_px = index.data(_dataRole).value<struct timeline_span>();
+ if (_dataRole == ATapDataModel::TIMELINE_DATA) {
+ double span_s = span_px.maxRelTime - span_px.minRelTime;
+ QTreeView * tree = qobject_cast<QTreeView *>(parent());
+ if (tree) {
+ QAbstractProxyModel * proxy = qobject_cast<QAbstractProxyModel *>(tree->model());
+ if (proxy && proxy->sourceModel()) {
+ QModelIndex indexStart = proxy->mapFromSource(proxy->sourceModel()->index(0, span_px.colStart));
+ int colStart = -1;
+ int start_px = 0;
+ if (indexStart.isValid()) {
+ colStart = indexStart.column();
+ start_px = tree->columnWidth(colStart);
+ }
+ int colDuration = -1;
+ int column_px = start_px;
+ QModelIndex indexDuration = proxy->mapFromSource(proxy->sourceModel()->index(0, span_px.colDuration));
+ if (indexDuration.isValid()) {
+ colDuration = indexDuration.column();
+ column_px += tree->columnWidth(colDuration);
+ }
+
+ span_px.start = ((span_px.startTime - span_px.minRelTime) * column_px) / span_s;
+ span_px.width = ((span_px.stopTime - span_px.startTime) * column_px) / span_s;
+
+ if (index.column() == colStart) {
+ drawBar = true;
+ } else if (index.column() == colDuration) {
+ drawBar = true;
+ span_px.start -= start_px;
+ }
+ }
+ }
+ }
+
+ if (!drawBar) {
+ QStyledItemDelegate::paint(painter, option, index);
+ return;
+ }
+
+ // Paint our rect with no text using the current style, then draw our
+ // bar and text over it.
+ option_vi.text = QString();
+ QStyle *style = option_vi.widget ? option_vi.widget->style() : QApplication::style();
+ style->drawControl(QStyle::CE_ItemViewItem, &option_vi, painter, option_vi.widget);
+
+ if (QApplication::style()->objectName().contains("vista")) {
+ // QWindowsVistaStyle::drawControl does this internally. Unfortunately there
+ // doesn't appear to be a more general way to do this.
+ option_vi.palette.setColor(QPalette::All, QPalette::HighlightedText,
+ option_vi.palette.color(QPalette::Active, QPalette::Text));
+ }
+
+ QPalette::ColorGroup cg = option_vi.state & QStyle::State_Enabled
+ ? QPalette::Normal : QPalette::Disabled;
+ QColor text_color = option_vi.palette.color(cg, QPalette::Text);
+ QColor bar_color = ColorUtils::alphaBlend(option_vi.palette.windowText(),
+ option_vi.palette.window(), bar_blend_);
+
+ if (cg == QPalette::Normal && !(option_vi.state & QStyle::State_Active))
+ cg = QPalette::Inactive;
+ if (option_vi.state & QStyle::State_Selected) {
+ text_color = option_vi.palette.color(cg, QPalette::HighlightedText);
+ bar_color = ColorUtils::alphaBlend(option_vi.palette.color(cg, QPalette::Window),
+ option_vi.palette.color(cg, QPalette::Highlight),
+ bar_blend_);
+ }
+
+ painter->save();
+ int border_radius = 3; // We use 3 px elsewhere, e.g. filter combos.
+ QRect timeline_rect = option.rect;
+ timeline_rect.adjust(span_px.start, 1, 0, -1);
+ timeline_rect.setWidth(span_px.width);
+ painter->setClipRect(option.rect);
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(bar_color);
+ painter->drawRoundedRect(timeline_rect, border_radius, border_radius);
+ painter->restore();
+
+ painter->save();
+ painter->setPen(text_color);
+ painter->drawText(option.rect, Qt::AlignCenter, index.data(Qt::DisplayRole).toString());
+ painter->restore();
+}
diff --git a/ui/qt/models/timeline_delegate.h b/ui/qt/models/timeline_delegate.h
new file mode 100644
index 00000000..06218b87
--- /dev/null
+++ b/ui/qt/models/timeline_delegate.h
@@ -0,0 +1,66 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef TIMELINE_DELEGATE_H
+#define TIMELINE_DELEGATE_H
+
+/*
+ * @file Timeline delegate.
+ *
+ * QStyledItemDelegate subclass that will draw a timeline indicator for
+ * the specified value.
+ *
+ * This is intended to be used in QTreeWidgets to show timelines, e.g. for
+ * conversations.
+ * To use it, first call setItemDelegate:
+ *
+ * myTreeWidget()->setItemDelegateForColumn(col_time_start_, new TimelineDelegate());
+ *
+ * Then, for each QTreeWidgetItem, set or return a timeline_span for the start and end
+ * of the timeline in pixels relative to the column width.
+ *
+ * setData(col_start_, Qt::UserRole, start_span);
+ * setData(col_end_, Qt::UserRole, end_span);
+ *
+ */
+
+#include <QStyledItemDelegate>
+
+// Pixels are relative to item rect and will be clipped.
+struct timeline_span {
+ int start;
+ int width;
+
+ double startTime;
+ double stopTime;
+ double minRelTime;
+ double maxRelTime;
+
+ int colStart;
+ int colDuration;
+};
+
+Q_DECLARE_METATYPE(timeline_span)
+
+class TimelineDelegate : public QStyledItemDelegate
+{
+public:
+ TimelineDelegate(QWidget *parent = 0);
+
+ void setDataRole(int role);
+
+protected:
+ void paint(QPainter *painter, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const;
+private:
+
+ int _dataRole;
+};
+
+#endif // TIMELINE_DELEGATE_H
diff --git a/ui/qt/models/tree_model_helpers.h b/ui/qt/models/tree_model_helpers.h
new file mode 100644
index 00000000..5734b660
--- /dev/null
+++ b/ui/qt/models/tree_model_helpers.h
@@ -0,0 +1,89 @@
+/** @file
+ *
+ * Utility template classes for basic tree model functionality
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef TREE_MODEL_HELPERS_H
+#define TREE_MODEL_HELPERS_H
+
+#include <config.h>
+#include <ui/qt/utils/variant_pointer.h>
+
+#include <QAbstractItemModel>
+
+//Base class to inherit basic tree item from
+template <typename Item>
+class ModelHelperTreeItem
+{
+public:
+ ModelHelperTreeItem(Item* parent)
+ : parent_(parent)
+ {
+ }
+
+ virtual ~ModelHelperTreeItem()
+ {
+ for (int row = 0; row < childItems_.count(); row++)
+ {
+ delete VariantPointer<Item>::asPtr(childItems_.value(row));
+ }
+
+ childItems_.clear();
+ }
+
+ void appendChild(Item* child)
+ {
+ childItems_.append(VariantPointer<Item>::asQVariant(child));
+ }
+
+ void prependChild(Item* child)
+ {
+ childItems_.prepend(VariantPointer<Item>::asQVariant(child));
+ }
+
+
+ void insertChild(int row, Item* child)
+ {
+ childItems_.insert(row, VariantPointer<Item>::asQVariant(child));
+ }
+
+ void removeChild(int row)
+ {
+ delete VariantPointer<Item>::asPtr(childItems_.value(row));
+ childItems_.removeAt(row);
+ }
+
+ Item* child(int row)
+ {
+ return VariantPointer<Item>::asPtr(childItems_.value(row));
+ }
+
+ int childCount() const
+ {
+ return static_cast<int>(childItems_.count());
+ }
+
+ int row()
+ {
+ if (parent_)
+ {
+ return static_cast<int>(parent_->childItems_.indexOf(VariantPointer<Item>::asQVariant((Item *)this)));
+ }
+
+ return 0;
+ }
+
+ Item* parentItem() {return parent_; }
+
+protected:
+ Item* parent_;
+ QList<QVariant> childItems_;
+};
+
+#endif // TREE_MODEL_HELPERS_H
diff --git a/ui/qt/models/uat_delegate.cpp b/ui/qt/models/uat_delegate.cpp
new file mode 100644
index 00000000..4e7d25b3
--- /dev/null
+++ b/ui/qt/models/uat_delegate.cpp
@@ -0,0 +1,221 @@
+/* uat_delegate.cpp
+ * Delegates for editing various field types in a UAT record.
+ *
+ * Copyright 2016 Peter Wu <peter@lekensteyn.nl>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/uat_delegate.h>
+#include <epan/packet.h> // for get_dissector_names()
+#include "epan/value_string.h"
+#include <wsutil/ws_assert.h>
+#include <QRegularExpression>
+#include <QComboBox>
+#include <QEvent>
+#include <QFileDialog>
+#include <QLineEdit>
+#include <QCheckBox>
+#include <QColorDialog>
+
+#include <ui/qt/widgets/display_filter_edit.h>
+#include <ui/qt/widgets/dissector_syntax_line_edit.h>
+#include <ui/qt/widgets/field_filter_edit.h>
+#include <ui/qt/widgets/editor_file_dialog.h>
+#include <ui/qt/widgets/path_selection_edit.h>
+
+// The Qt docs suggest overriding updateEditorGeometry, but the
+// defaults seem sane.
+
+UatDelegate::UatDelegate(QObject *parent) : QStyledItemDelegate(parent)
+{
+}
+
+uat_field_t *UatDelegate::indexToField(const QModelIndex &index) const
+{
+ const QVariant v = index.model()->data(index, Qt::UserRole);
+ return static_cast<uat_field_t *>(v.value<void *>());
+}
+
+QWidget *UatDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ uat_field_t *field = indexToField(index);
+ QWidget *editor = nullptr;
+
+ switch (field->mode) {
+ case PT_TXTMOD_DIRECTORYNAME:
+ case PT_TXTMOD_FILENAME:
+ if (index.isValid()) {
+ QString filename_old = index.model()->data(index, Qt::EditRole).toString();
+ PathSelectionEdit * pathEdit = new PathSelectionEdit(field->title, QString(), field->mode != PT_TXTMOD_DIRECTORYNAME, parent);
+ connect(pathEdit, &PathSelectionEdit::pathChanged, this, &UatDelegate::pathHasChanged);
+ return pathEdit;
+ }
+ break;
+ case PT_TXTMOD_COLOR:
+ if (index.isValid()) {
+ QColor color(index.model()->data(index, Qt::DecorationRole).toString());
+ QColorDialog * colorDialog = new QColorDialog(color, parent);
+ // Don't fall through and set setAutoFillBackground(true)
+ return colorDialog;
+ }
+ break;
+
+ case PT_TXTMOD_ENUM:
+ {
+ // Note: the string repr. is written, not the integer value.
+ QComboBox *cb_editor = new QComboBox(parent);
+ const value_string *enum_vals = (const value_string *)field->fld_data;
+ for (int i = 0; enum_vals[i].strptr != nullptr; i++) {
+ cb_editor->addItem(enum_vals[i].strptr);
+ }
+ editor = cb_editor;
+ cb_editor->setMinimumWidth(cb_editor->minimumSizeHint().width());
+ break;
+ }
+
+ case PT_TXTMOD_DISSECTOR:
+ {
+ editor = new DissectorSyntaxLineEdit(parent);
+ break;
+ }
+
+ case PT_TXTMOD_STRING:
+ // TODO add a live validator? Should SyntaxLineEdit be used?
+ editor = QStyledItemDelegate::createEditor(parent, option, index);
+ break;
+
+ case PT_TXTMOD_DISPLAY_FILTER:
+ editor = new DisplayFilterEdit(parent);
+ break;
+
+ case PT_TXTMOD_PROTO_FIELD:
+ editor = new FieldFilterEdit(parent);
+ break;
+
+ case PT_TXTMOD_HEXBYTES:
+ {
+ // Requires input of the form "ab cd ef" (with possibly no or a colon
+ // separator instead of a single whitespace) for the editor to accept.
+ QRegularExpression hexbytes_regex("([0-9a-f]{2}[ :]?)*");
+ hexbytes_regex.setPatternOptions(QRegularExpression::CaseInsensitiveOption);
+ // QString types from QStyledItemDelegate are documented to return a
+ // QLineEdit. Note that Qt returns a subclass from QLineEdit which
+ // automatically adapts the width to the typed contents.
+ QLineEdit *le_editor = static_cast<QLineEdit *>(
+ QStyledItemDelegate::createEditor(parent, option, index));
+ le_editor->setValidator(new QRegularExpressionValidator(hexbytes_regex, le_editor));
+ editor = le_editor;
+ break;
+ }
+
+ case PT_TXTMOD_BOOL:
+ // model will handle creating checkbox
+ break;
+
+ case PT_TXTMOD_NONE:
+ break;
+
+ default:
+ ws_assert_not_reached();
+ break;
+ }
+
+ if (editor) {
+ editor->setAutoFillBackground(true);
+ }
+ return editor;
+}
+
+void UatDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
+{
+ uat_field_t *field = indexToField(index);
+
+ switch (field->mode) {
+ case PT_TXTMOD_DIRECTORYNAME:
+ case PT_TXTMOD_FILENAME:
+ if (index.isValid() && qobject_cast<PathSelectionEdit *>(editor))
+ qobject_cast<PathSelectionEdit *>(editor)->setPath(index.model()->data(index, Qt::EditRole).toString());
+ break;
+ case PT_TXTMOD_ENUM:
+ {
+ QComboBox *combobox = static_cast<QComboBox *>(editor);
+ const QString &data = index.model()->data(index, Qt::EditRole).toString();
+ combobox->setCurrentText(data);
+
+ break;
+ }
+ case PT_TXTMOD_COLOR:
+ {
+ if (qobject_cast<QColorDialog *>(editor))
+ {
+ QColor color(index.model()->data(index, Qt::DecorationRole).toString());
+ qobject_cast<QColorDialog *>(editor)->setCurrentColor(color);
+ }
+ break;
+ }
+
+ default:
+ QStyledItemDelegate::setEditorData(editor, index);
+ }
+}
+
+void UatDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
+ const QModelIndex &index) const
+{
+ uat_field_t *field = indexToField(index);
+
+ switch (field->mode) {
+ case PT_TXTMOD_DIRECTORYNAME:
+ case PT_TXTMOD_FILENAME:
+ if (index.isValid() && qobject_cast<PathSelectionEdit *>(editor))
+ const_cast<QAbstractItemModel *>(index.model())->setData(index, qobject_cast<PathSelectionEdit *>(editor)->path(), Qt::EditRole);
+ break;
+ case PT_TXTMOD_ENUM:
+ {
+ QComboBox *combobox = static_cast<QComboBox *>(editor);
+ const QString &data = combobox->currentText();
+ model->setData(index, data, Qt::EditRole);
+ break;
+ }
+ case PT_TXTMOD_COLOR:
+ //do nothing, dialog signals will update table
+ if (qobject_cast<QColorDialog *>(editor))
+ {
+ QColor newColor = qobject_cast<QColorDialog *>(editor)->currentColor();
+ const_cast<QAbstractItemModel *>(index.model())->setData(index, newColor.name(), Qt::EditRole);
+ }
+ break;
+
+ default:
+ QStyledItemDelegate::setModelData(editor, model, index);
+ }
+}
+
+void UatDelegate::updateEditorGeometry(QWidget *editor,
+ const QStyleOptionViewItem &option,
+ const QModelIndex &index) const
+{
+ uat_field_t *field = indexToField(index);
+
+ switch (field->mode) {
+ case PT_TXTMOD_DIRECTORYNAME:
+ case PT_TXTMOD_FILENAME:
+ editor->setGeometry(option.rect);
+ break;
+ default:
+ QStyledItemDelegate::updateEditorGeometry(editor, option, index);
+ }
+}
+
+void UatDelegate::pathHasChanged(QString)
+{
+ PathSelectionEdit * editor = qobject_cast<PathSelectionEdit *>(sender());
+ if (editor)
+ emit commitData(editor);
+}
diff --git a/ui/qt/models/uat_delegate.h b/ui/qt/models/uat_delegate.h
new file mode 100644
index 00000000..e57e533d
--- /dev/null
+++ b/ui/qt/models/uat_delegate.h
@@ -0,0 +1,42 @@
+/** @file
+ *
+ * Delegates for editing various field types in a UAT record.
+ *
+ * Copyright 2016 Peter Wu <peter@lekensteyn.nl>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef UAT_DELEGATE_H
+#define UAT_DELEGATE_H
+
+#include <config.h>
+#include <glib.h>
+#include <epan/uat-int.h>
+
+#include <QStyledItemDelegate>
+#include <QModelIndex>
+
+class UatDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+
+public:
+ UatDelegate(QObject *parent = 0);
+
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+ void setEditorData(QWidget *editor, const QModelIndex &index) const override;
+ void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const override;
+ void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const override;
+
+protected slots:
+ void pathHasChanged(QString newPath);
+
+private:
+ uat_field_t *indexToField(const QModelIndex &index) const;
+};
+#endif // UAT_DELEGATE_H
diff --git a/ui/qt/models/uat_model.cpp b/ui/qt/models/uat_model.cpp
new file mode 100644
index 00000000..a841ca8c
--- /dev/null
+++ b/ui/qt/models/uat_model.cpp
@@ -0,0 +1,545 @@
+/* uat_model.cpp
+ * Data model for UAT records.
+ *
+ * Copyright 2016 Peter Wu <peter@lekensteyn.nl>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "uat_model.h"
+#include <epan/to_str.h>
+#include <QBrush>
+#include <QDebug>
+
+UatModel::UatModel(QObject *parent, epan_uat *uat) :
+ QAbstractTableModel(parent),
+ uat_(0)
+{
+ loadUat(uat);
+}
+
+UatModel::UatModel(QObject * parent, QString tableName) :
+ QAbstractTableModel(parent),
+ uat_(0)
+{
+ loadUat(uat_get_table_by_name(tableName.toStdString().c_str()));
+}
+
+void UatModel::loadUat(epan_uat * uat)
+{
+ uat_ = uat;
+
+ dirty_records.reserve(uat_->raw_data->len);
+ // Validate existing data such that they can be marked as invalid if necessary.
+ record_errors.reserve(uat_->raw_data->len);
+ for (int i = 0; i < (int)uat_->raw_data->len; i++) {
+ record_errors.push_back(QMap<int, QString>());
+ checkRow(i);
+ // Assume that records are initially not modified.
+ dirty_records.push_back(false);
+ }
+}
+
+void UatModel::reloadUat()
+{
+ beginResetModel();
+ loadUat(uat_);
+ endResetModel();
+}
+
+bool UatModel::applyChanges(QString &error)
+{
+ if (uat_->changed) {
+ gchar *err = NULL;
+
+ if (!uat_save(uat_, &err)) {
+ error = QString("Error while saving %1: %2").arg(uat_->name).arg(err);
+ g_free(err);
+ }
+
+ if (uat_->post_update_cb) {
+ uat_->post_update_cb();
+ }
+ return true;
+ }
+
+ return false;
+}
+
+bool UatModel::revertChanges(QString &error)
+{
+ // Ideally this model should remember the changes made and try to undo them
+ // to avoid calling post_update_cb. Calling uat_clear + uat_load is a lazy
+ // option and might fail (e.g. when the UAT file is removed).
+ if (uat_->changed) {
+ gchar *err = NULL;
+ uat_clear(uat_);
+ if (!uat_load(uat_, NULL, &err)) {
+ error = QString("Error while loading %1: %2").arg(uat_->name).arg(err);
+ g_free(err);
+ }
+ return true;
+ }
+
+ return false;
+}
+
+Qt::ItemFlags UatModel::flags(const QModelIndex &index) const
+{
+ if (!index.isValid())
+ return Qt::ItemFlags();
+
+ uat_field_t *field = &uat_->fields[index.column()];
+
+ Qt::ItemFlags flags = QAbstractTableModel::flags(index);
+ if (field->mode == PT_TXTMOD_BOOL)
+ {
+ flags |= Qt::ItemIsUserCheckable;
+ }
+ flags |= Qt::ItemIsEditable;
+ return flags;
+}
+
+QVariant UatModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid()) {
+ return QVariant();
+ }
+
+ void *rec = UAT_INDEX_PTR(uat_, index.row());
+ uat_field_t *field = &uat_->fields[index.column()];
+ if (role == Qt::DisplayRole || role == Qt::EditRole) {
+ char *str = NULL;
+ guint length = 0;
+ field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data);
+
+ switch (field->mode) {
+ case PT_TXTMOD_HEXBYTES:
+ {
+ char* temp_str = bytes_to_str(NULL, (const guint8 *) str, length);
+ g_free(str);
+ QString qstr(temp_str);
+ wmem_free(NULL, temp_str);
+ return qstr;
+ }
+ case PT_TXTMOD_BOOL:
+ case PT_TXTMOD_COLOR:
+ return QVariant();
+ default:
+ {
+ QString qstr(str);
+ g_free(str);
+ return qstr;
+ }
+ }
+ }
+
+ if ((role == Qt::CheckStateRole) && (field->mode == PT_TXTMOD_BOOL))
+ {
+ char *str = NULL;
+ guint length = 0;
+ enum Qt::CheckState state = Qt::Unchecked;
+ field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data);
+ if ((g_strcmp0(str, "TRUE") == 0) ||
+ (g_strcmp0(str, "Enabled") == 0))
+ state = Qt::Checked;
+
+ g_free(str);
+ return state;
+ }
+
+ if (role == Qt::UserRole) {
+ return QVariant::fromValue(static_cast<void *>(field));
+ }
+
+ const QMap<int, QString> &errors = record_errors[index.row()];
+ // mark fields that fail the validation.
+ if (role == Qt::BackgroundRole) {
+ if (errors.contains(index.column())) {
+ // TODO is it OK to color cells like this? Maybe some other marker is better?
+ return QBrush("pink");
+ }
+ return QVariant();
+ }
+
+ if ((role == Qt::DecorationRole) && (field->mode == PT_TXTMOD_COLOR)) {
+ char *str = NULL;
+ guint length = 0;
+ field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data);
+
+ return QColor(QString(str));
+ }
+
+ // expose error message if any.
+ if (role == Qt::UserRole + 1) {
+ if (errors.contains(index.column())) {
+ return errors[index.column()];
+ }
+ return QVariant();
+ }
+
+ return QVariant();
+}
+
+QModelIndex UatModel::findRowForColumnContent(QVariant columnContent, int columnToCheckAgainst, int role)
+{
+ if (! columnContent.isValid())
+ return QModelIndex();
+
+ for (int i = 0; i < rowCount(); i++)
+ {
+ QVariant r_expr = data(index(i, columnToCheckAgainst), role);
+ if (r_expr == columnContent)
+ return index(i, columnToCheckAgainst);
+ }
+
+ return QModelIndex();
+}
+
+QVariant UatModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation != Qt::Horizontal) {
+ return QVariant();
+ }
+
+ if (role == Qt::ToolTipRole && uat_->fields[section].desc) {
+ return uat_->fields[section].desc;
+ }
+
+ if (role == Qt::DisplayRole) {
+ return uat_->fields[section].title;
+ }
+
+ return QVariant();
+}
+
+int UatModel::rowCount(const QModelIndex &parent) const
+{
+ // there are no children
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return uat_->raw_data->len;
+}
+
+int UatModel::columnCount(const QModelIndex &parent) const
+{
+ // there are no children
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return uat_->ncols;
+}
+
+QModelIndex UatModel::appendEntry(QVariantList rowData)
+{
+ // Don't add an empty row, or a row with more entries than we have columns,
+ // but a row with fewer can be added, where the remaining entries are empty
+ if (rowData.count() == 0 || rowData.count() > columnCount())
+ return QModelIndex();
+
+ QModelIndex newIndex;
+ int row = rowCount();
+ emit beginInsertRows(QModelIndex(), row, row);
+
+ // Initialize with given values
+ void *record = g_malloc0(uat_->record_size);
+ for (int col = 0; col < columnCount(); col++) {
+ uat_field_t *field = &uat_->fields[col];
+
+ QString data;
+ if (rowData.count() > col) {
+ if (field->mode != PT_TXTMOD_BOOL) {
+ data = rowData[col].toString();
+ } else {
+ if (rowData[col].toInt() == Qt::Checked) {
+ data = QString("TRUE");
+ } else {
+ data = QString("FALSE");
+ }
+ }
+ }
+
+ QByteArray bytes = field->mode == PT_TXTMOD_HEXBYTES ? QByteArray::fromHex(data.toUtf8()) : data.toUtf8();
+ field->cb.set(record, bytes.constData(), (unsigned) bytes.size(), field->cbdata.set, field->fld_data);
+ }
+ uat_insert_record_idx(uat_, row, record);
+ if (uat_->free_cb) {
+ uat_->free_cb(record);
+ }
+ g_free(record);
+
+ record_errors.insert(row, QMap<int, QString>());
+ // a new row is created. For the moment all fields are empty, so validation
+ // will likely mark everything as invalid. Ideally validation should be
+ // postponed until the row (in the view) is not selected anymore
+ checkRow(row);
+ dirty_records.insert(row, true);
+ uat_->changed = TRUE;
+
+ emit endInsertRows();
+
+ newIndex = index(row, 0, QModelIndex());
+
+ return newIndex;
+}
+
+bool UatModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid())
+ return false;
+
+ uat_field_t *field = &uat_->fields[index.column()];
+
+ if ((role != Qt::EditRole) &&
+ ((field->mode == PT_TXTMOD_BOOL) && (role != Qt::CheckStateRole)))
+ return false;
+
+ if (data(index, role) == value) {
+ // Data appears unchanged, do not do additional checks.
+ return true;
+ }
+
+ const int row = index.row();
+ void *rec = UAT_INDEX_PTR(uat_, row);
+
+ //qDebug() << "Changing (" << row << "," << index.column() << ") from " << data(index, Qt::EditRole) << " to " << value;
+ if (field->mode != PT_TXTMOD_BOOL) {
+ const QByteArray &str = value.toString().toUtf8();
+ const QByteArray &bytes = field->mode == PT_TXTMOD_HEXBYTES ? QByteArray::fromHex(str) : str;
+ field->cb.set(rec, bytes.constData(), (unsigned) bytes.size(), field->cbdata.set, field->fld_data);
+ } else {
+ if (value.toInt() == Qt::Checked) {
+ field->cb.set(rec, "TRUE", 4, field->cbdata.set, field->fld_data);
+ } else {
+ field->cb.set(rec, "FALSE", 5, field->cbdata.set, field->fld_data);
+ }
+ }
+
+ QVector<int> roles;
+ roles << role;
+
+ // Check validity of all rows, obtaining a list of columns where the
+ // validity status has changed.
+ const QList<int> &updated_cols = checkRow(row);
+ if (!updated_cols.isEmpty()) {
+ roles << Qt::BackgroundRole;
+ //qDebug() << "validation status changed:" << updated_cols;
+ }
+
+ if (record_errors[row].isEmpty()) {
+ // If all individual fields are valid, invoke the update callback. This
+ // might detect additional issues in either individual fields, or the
+ // combination of them.
+ if (uat_->update_cb) {
+ char *err = NULL;
+ if (!uat_->update_cb(rec, &err)) {
+ // TODO the error is not exactly on the first column, but we need a way to show the error.
+ record_errors[row].insert(0, err);
+ g_free(err);
+ }
+ }
+ }
+ uat_update_record(uat_, rec, record_errors[row].isEmpty());
+ dirty_records[row] = true;
+ uat_->changed = TRUE;
+
+ if (updated_cols.size() > updated_cols.count(index.column())) {
+ // The validation status for other columns were also affected by
+ // changing this field, mark those as dirty!
+ emit dataChanged(this->index(row, updated_cols.first()),
+ this->index(row, updated_cols.last()), roles);
+ } else {
+
+ emit dataChanged(index, index, roles);
+ }
+ return true;
+}
+
+bool UatModel::insertRows(int row, int count, const QModelIndex &/*parent*/)
+{
+ // support insertion of just one item for now.
+ if (count != 1 || row < 0 || row > rowCount())
+ return false;
+
+ beginInsertRows(QModelIndex(), row, row);
+
+ // Initialize with empty values, caller should use setData to populate it.
+ void *record = g_malloc0(uat_->record_size);
+ for (int col = 0; col < columnCount(); col++) {
+ uat_field_t *field = &uat_->fields[col];
+ field->cb.set(record, "", 0, field->cbdata.set, field->fld_data);
+ }
+ uat_insert_record_idx(uat_, row, record);
+ if (uat_->free_cb) {
+ uat_->free_cb(record);
+ }
+ g_free(record);
+
+ record_errors.insert(row, QMap<int, QString>());
+ // a new row is created. For the moment all fields are empty, so validation
+ // will likely mark everything as invalid. Ideally validation should be
+ // postponed until the row (in the view) is not selected anymore
+ checkRow(row);
+ dirty_records.insert(row, true);
+ uat_->changed = TRUE;
+ endInsertRows();
+ return true;
+}
+
+bool UatModel::removeRows(int row, int count, const QModelIndex &/*parent*/)
+{
+ if (count != 1 || row < 0 || row >= rowCount())
+ return false;
+
+ beginRemoveRows(QModelIndex(), row, row);
+ uat_remove_record_idx(uat_, row);
+ record_errors.removeAt(row);
+ dirty_records.removeAt(row);
+ uat_->changed = TRUE;
+ endRemoveRows();
+ return true;
+}
+
+void UatModel::clearAll()
+{
+ if (rowCount() < 1)
+ return;
+
+ beginResetModel();
+ uat_clear(uat_);
+ record_errors.clear();
+ dirty_records.clear();
+ uat_->changed = TRUE;
+ endResetModel();
+}
+
+QModelIndex UatModel::copyRow(QModelIndex original)
+{
+ if (! original.isValid())
+ return QModelIndex();
+
+ int newRow = rowCount();
+
+ beginInsertRows(QModelIndex(), newRow, newRow);
+
+ // Initialize with empty values, caller should use setData to populate it.
+ void *record = g_malloc0(uat_->record_size);
+ for (int col = 0; col < columnCount(); col++) {
+ uat_field_t *field = &uat_->fields[col];
+ field->cb.set(record, "", 0, field->cbdata.set, field->fld_data);
+ }
+ uat_insert_record_idx(uat_, newRow, record);
+ if (uat_->free_cb) {
+ uat_->free_cb(record);
+ }
+ g_free(record);
+
+ record_errors.insert(newRow, QMap<int, QString>());
+ // a new row is created. For the moment all fields are empty, so validation
+ // will likely mark everything as invalid. Ideally validation should be
+ // postponed until the row (in the view) is not selected anymore
+ checkRow(newRow);
+ dirty_records.insert(newRow, true);
+
+ // the UAT record has been created, now it is filled with the infromation
+ const void *src_record = UAT_INDEX_PTR(uat_, original.row());
+ void *dst_record = UAT_INDEX_PTR(uat_, newRow);
+ // insertRows always initializes the record with empty value. Before copying
+ // over the new values, be sure to clear the old fields.
+ if (uat_->free_cb) {
+ uat_->free_cb(dst_record);
+ }
+ if (uat_->copy_cb) {
+ uat_->copy_cb(dst_record, src_record, uat_->record_size);
+ } else {
+ /* According to documentation of uat_copy_cb_t memcpy should be used if uat_->copy_cb is NULL */
+ memcpy(dst_record, src_record, uat_->record_size);
+ }
+ gboolean src_valid = g_array_index(uat_->valid_data, gboolean, original.row());
+ uat_update_record(uat_, dst_record, src_valid);
+ record_errors[newRow] = record_errors[original.row()];
+ dirty_records[newRow] = true;
+
+ uat_->changed = TRUE;
+
+ endInsertRows();
+
+ return index(newRow, 0, QModelIndex());
+}
+
+bool UatModel::moveRow(int src_row, int dst_row)
+{
+ if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount())
+ return false;
+
+ int dst = src_row < dst_row ? dst_row + 1 : dst_row;
+
+ beginMoveRows(QModelIndex(), src_row, src_row, QModelIndex(), dst);
+ uat_move_index(uat_, src_row, dst_row);
+ record_errors.move(src_row, dst_row);
+ dirty_records.move(src_row, dst_row);
+ uat_->changed = TRUE;
+ endMoveRows();
+
+ return true;
+}
+
+bool UatModel::hasErrors() const
+{
+ for (int i = 0; i < rowCount(); i++) {
+ // Ignore errors on unmodified records, these should not prevent the OK
+ // button from saving modifications to other entries.
+ if (dirty_records[i] && !record_errors[i].isEmpty()) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool UatModel::checkField(int row, int col, char **error) const
+{
+ uat_field_t *field = &uat_->fields[col];
+ void *rec = UAT_INDEX_PTR(uat_, row);
+
+ if (!field->cb.chk) {
+ return true;
+ }
+
+ char *str = NULL;
+ guint length;
+ field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data);
+
+ bool ok = field->cb.chk(rec, str, length, field->cbdata.chk, field->fld_data, error);
+ g_free(str);
+ return ok;
+}
+
+// Validates all fields in the given record, setting error messages as needed.
+// Returns the columns that have changed (not the columns with errors).
+QList<int> UatModel::checkRow(int row)
+{
+ Q_ASSERT(0 <= row && row < rowCount());
+
+ QList<int> changed;
+ QMap<int, QString> &errors = record_errors[row];
+ for (int col = 0; col < columnCount(); col++) {
+ char *err;
+ bool error_changed = errors.remove(col) > 0;
+ if (!checkField(row, col, &err)) {
+ errors.insert(col, err);
+ g_free(err);
+ error_changed = !error_changed;
+ }
+ if (error_changed) {
+ changed << col;
+ }
+ }
+ return changed;
+}
diff --git a/ui/qt/models/uat_model.h b/ui/qt/models/uat_model.h
new file mode 100644
index 00000000..5da647a5
--- /dev/null
+++ b/ui/qt/models/uat_model.h
@@ -0,0 +1,82 @@
+/** @file
+ *
+ * Data model for UAT records.
+ *
+ * Copyright 2016 Peter Wu <peter@lekensteyn.nl>
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef UAT_MODEL_H
+#define UAT_MODEL_H
+
+#include <config.h>
+#include <glib.h>
+
+#include <QAbstractItemModel>
+#include <QList>
+#include <QMap>
+#include <epan/uat-int.h>
+
+class UatModel : public QAbstractTableModel
+{
+public:
+ UatModel(QObject *parent, uat_t *uat = 0);
+ UatModel(QObject *parent, QString tableName);
+
+ Qt::ItemFlags flags(const QModelIndex &index) const;
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+ bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
+ bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());
+
+ QModelIndex appendEntry(QVariantList row);
+
+ QModelIndex copyRow(QModelIndex original);
+ bool moveRow(int src_row, int dst_row);
+
+ bool moveRow(const QModelIndex &sourceParent, int sourceRow, const QModelIndex &destinationParent, int destinationChild);
+
+ void reloadUat();
+ bool hasErrors() const;
+ void clearAll();
+
+ /**
+ * If the UAT has changed, save the contents to file and invoke the UAT
+ * post_update_cb.
+ *
+ * @param error An error while saving changes, if any.
+ * @return true if anything changed, false otherwise.
+ */
+ bool applyChanges(QString &error);
+
+ /**
+ * Undo any changes to the UAT.
+ *
+ * @param error An error while restoring the original UAT, if any.
+ * @return true if anything changed, false otherwise.
+ */
+ bool revertChanges(QString &error);
+
+ QModelIndex findRowForColumnContent(QVariant columnContent, int columnToCheckAgainst, int role = Qt::DisplayRole);
+
+private:
+ bool checkField(int row, int col, char **error) const;
+ QList<int> checkRow(int row);
+ void loadUat(uat_t * uat = 0);
+
+ epan_uat *uat_;
+ QList<bool> dirty_records;
+ QList<QMap<int, QString> > record_errors;
+};
+#endif // UAT_MODEL_H
diff --git a/ui/qt/models/url_link_delegate.cpp b/ui/qt/models/url_link_delegate.cpp
new file mode 100644
index 00000000..a2c32bef
--- /dev/null
+++ b/ui/qt/models/url_link_delegate.cpp
@@ -0,0 +1,51 @@
+/* url_link_delegate.cpp
+ * Delegates for displaying links as links, including elide model
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include <ui/qt/models/url_link_delegate.h>
+
+#include <QPainter>
+
+#include <ui/qt/utils/color_utils.h>
+
+UrlLinkDelegate::UrlLinkDelegate(QObject *parent)
+ : QStyledItemDelegate(parent),
+ re_col_(-1),
+ url_re_(new QRegularExpression())
+{}
+
+UrlLinkDelegate::~UrlLinkDelegate()
+{
+ delete url_re_;
+}
+
+void UrlLinkDelegate::setColCheck(int column, QString &pattern)
+{
+ re_col_ = column;
+ url_re_->setPattern(pattern);
+}
+
+void UrlLinkDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
+ if (re_col_ >= 0 && url_re_) {
+ QModelIndex re_idx = index.model()->index(index.row(), re_col_);
+ QString col_text = index.model()->data(re_idx).toString();
+ if (!url_re_->match(col_text).hasMatch()) {
+ QStyledItemDelegate::paint(painter, option, index);
+ return;
+ }
+ }
+
+ QStyleOptionViewItem opt = option;
+ initStyleOption(&opt, index);
+
+ opt.font.setUnderline(true);
+ opt.palette.setColor(QPalette::Text, ColorUtils::themeLinkBrush().color());
+
+ QStyledItemDelegate::paint(painter, opt, index);
+}
diff --git a/ui/qt/models/url_link_delegate.h b/ui/qt/models/url_link_delegate.h
new file mode 100644
index 00000000..c3539284
--- /dev/null
+++ b/ui/qt/models/url_link_delegate.h
@@ -0,0 +1,36 @@
+/** @file
+ *
+ * Delegates for displaying links as links, including elide model
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef URL_LINK_DELEGATE_H
+#define URL_LINK_DELEGATE_H
+
+#include <QStyledItemDelegate>
+#include <QStyleOptionViewItem>
+#include <QModelIndex>
+#include <QRegularExpression>
+
+class UrlLinkDelegate : public QStyledItemDelegate
+{
+public:
+ explicit UrlLinkDelegate(QObject *parent = Q_NULLPTR);
+ ~UrlLinkDelegate();
+ // If pattern matches the string in column, render as a URL.
+ // Otherwise render as plain text.
+ void setColCheck(int column, QString &pattern);
+
+protected:
+ virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+
+private:
+ int re_col_;
+ QRegularExpression *url_re_;
+};
+#endif // URL_LINK_DELEGATE_H
diff --git a/ui/qt/models/voip_calls_info_model.cpp b/ui/qt/models/voip_calls_info_model.cpp
new file mode 100644
index 00000000..23ba46f1
--- /dev/null
+++ b/ui/qt/models/voip_calls_info_model.cpp
@@ -0,0 +1,249 @@
+/* voip_calls_info_model.cpp
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "voip_calls_info_model.h"
+#include <wsutil/utf8_entities.h>
+#include <wsutil/ws_assert.h>
+#include <ui/qt/utils/qt_ui_utils.h>
+
+#include <QDateTime>
+
+VoipCallsInfoModel::VoipCallsInfoModel(QObject *parent) :
+ QAbstractTableModel(parent),
+ mTimeOfDay_(false)
+{
+}
+
+voip_calls_info_t *VoipCallsInfoModel::indexToCallInfo(const QModelIndex &index)
+{
+ return VariantPointer<voip_calls_info_t>::asPtr(index.data(Qt::UserRole));
+}
+
+QVariant VoipCallsInfoModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid()) {
+ return QVariant();
+ }
+
+ // call_info will be non-NULL since the index is valid
+ voip_calls_info_t *call_info = static_cast<voip_calls_info_t *>(callinfos_[index.row()]);
+
+ if (role == Qt::UserRole) {
+ return VariantPointer<voip_calls_info_t>::asQVariant(call_info);
+ }
+
+ if (role != Qt::DisplayRole) {
+ return QVariant();
+ }
+
+ switch ((Column) index.column()) {
+ case StartTime:
+ return timeData(&(call_info->start_fd->abs_ts), &(call_info->start_rel_ts));
+ case StopTime:
+ return timeData(&(call_info->stop_fd->abs_ts), &(call_info->stop_rel_ts));
+ case InitialSpeaker:
+ return address_to_display_qstring(&(call_info->initial_speaker));
+ case From:
+ return QString::fromUtf8(call_info->from_identity);
+ case To:
+ return QString::fromUtf8(call_info->to_identity);
+ case Protocol:
+ return ((call_info->protocol == VOIP_COMMON) && call_info->protocol_name) ?
+ call_info->protocol_name : voip_protocol_name[call_info->protocol];
+ case Duration:
+ {
+ guint callDuration = nstime_to_sec(&(call_info->stop_fd->abs_ts)) - nstime_to_sec(&(call_info->start_fd->abs_ts));
+ return QString("%1:%2:%3").arg(callDuration / 3600, 2, 10, QChar('0')).arg((callDuration % 3600) / 60, 2, 10, QChar('0')).arg(callDuration % 60, 2, 10, QChar('0'));
+ }
+ case Packets:
+ return call_info->npackets;
+ case State:
+ return QString(voip_call_state_name[call_info->call_state]);
+ case Comments:
+ /* Add comments based on the protocol */
+ switch (call_info->protocol) {
+ case VOIP_ISUP:
+ {
+ isup_calls_info_t *isup_info = (isup_calls_info_t *)call_info->prot_info;
+ return QString("%1-%2 %3 %4-%5")
+ .arg(isup_info->ni)
+ .arg(isup_info->opc)
+ .arg(UTF8_RIGHTWARDS_ARROW)
+ .arg(isup_info->ni)
+ .arg(isup_info->dpc);
+ }
+ break;
+ case VOIP_H323:
+ {
+ h323_calls_info_t *h323_info = (h323_calls_info_t *)call_info->prot_info;
+ gboolean flag = FALSE;
+ static const QString on_str = tr("On");
+ static const QString off_str = tr("Off");
+ if (call_info->call_state == VOIP_CALL_SETUP) {
+ flag = h323_info->is_faststart_Setup;
+ } else {
+ if ((h323_info->is_faststart_Setup) && (h323_info->is_faststart_Proc)) {
+ flag = TRUE;
+ }
+ }
+ return tr("Tunneling: %1 Fast Start: %2")
+ .arg(h323_info->is_h245Tunneling ? on_str : off_str)
+ .arg(flag ? on_str : off_str);
+ }
+ break;
+ case VOIP_COMMON:
+ default:
+ return QString::fromUtf8(call_info->call_comment);
+ }
+ case ColumnCount:
+ ws_assert_not_reached();
+ }
+ return QVariant();
+}
+
+QVariant VoipCallsInfoModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
+ switch ((Column) section) {
+ case StartTime:
+ return tr("Start Time");
+ case StopTime:
+ return tr("Stop Time");
+ case InitialSpeaker:
+ return tr("Initial Speaker");
+ case From:
+ return tr("From");
+ case To:
+ return tr("To");
+ case Protocol:
+ return tr("Protocol");
+ case Duration:
+ return tr("Duration");
+ case Packets:
+ return tr("Packets");
+ case State:
+ return tr("State");
+ case Comments:
+ return tr("Comments");
+ case ColumnCount:
+ ws_assert_not_reached();
+ }
+ }
+ return QVariant();
+}
+
+int VoipCallsInfoModel::rowCount(const QModelIndex &parent) const
+{
+ // there are no children
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return static_cast<int>(callinfos_.size());
+}
+
+int VoipCallsInfoModel::columnCount(const QModelIndex &parent) const
+{
+ // there are no children
+ if (parent.isValid()) {
+ return 0;
+ }
+
+ return ColumnCount;
+}
+
+QVariant VoipCallsInfoModel::timeData(nstime_t *abs_ts, nstime_t *rel_ts) const
+{
+ if (mTimeOfDay_) {
+ return QDateTime::fromMSecsSinceEpoch(nstime_to_msec(abs_ts), Qt::LocalTime).toString("yyyy-MM-dd hh:mm:ss");
+ } else {
+ // XXX Pull digit count from capture file precision
+ return QString::number(nstime_to_sec(rel_ts), 'f', 6);
+ }
+}
+
+void VoipCallsInfoModel::setTimeOfDay(bool timeOfDay)
+{
+ mTimeOfDay_ = timeOfDay;
+ if (rowCount() > 0) {
+ // Update both the start and stop column in all rows.
+ emit dataChanged(index(0, StartTime), index(rowCount() - 1, StopTime));
+ }
+}
+
+bool VoipCallsInfoModel::timeOfDay() const
+{
+ return mTimeOfDay_;
+}
+
+void VoipCallsInfoModel::updateCalls(GQueue *callsinfos)
+{
+ if (callsinfos) {
+ qsizetype calls = callinfos_.count();
+ int cnt = 0;
+ GList *cur_call;
+
+ // Iterate new callsinfos and replace data in mode if required
+ cur_call = g_queue_peek_nth_link(callsinfos, 0);
+ while (cur_call && (cnt < calls)) {
+ if (callinfos_.at(cnt) != cur_call->data) {
+ // Data changed, use it
+ callinfos_.replace(cnt, cur_call->data);
+ }
+ cur_call = gxx_list_next(cur_call);
+ cnt++;
+ }
+
+ // Add new rows
+ cur_call = g_queue_peek_nth_link(callsinfos, rowCount());
+ guint extra = g_list_length(cur_call);
+ if (extra > 0) {
+ beginInsertRows(QModelIndex(), rowCount(), rowCount() + extra - 1);
+ while (cur_call && cur_call->data) {
+ voip_calls_info_t *call_info = gxx_list_data(voip_calls_info_t*, cur_call);
+ callinfos_.push_back(call_info);
+ cur_call = gxx_list_next(cur_call);
+ }
+ endInsertRows();
+ }
+ }
+}
+
+void VoipCallsInfoModel::removeAllCalls()
+{
+ beginRemoveRows(QModelIndex(), 0, rowCount() - 1);
+ callinfos_.clear();
+ endRemoveRows();
+}
+
+// Proxy model that allows columns to be sorted.
+VoipCallsInfoSortedModel::VoipCallsInfoSortedModel(QObject *parent) :
+ QSortFilterProxyModel(parent)
+{
+}
+
+bool VoipCallsInfoSortedModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
+{
+ voip_calls_info_t *a = VoipCallsInfoModel::indexToCallInfo(source_left);
+ voip_calls_info_t *b = VoipCallsInfoModel::indexToCallInfo(source_right);
+
+ if (a && b) {
+ switch (source_left.column()) {
+ case VoipCallsInfoModel::StartTime:
+ return nstime_cmp(&(a->start_rel_ts), &(b->start_rel_ts)) < 0;
+ case VoipCallsInfoModel::StopTime:
+ return nstime_cmp(&(a->stop_rel_ts), &(b->stop_rel_ts)) < 0;
+ case VoipCallsInfoModel::InitialSpeaker:
+ return cmp_address(&(a->initial_speaker), &(b->initial_speaker)) < 0;
+ }
+ }
+
+ // fallback to string cmp on other fields
+ return QSortFilterProxyModel::lessThan(source_left, source_right);
+}
diff --git a/ui/qt/models/voip_calls_info_model.h b/ui/qt/models/voip_calls_info_model.h
new file mode 100644
index 00000000..2f8d4007
--- /dev/null
+++ b/ui/qt/models/voip_calls_info_model.h
@@ -0,0 +1,71 @@
+/** @file
+ *
+ * Wireshark - Network traffic analyzer
+ * By Gerald Combs <gerald@wireshark.org>
+ * Copyright 1998 Gerald Combs
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef VOIP_CALLS_INFO_MODEL_H
+#define VOIP_CALLS_INFO_MODEL_H
+
+#include <config.h>
+#include <glib.h>
+
+#include "ui/voip_calls.h"
+#include <ui/qt/utils/variant_pointer.h>
+
+#include <QAbstractTableModel>
+#include <QSortFilterProxyModel>
+
+class VoipCallsInfoModel : public QAbstractTableModel
+{
+ Q_OBJECT
+
+public:
+ VoipCallsInfoModel(QObject *parent = 0);
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ QVariant headerData(int section, Qt::Orientation orientation,
+ int role = Qt::DisplayRole) const;
+ int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ void setTimeOfDay(bool timeOfDay);
+ bool timeOfDay() const;
+ void updateCalls(GQueue *callsinfos);
+ void removeAllCalls();
+
+ static voip_calls_info_t *indexToCallInfo(const QModelIndex &index);
+
+ enum Column
+ {
+ StartTime,
+ StopTime,
+ InitialSpeaker,
+ From,
+ To,
+ Protocol,
+ Duration,
+ Packets,
+ State,
+ Comments,
+ ColumnCount /* not an actual column, but used to find max. cols. */
+ };
+
+private:
+ QList<void *> callinfos_;
+ bool mTimeOfDay_;
+
+ QVariant timeData(nstime_t *abs_ts, nstime_t *rel_ts) const;
+};
+
+class VoipCallsInfoSortedModel : public QSortFilterProxyModel
+{
+public:
+ VoipCallsInfoSortedModel(QObject *parent = 0);
+
+protected:
+ bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const;
+};
+
+#endif // VOIP_CALLS_INFO_MODEL_H