forked from osmocom/wireshark
Qt: Implement total columns for traffic tables
Implement a column type, which will show total values for traffic columns. Implements #15071
This commit is contained in:
parent
0640b711ea
commit
51d5cb42b2
|
@ -638,6 +638,10 @@ add_conversation_table_data_with_conv_id(
|
|||
new_conv_item.tx_frames = 0;
|
||||
new_conv_item.rx_bytes = 0;
|
||||
new_conv_item.tx_bytes = 0;
|
||||
new_conv_item.rx_frames_total = 0;
|
||||
new_conv_item.tx_frames_total = 0;
|
||||
new_conv_item.rx_bytes_total = 0;
|
||||
new_conv_item.tx_bytes_total = 0;
|
||||
|
||||
if (ts) {
|
||||
memcpy(&new_conv_item.start_time, ts, sizeof(new_conv_item.start_time));
|
||||
|
@ -782,6 +786,10 @@ add_hostlist_table_data(conv_hash_t *ch, const address *addr, guint32 port, gboo
|
|||
host.tx_frames=0;
|
||||
host.rx_bytes=0;
|
||||
host.tx_bytes=0;
|
||||
host.rx_frames_total=0;
|
||||
host.tx_frames_total=0;
|
||||
host.rx_bytes_total=0;
|
||||
host.tx_bytes_total=0;
|
||||
host.modified = TRUE;
|
||||
host.filtered = TRUE;
|
||||
|
||||
|
|
|
@ -111,10 +111,10 @@ typedef struct _conversation_item_t {
|
|||
guint64 rx_bytes; /**< number of received bytes */
|
||||
guint64 tx_bytes; /**< number of transmitted bytes */
|
||||
|
||||
guint64 rx_frames_total; /**< number of received packets */
|
||||
guint64 tx_frames_total; /**< number of transmitted packets */
|
||||
guint64 rx_bytes_total; /**< number of received bytes */
|
||||
guint64 tx_bytes_total; /**< number of transmitted bytes */
|
||||
guint64 rx_frames_total; /**< number of received packets total */
|
||||
guint64 tx_frames_total; /**< number of transmitted packets total */
|
||||
guint64 rx_bytes_total; /**< number of received bytes total */
|
||||
guint64 tx_bytes_total; /**< number of transmitted bytes total */
|
||||
|
||||
nstime_t start_time; /**< relative start time for the conversation */
|
||||
nstime_t stop_time; /**< relative stop time for the conversation */
|
||||
|
@ -135,10 +135,10 @@ typedef struct _hostlist_talker_t {
|
|||
guint64 rx_bytes; /**< number of received bytes */
|
||||
guint64 tx_bytes; /**< number of transmitted bytes */
|
||||
|
||||
guint64 rx_frames_total; /**< number of received packets */
|
||||
guint64 tx_frames_total; /**< number of transmitted packets */
|
||||
guint64 rx_bytes_total; /**< number of received bytes */
|
||||
guint64 tx_bytes_total; /**< number of transmitted bytes */
|
||||
guint64 rx_frames_total; /**< number of received packets total */
|
||||
guint64 tx_frames_total; /**< number of transmitted packets total */
|
||||
guint64 rx_bytes_total; /**< number of received bytes total */
|
||||
guint64 tx_bytes_total; /**< number of transmitted bytes total */
|
||||
|
||||
gboolean modified; /**< new to redraw the row */
|
||||
gboolean filtered; /**< the entry contains only filtered data */
|
||||
|
|
|
@ -30,10 +30,13 @@
|
|||
#include <QWidget>
|
||||
#include <QDateTime>
|
||||
|
||||
static QString formatString(qlonglong value)
|
||||
{
|
||||
return gchar_free_to_qstring(format_size(value, FORMAT_SIZE_UNIT_NONE, FORMAT_SIZE_PREFIX_SI));
|
||||
}
|
||||
|
||||
ATapDataModel::ATapDataModel(dataModelType type, int protoId, QString filter, QObject *parent):
|
||||
QAbstractListModel(parent),
|
||||
_protoId(protoId),
|
||||
_filter(filter)
|
||||
QAbstractListModel(parent)
|
||||
{
|
||||
hash_.conv_array = nullptr;
|
||||
hash_.hashtable = nullptr;
|
||||
|
@ -44,6 +47,9 @@ ATapDataModel::ATapDataModel(dataModelType type, int protoId, QString filter, QO
|
|||
_absoluteTime = false;
|
||||
_nanoseconds = false;
|
||||
|
||||
_protoId = protoId;
|
||||
_filter = filter;
|
||||
|
||||
_minRelStartTime = 0;
|
||||
_maxRelStopTime = 0;
|
||||
|
||||
|
@ -102,8 +108,8 @@ bool ATapDataModel::enableTap()
|
|||
|
||||
/* 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(), 0,
|
||||
&ATapDataModel::tapReset, conversationPacketHandler(), &ATapDataModel::tapDraw, nullptr);
|
||||
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) {
|
||||
_disableTap = true;
|
||||
emit tapListenerChanged(false);
|
||||
|
@ -209,7 +215,7 @@ void ATapDataModel::updateData(GArray * newData)
|
|||
endResetModel();
|
||||
|
||||
if (_type == ATapDataModel::DATAMODEL_CONVERSATION)
|
||||
((ConversationDataModel *)(this))->updateData();
|
||||
((ConversationDataModel *)(this))->doDataUpdate();
|
||||
}
|
||||
|
||||
bool ATapDataModel::resolveNames() const
|
||||
|
@ -273,7 +279,7 @@ void ATapDataModel::setFilter(QString filter)
|
|||
return;
|
||||
|
||||
_filter = filter;
|
||||
GString * errorString = set_tap_dfilter(&hash_, !filter.isEmpty() ? filter.toUtf8().constData() : nullptr);
|
||||
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 */
|
||||
|
@ -284,6 +290,11 @@ void ATapDataModel::setFilter(QString filter)
|
|||
g_string_free(errorString, TRUE);
|
||||
}
|
||||
|
||||
QString ATapDataModel::filter() const
|
||||
{
|
||||
return _filter;
|
||||
}
|
||||
|
||||
ATapDataModel::dataModelType ATapDataModel::modelType() const
|
||||
{
|
||||
return _type;
|
||||
|
@ -294,6 +305,12 @@ 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)
|
||||
{}
|
||||
|
@ -303,6 +320,8 @@ int EndpointDataModel::columnCount(const QModelIndex &) const
|
|||
int columnMax = ENDP_NUM_COLUMNS;
|
||||
if (portsAreHidden())
|
||||
columnMax--;
|
||||
if (showTotalColumn())
|
||||
columnMax += 2;
|
||||
return columnMax;
|
||||
}
|
||||
|
||||
|
@ -315,6 +334,14 @@ QVariant EndpointDataModel::headerData(int section, Qt::Orientation orientation,
|
|||
if (portsAreHidden() && section >= ENDP_COLUMN_PORT)
|
||||
column += 1;
|
||||
|
||||
if (showTotalColumn())
|
||||
{
|
||||
if (column == ENDP_COLUMN_PKT_AB || column == ENDP_COLUMN_BYTES_AB)
|
||||
column += 8;
|
||||
else if (column > ENDP_COLUMN_BYTES_AB)
|
||||
column -= 2;
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (column) {
|
||||
case 0:
|
||||
|
@ -341,6 +368,10 @@ QVariant EndpointDataModel::headerData(int section, Qt::Orientation orientation,
|
|||
return tr("AS Number"); break;
|
||||
case 11:
|
||||
return tr("AS Organization"); break;
|
||||
case 12:
|
||||
return tr("Total Packets"); break;
|
||||
case 13:
|
||||
return tr("Percent filtered"); break;
|
||||
}
|
||||
} else if (role == Qt::TextAlignmentRole) {
|
||||
if (section == ENDP_COLUMN_ADDR)
|
||||
|
@ -351,11 +382,6 @@ QVariant EndpointDataModel::headerData(int section, Qt::Orientation orientation,
|
|||
return QVariant();
|
||||
}
|
||||
|
||||
static QString formatString(qlonglong value)
|
||||
{
|
||||
return gchar_free_to_qstring(format_size(value, FORMAT_SIZE_UNIT_NONE, FORMAT_SIZE_PREFIX_SI));
|
||||
}
|
||||
|
||||
QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if (! idx.isValid())
|
||||
|
@ -378,12 +404,18 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
|
|||
QString ipAddress(addr);
|
||||
#endif
|
||||
|
||||
int column = idx.column();
|
||||
if (portsAreHidden() && idx.column() >= ENDP_COLUMN_PORT)
|
||||
column += 1;
|
||||
|
||||
if (showTotalColumn()) {
|
||||
if (column == ENDP_COLUMN_PKT_AB || column == ENDP_COLUMN_BYTES_AB)
|
||||
column += 8;
|
||||
else if (column > ENDP_COLUMN_BYTES_AB)
|
||||
column -= 2;
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole || role == ATapDataModel::UNFORMATTED_DISPLAYDATA) {
|
||||
|
||||
int column = idx.column();
|
||||
if (portsAreHidden() && idx.column() >= ENDP_COLUMN_PORT)
|
||||
column += 1;
|
||||
|
||||
switch (column) {
|
||||
case ENDP_COLUMN_ADDR: {
|
||||
char* addr_str = get_conversation_address(NULL, &item->myaddress, _resolveNames);
|
||||
|
@ -401,7 +433,10 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
|
|||
return quint32(item->port);
|
||||
}
|
||||
case ENDP_COLUMN_PACKETS:
|
||||
return (qlonglong)(item->tx_frames + item->rx_frames);
|
||||
{
|
||||
qlonglong packets = (qlonglong)(item->tx_frames + item->rx_frames);
|
||||
return 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));
|
||||
|
@ -433,6 +468,22 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
|
|||
return QVariant(mmdb_lookup->as_org);
|
||||
}
|
||||
return QVariant();
|
||||
case 12:
|
||||
{
|
||||
if (showTotalColumn())
|
||||
return (qlonglong)(item->tx_frames_total + item->rx_frames_total);
|
||||
return QVariant();
|
||||
}
|
||||
case 13:
|
||||
{
|
||||
if (showTotalColumn()) {
|
||||
qlonglong totalPackets = (qlonglong)(item->tx_frames_total + item->rx_frames_total);
|
||||
qlonglong packets = (qlonglong)(item->tx_frames + item->rx_frames);
|
||||
double percent = totalPackets == 0 ? 0 : packets * 100 / totalPackets;
|
||||
return QString::number(percent, 'f', 2) + "%";
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
@ -442,6 +493,8 @@ QVariant EndpointDataModel::data(const QModelIndex &idx, int role) const
|
|||
return Qt::AlignRight;
|
||||
} else if (role == ATapDataModel::DISPLAY_FILTER) {
|
||||
return QString(get_hostlist_filter(item));
|
||||
} else if (role == ATapDataModel::ROW_IS_FILTERED) {
|
||||
return (bool)item->filtered && showTotalColumn();
|
||||
}
|
||||
#ifdef HAVE_MAXMINDDB
|
||||
else if (role == ATapDataModel::GEODATA_AVAILABLE) {
|
||||
|
@ -460,7 +513,7 @@ ConversationDataModel::ConversationDataModel(int protoId, QString filter, QObjec
|
|||
ATapDataModel(ATapDataModel::DATAMODEL_CONVERSATION, protoId, filter, parent)
|
||||
{}
|
||||
|
||||
void ConversationDataModel::updateData()
|
||||
void ConversationDataModel::doDataUpdate()
|
||||
{
|
||||
_minRelStartTime = 0;
|
||||
_maxRelStopTime = 0;
|
||||
|
@ -490,6 +543,8 @@ int ConversationDataModel::columnCount(const QModelIndex &) const
|
|||
int columnMax = CONV_NUM_COLUMNS;
|
||||
if (portsAreHidden())
|
||||
columnMax -= 2;
|
||||
if (showTotalColumn())
|
||||
columnMax += 2;
|
||||
return columnMax;
|
||||
}
|
||||
|
||||
|
@ -506,6 +561,13 @@ QVariant ConversationDataModel::headerData(int section, Qt::Orientation orientat
|
|||
if (column > CONV_COLUMN_DST_ADDR)
|
||||
column++;
|
||||
}
|
||||
if (showTotalColumn())
|
||||
{
|
||||
if (column == CONV_COLUMN_PKT_AB || column == CONV_COLUMN_BYTES_AB)
|
||||
column += 8;
|
||||
else if (column > CONV_COLUMN_BYTES_AB)
|
||||
column -= 2;
|
||||
}
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
switch (column) {
|
||||
|
@ -537,6 +599,10 @@ QVariant ConversationDataModel::headerData(int section, Qt::Orientation orientat
|
|||
return tr("Bits/s A " UTF8_RIGHTWARDS_ARROW " B"); break;
|
||||
case 13:
|
||||
return tr("Bits/s B " UTF8_RIGHTWARDS_ARROW " A"); break;
|
||||
case 14:
|
||||
return tr("Total Packets"); break;
|
||||
case 15:
|
||||
return tr("Percent filtered"); break;
|
||||
}
|
||||
} else if (role == Qt::TextAlignmentRole) {
|
||||
if (column == CONV_COLUMN_SRC_ADDR || column == CONV_COLUMN_DST_ADDR)
|
||||
|
@ -580,6 +646,12 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
|
|||
bpsCalculated = true;
|
||||
}
|
||||
|
||||
if (showTotalColumn()) {
|
||||
if (column == CONV_COLUMN_PKT_AB || column == CONV_COLUMN_BYTES_AB)
|
||||
column += 8;
|
||||
else if (column > CONV_COLUMN_BYTES_AB)
|
||||
column -= 2;
|
||||
}
|
||||
if (role == Qt::DisplayRole || role == ATapDataModel::UNFORMATTED_DISPLAYDATA) {
|
||||
switch(column) {
|
||||
case CONV_COLUMN_SRC_ADDR:
|
||||
|
@ -651,6 +723,22 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
|
|||
return bpsCalculated ? (role == Qt::DisplayRole ? formatString(bps_ab) : QVariant((qlonglong)bps_ab)): QVariant();
|
||||
case CONV_COLUMN_BPS_BA:
|
||||
return bpsCalculated ? (role == Qt::DisplayRole ? formatString(bps_ba) : QVariant((qlonglong)bps_ba)): QVariant();
|
||||
case 14:
|
||||
{
|
||||
if (showTotalColumn())
|
||||
return (qlonglong)(conv_item->tx_frames_total + conv_item->rx_frames_total);
|
||||
return (qlonglong) 0;
|
||||
}
|
||||
case 15:
|
||||
{
|
||||
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);
|
||||
double percent = totalPackets == 0 ? 0 : packets * 100 / totalPackets;
|
||||
return QString::number(percent, 'f', 2) + "%";
|
||||
}
|
||||
return (qlonglong) 0;
|
||||
}
|
||||
}
|
||||
} else if (role == Qt::ToolTipRole) {
|
||||
if (column == CONV_COLUMN_START || column == CONV_COLUMN_DURATION)
|
||||
|
@ -675,6 +763,8 @@ QVariant ConversationDataModel::data(const QModelIndex &idx, int role) const
|
|||
return (int)(conv_item->etype);
|
||||
} 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();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
|
|
|
@ -46,7 +46,8 @@ public:
|
|||
#endif
|
||||
TIMELINE_DATA,
|
||||
ENDPOINT_DATATYPE,
|
||||
CONVERSATION_ID
|
||||
CONVERSATION_ID,
|
||||
ROW_IS_FILTERED
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
|
@ -106,6 +107,13 @@ public:
|
|||
*/
|
||||
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
|
||||
*
|
||||
|
@ -203,8 +211,8 @@ protected:
|
|||
void updateData(GArray * data);
|
||||
|
||||
dataModelType _type;
|
||||
|
||||
GArray * storage_;
|
||||
QString _filter;
|
||||
|
||||
bool _absoluteTime;
|
||||
bool _nanoseconds;
|
||||
|
@ -216,9 +224,10 @@ protected:
|
|||
|
||||
register_ct_t* registerTable() const;
|
||||
|
||||
bool showTotalColumn() const;
|
||||
|
||||
private:
|
||||
int _protoId;
|
||||
QString _filter;
|
||||
|
||||
QMap<QString, QVariant> _lookUp;
|
||||
|
||||
|
@ -286,7 +295,7 @@ public:
|
|||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
|
||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const;
|
||||
|
||||
void updateData();
|
||||
void doDataUpdate();
|
||||
|
||||
conv_item_t * itemForRow(int row);
|
||||
|
||||
|
|
|
@ -66,6 +66,24 @@ int TabData::protoId() const
|
|||
return _protoId;
|
||||
}
|
||||
|
||||
|
||||
TrafficDataFilterProxy::TrafficDataFilterProxy(QObject *parent) :
|
||||
QSortFilterProxyModel(parent)
|
||||
{}
|
||||
|
||||
bool TrafficDataFilterProxy::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
ATapDataModel * dataModel = qobject_cast<ATapDataModel *>(sourceModel());
|
||||
if (dataModel) {
|
||||
bool isFiltered = dataModel->data(dataModel->index(source_row, 0), ATapDataModel::ROW_IS_FILTERED).toBool();
|
||||
if (dataModel->filter().length() > 0)
|
||||
return ! isFiltered;
|
||||
}
|
||||
|
||||
return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
|
||||
}
|
||||
|
||||
|
||||
static gboolean iterateProtocols(const void *key, void *value, void *userdata)
|
||||
{
|
||||
QMap<int, QString> *protocols = (QMap<int, QString> *)userdata;
|
||||
|
@ -211,7 +229,7 @@ QTreeView * TrafficTab::createTree(int protoId)
|
|||
}
|
||||
}
|
||||
|
||||
QSortFilterProxyModel * proxyModel = new QSortFilterProxyModel();
|
||||
TrafficDataFilterProxy * proxyModel = new TrafficDataFilterProxy();
|
||||
proxyModel->setSourceModel(model);
|
||||
tree->setModel(proxyModel);
|
||||
|
||||
|
@ -221,13 +239,13 @@ QTreeView * TrafficTab::createTree(int protoId)
|
|||
|
||||
tree->sortByColumn(0, Qt::AscendingOrder);
|
||||
|
||||
connect(proxyModel, &QSortFilterProxyModel::modelReset, this, [tree]() {
|
||||
connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, [tree]() {
|
||||
if (tree->model()->rowCount() > 0) {
|
||||
for (int col = 0; col < tree->model()->columnCount(); col++)
|
||||
tree->resizeColumnToContents(col);
|
||||
}
|
||||
});
|
||||
connect(proxyModel, &QSortFilterProxyModel::modelReset, this, &TrafficTab::modelReset);
|
||||
connect(proxyModel, &TrafficDataFilterProxy::modelReset, this, &TrafficTab::modelReset);
|
||||
}
|
||||
|
||||
return tree;
|
||||
|
@ -339,7 +357,7 @@ void TrafficTab::doCurrentIndexChange(const QModelIndex & cur, const QModelIndex
|
|||
if (! cur.isValid())
|
||||
return;
|
||||
|
||||
const QSortFilterProxyModel * proxy = qobject_cast<const QSortFilterProxyModel *>(cur.model());
|
||||
const TrafficDataFilterProxy * proxy = qobject_cast<const TrafficDataFilterProxy *>(cur.model());
|
||||
if (! proxy)
|
||||
return;
|
||||
|
||||
|
@ -371,10 +389,10 @@ QVariant TrafficTab::currentItemData(int role)
|
|||
|
||||
void TrafficTab::modelReset()
|
||||
{
|
||||
if (! qobject_cast<QSortFilterProxyModel *>(sender()))
|
||||
if (! qobject_cast<TrafficDataFilterProxy *>(sender()))
|
||||
return;
|
||||
|
||||
QSortFilterProxyModel * qsfpm = qobject_cast<QSortFilterProxyModel *>(sender());
|
||||
TrafficDataFilterProxy * qsfpm = qobject_cast<TrafficDataFilterProxy *>(sender());
|
||||
if (! qobject_cast<ATapDataModel *>(qsfpm->sourceModel()))
|
||||
return;
|
||||
|
||||
|
@ -408,8 +426,8 @@ ATapDataModel * TrafficTab::modelForWidget(QWidget * searchWidget)
|
|||
{
|
||||
if (qobject_cast<QTreeView *>(searchWidget)) {
|
||||
QTreeView * tree = qobject_cast<QTreeView *>(searchWidget);
|
||||
if (qobject_cast<QSortFilterProxyModel *>(tree->model())) {
|
||||
QSortFilterProxyModel * qsfpm = qobject_cast<QSortFilterProxyModel *>(tree->model());
|
||||
if (qobject_cast<TrafficDataFilterProxy *>(tree->model())) {
|
||||
TrafficDataFilterProxy * qsfpm = qobject_cast<TrafficDataFilterProxy *>(tree->model());
|
||||
if (qobject_cast<ATapDataModel *>(qsfpm->sourceModel())) {
|
||||
return qobject_cast<ATapDataModel *>(qsfpm->sourceModel());
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <QFile>
|
||||
#include <QUrl>
|
||||
#include <QAbstractItemDelegate>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @brief Callback for creating an ATapDataModel
|
||||
|
@ -62,6 +63,17 @@ private:
|
|||
|
||||
Q_DECLARE_METATYPE(TabData)
|
||||
|
||||
class TrafficDataFilterProxy : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
TrafficDataFilterProxy(QObject *parent = nullptr);
|
||||
|
||||
protected:
|
||||
virtual bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A QTabWidget class, providing tap information
|
||||
*
|
||||
|
|
Loading…
Reference in New Issue