diff options
Diffstat (limited to 'ui/qt/models')
30 files changed, 5809 insertions, 0 deletions
diff --git a/ui/qt/models/cache_proxy_model.cpp b/ui/qt/models/cache_proxy_model.cpp new file mode 100644 index 0000000000..f98f59528b --- /dev/null +++ b/ui/qt/models/cache_proxy_model.cpp @@ -0,0 +1,112 @@ +/* cache_proxy_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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, SIGNAL(modelReset()), + this, SLOT(resetCacheModel())); + } else { + if (sourceModel()) { + // Prevent further updates to source model from invalidating cache. + disconnect(sourceModel(), SIGNAL(modelReset()), + this, SLOT(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 0000000000..9163a4695c --- /dev/null +++ b/ui/qt/models/cache_proxy_model.h @@ -0,0 +1,59 @@ +/* cache_proxy_model.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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/interface_sort_filter_model.cpp b/ui/qt/models/interface_sort_filter_model.cpp new file mode 100644 index 0000000000..ddaebb1c9c --- /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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 "wireshark_application.h" + +#include <QAbstractItemModel> + +InterfaceSortFilterModel::InterfaceSortFilterModel(QObject *parent) : + QSortFilterProxyModel(parent) +{ + resetAllFilter(); +} + +void InterfaceSortFilterModel::resetAllFilter() +{ + _filterHidden = true; + _filterTypes = true; + _invertTypeFilter = false; + _storeOnChange = 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(wsApp, SIGNAL(preferencesChanged()), this, SLOT(resetPreferenceData())); + resetPreferenceData(); + } +} + +void InterfaceSortFilterModel::setFilterHidden(bool filter) +{ + _filterHidden = filter; + + invalidate(); +} + +#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 _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(), _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 = QString(tr("No interfaces to be displayed. %1 interfaces filtered.")).arg(interfacesHidden()); + + return result; +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ 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 0000000000..5a6f35b97c --- /dev/null +++ b/ui/qt/models/interface_sort_filter_model.h @@ -0,0 +1,106 @@ +/* interface_sort_filter_model.h + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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(); + +#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; + +private: + bool _filterHidden; + bool _filterTypes; + bool _invertTypeFilter; + bool _storeOnChange; + +#ifdef HAVE_PCAP_REMOTE + bool _remoteDisplay; +#endif + + QList<int> displayHiddenTypes; + + QList<InterfaceTreeColumns> _columns; + +private slots: + void resetPreferenceData(); +}; + +#endif // INTERFACE_SORT_FILTER_MODEL_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ 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 0000000000..0937dbcde6 --- /dev/null +++ b/ui/qt/models/interface_tree_cache_model.cpp @@ -0,0 +1,620 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + + +#include <ui/qt/models/interface_tree_cache_model.h> + +#include "glib.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 "wireshark_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_INTERFACE_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(); + } +#ifdef HAVE_EXTCAP + 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 */ + } +#endif + 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 + + global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, idx); + g_array_insert_val(global_capture_opts.all_ifaces, idx, device); + + ++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_INTERFACE_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; +#ifdef HAVE_EXTCAP + if ( device.if_info.type == IF_EXTCAP ) + allowExtendedColumns = false; +#endif + 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; + } + + wsApp->emitAppSignal(WiresharkApplication::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() ) + return result; + if ( ! global_capture_opts.all_ifaces && newDevices.size() == 0 ) + 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 +{ + Q_UNUSED(index) + +#ifdef HAVE_LIBPCAP + const interface_t * device = lookup(index); + if ( device == 0 ) + return false; + +#ifdef HAVE_EXTCAP + 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 + +#endif + return true; +} + +// Whether this field is available for modification and display. +bool InterfaceTreeCacheModel::isAvailableField(const QModelIndex &index) const +{ + Q_UNUSED(index) + +#ifdef HAVE_LIBPCAP + 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 0; + + 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_INTERFACE_NAME ) + { + + 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 sourceModel->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 + { + g_array_remove_index(global_capture_opts.all_ifaces, row); + emit endRemoveRows(); + wsApp->emitAppSignal(WiresharkApplication::LocalInterfacesChanged); + } +} +#endif + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ 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 0000000000..3898857463 --- /dev/null +++ b/ui/qt/models/interface_tree_cache_model.h @@ -0,0 +1,92 @@ +/* interface_tree_cache_model.h + * Model caching interface changes before sending them to global storage + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 +{ + Q_OBJECT + +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_ */ + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/interface_tree_model.cpp b/ui/qt/models/interface_tree_model.cpp new file mode 100644 index 0000000000..7acda1340e --- /dev/null +++ b/ui/qt/models/interface_tree_model.cpp @@ -0,0 +1,558 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <ui/qt/models/interface_tree_model.h> + +#ifdef HAVE_LIBPCAP +#include "ui/capture.h" +#include "caputils/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 "wireshark_application.h" + +/* Needed for the meta type declaration of QList<int>* */ +#include <ui/qt/models/sparkline_delegate.h> + +#ifdef HAVE_EXTCAP +#include "extcap.h" +#endif + +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(wsApp, SIGNAL(appInitialized()), this, SLOT(interfaceListChanged())); + connect(wsApp, SIGNAL(localInterfaceListChanged()), this, SLOT(interfaceListChanged())); +} + +InterfaceTreeModel::~InterfaceTreeModel(void) +{ +#ifdef HAVE_LIBPCAP + if (stat_cache_) { + capture_stat_stop(stat_cache_); + stat_cache_ = NULL; + } +#endif // HAVE_LIBPCAP +} + +QString InterfaceTreeModel::interfaceError() +{ + QString errorText; + if ( rowCount() == 0 ) + { + errorText = tr("No Interfaces found."); + } +#ifdef HAVE_LIBPCAP + else if ( global_capture_opts.ifaces_err != 0 ) + { + errorText = tr(global_capture_opts.ifaces_err_info); + } +#endif + + return errorText; +} + +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.display_name); + } + else if ( col == IFTREE_COL_INTERFACE_NAME ) + { + return QString(device.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)); + } +#ifdef HAVE_EXTCAP + else if ( col == IFTREE_COL_EXTCAP_PATH ) + { + return QString(device.if_info.extcap); + } +#endif + 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_INTERFACE_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 ) + { + QString linkname = QObject::tr("DLT %1").arg(device.active_dlt); + for (GList *list = device.links; list != NULL; list = g_list_next(list)) { + link_row *linkr = (link_row*)(list->data); + if (linkr->dlt != -1 && 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 ( points.contains(device.name) ) + return qVariantFromValue(points[device.name]); + } + else if ( col == IFTREE_COL_HIDDEN ) + { + return QVariant::fromValue((bool)device.hidden); + } + } +#ifdef HAVE_EXTCAP + /* 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; + } + } +#endif + /* 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_INTERFACE_NAME ) + { + return tr("Friendly Name"); + } + else if ( section == IFTREE_COL_NAME ) + { + return tr("Interface Name"); + } + else if ( section == IFTREE_COL_PIPE_PATH ) + { + return tr("Local Pipe Path"); + } + else if ( section == IFTREE_COL_INTERFACE_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() +{ + emit beginResetModel(); + + points.clear(); + + emit 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', ", "); + } +#ifdef HAVE_EXTCAP + else if ( device.if_info.type == IF_EXTCAP ) + { + tt_str = QString(tr("Extcap interface: %1")).arg(get_basename(device.if_info.extcap)); + } +#endif + 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 + guint diff; + 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 ) + 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); + } + if ( !stat_cache_ ) + return; + + struct pcap_stat stats; + + diff = 0; + if ( capture_stats(stat_cache_, device.name, &stats) ) + { + 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; + + global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, idx); + g_array_insert_val(global_capture_opts.all_ifaces, idx, device); + } + + points[device.name].append(diff); + emit dataChanged(index(idx, IFTREE_COL_STATS), index(idx, IFTREE_COL_STATS)); +#else + Q_UNUSED(idx) +#endif +} + +void InterfaceTreeModel::getPoints(int idx, PointList *pts) +{ +#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 ( points.contains(device.name) ) + pts->append(points[device.name]); +#else + Q_UNUSED(idx) + Q_UNUSED(pts) +#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 ( !device.locked ) + { + 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; + } + device.locked = TRUE; + global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, idx); + g_array_insert_val(global_capture_opts.all_ifaces, idx, device); + + device.locked = FALSE; + global_capture_opts.all_ifaces = g_array_remove_index(global_capture_opts.all_ifaces, idx); + g_array_insert_val(global_capture_opts.all_ifaces, idx, device); + } + } +#else + Q_UNUSED(sourceSelection) +#endif + return selectionHasChanged; +} + + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/interface_tree_model.h b/ui/qt/models/interface_tree_model.h new file mode 100644 index 0000000000..a441b39e33 --- /dev/null +++ b/ui/qt/models/interface_tree_model.h @@ -0,0 +1,125 @@ +/* interface_tree_model.h + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef INTERFACE_TREE_MODEL_H +#define INTERFACE_TREE_MODEL_H + +#include <config.h> + +#ifdef HAVE_LIBPCAP +#include "ui/capture.h" +#include "ui/capture_globals.h" +#endif + +#include <glib.h> + +#include <QAbstractTableModel> +#include <QList> +#include <QMap> +#include <QItemSelection> + +typedef QList<int> PointList; + +enum InterfaceTreeColumns +{ +#ifdef HAVE_EXTCAP + IFTREE_COL_EXTCAP, + IFTREE_COL_EXTCAP_PATH, +#endif + IFTREE_COL_NAME, + IFTREE_COL_INTERFACE_NAME, + IFTREE_COL_INTERFACE_COMMENT, + IFTREE_COL_HIDDEN, + IFTREE_COL_DLT, + IFTREE_COL_PROMISCUOUSMODE, + IFTREE_COL_TYPE, + IFTREE_COL_STATS, + 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 getPoints(int idx, PointList *pts); + +protected slots: + void interfaceListChanged(); + +private: + QVariant toolTipForInterface(int idx) const; + QMap<QString, PointList> points; + +#ifdef HAVE_LIBPCAP + if_stat_cache_t *stat_cache_; +#endif // HAVE_LIBPCAP +}; + +#endif // INTERFACE_TREE_MODEL_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ 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 0000000000..cdcd41f359 --- /dev/null +++ b/ui/qt/models/numeric_value_chooser_delegate.cpp @@ -0,0 +1,117 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 = qVariantFromValue(_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, SIGNAL(valueChanged(int)), this, SLOT(onValueChanged(int))); + + 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 : qVariantFromValue(spinBox->value())); + } else { + QStyledItemDelegate::setModelData(editor, model, index); + } +} + +void NumericValueChooserDelegate::onValueChanged(int) +{ + QSpinBox * spinBox = qobject_cast<QSpinBox *>(sender()); + emit commitData(spinBox); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ 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 0000000000..87a504ada1 --- /dev/null +++ b/ui/qt/models/numeric_value_chooser_delegate.h @@ -0,0 +1,69 @@ +/* numeric_value_chooser_delegate.h + * Delegate to select a numeric value for a treeview entry + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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_ */ + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/packet_list_model.cpp b/ui/qt/models/packet_list_model.cpp new file mode 100644 index 0000000000..d2d5b2f15a --- /dev/null +++ b/ui/qt/models/packet_list_model.cpp @@ -0,0 +1,753 @@ +/* packet_list_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <algorithm> + +#include "packet_list_model.h" + +#include "file.h" + +#include <wsutil/nstime.h> +#include <epan/column.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 "wireshark_application.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 + +static const int reserved_packets_ = 100000; + +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) +{ + setCaptureFile(cf); + PacketListRecord::clearStringPool(); + + physical_rows_.reserve(reserved_packets_); + visible_rows_.reserve(reserved_packets_); + new_visible_rows_.reserve(1000); + number_to_row_.reserve(reserved_packets_); + + connect(this, SIGNAL(maxLineCountChanged(QModelIndex)), + this, SLOT(emitItemHeightChanged(QModelIndex)), + Qt::QueuedConnection); + idle_dissection_timer_ = new QElapsedTimer(); +} + +PacketListModel::~PacketListModel() +{ + delete idle_dissection_timer_; +} + +void PacketListModel::setCaptureFile(capture_file *cf) +{ + cap_file_ = cf; + resetColumns(); +} + +// 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() +{ + int pos = visible_rows_.count(); + + beginResetModel(); + visible_rows_.resize(0); + number_to_row_.fill(0); + endResetModel(); + + beginInsertRows(QModelIndex(), pos, pos); + foreach (PacketListRecord *record, physical_rows_) { + frame_data *fdata = record->frameData(); + + if (fdata->flags.passed_dfilter || fdata->flags.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] = visible_rows_.count(); + } + } + endInsertRows(); + idle_dissection_row_ = 0; + return visible_rows_.count(); +} + +void PacketListModel::clear() { + beginResetModel(); + qDeleteAll(physical_rows_); + physical_rows_.resize(0); + visible_rows_.resize(0); + new_visible_rows_.resize(0); + number_to_row_.resize(0); + PacketListRecord::clearStringPool(); + endResetModel(); + max_row_height_ = 0; + max_line_count_ = 1; + idle_dissection_row_ = 0; +} + +void PacketListModel::resetColumns() +{ + if (cap_file_) { + PacketListRecord::resetColumns(&cap_file_->cinfo); + } + dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); + headerDataChanged(Qt::Horizontal, 0, columnCount() - 1); +} + +void PacketListModel::resetColorized() +{ + foreach (PacketListRecord *record, physical_rows_) { + record->resetColorized(); + } + dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} + +void PacketListModel::toggleFrameMark(const QModelIndex &fm_index) +{ + if (!cap_file_ || !fm_index.isValid()) return; + + PacketListRecord *record = static_cast<PacketListRecord*>(fm_index.internalPointer()); + if (!record) return; + + frame_data *fdata = record->frameData(); + if (!fdata) return; + + if (fdata->flags.marked) + cf_unmark_frame(cap_file_, fdata); + else + cf_mark_frame(cap_file_, fdata); + + dataChanged(fm_index, fm_index); +} + +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()); + } + } + dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} + +void PacketListModel::toggleFrameIgnore(const QModelIndex &i_index) +{ + if (!cap_file_ || !i_index.isValid()) return; + + PacketListRecord *record = static_cast<PacketListRecord*>(i_index.internalPointer()); + if (!record) return; + + frame_data *fdata = record->frameData(); + if (!fdata) return; + + if (fdata->flags.ignored) + cf_unignore_frame(cap_file_, fdata); + else + cf_ignore_frame(cap_file_, fdata); +} + +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()); + } + } + dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} + +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->flags.ref_time) { + fdata->flags.ref_time=0; + cap_file_->ref_time_count--; + } else { + fdata->flags.ref_time=1; + cap_file_->ref_time_count++; + } + cf_reftime_packets(cap_file_); + if (!fdata->flags.ref_time && !fdata->flags.passed_dfilter) { + cap_file_->displayed_count--; + } + record->resetColumns(&cap_file_->cinfo); + 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->flags.ref_time) { + fdata->flags.ref_time = 0; + } + } + cap_file_->ref_time_count = 0; + cf_reftime_packets(cap_file_); + PacketListRecord::resetColumns(&cap_file_->cinfo); + dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} + +void PacketListModel::applyTimeShift() +{ + resetColumns(); + dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); +} + +void PacketListModel::setMaximiumRowHeight(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." + dataChanged(index(0, 0), index(0, columnCount() - 1)); +} + +//void PacketListModel::setMonospaceFont(const QFont &mono_font, int row_height) +//{ +// QFontMetrics fm(mono_font_); +// mono_font_ = mono_font; +// row_height_ = row_height; +// line_spacing_ = fm.lineSpacing(); +//} + +// The Qt MVC documentation suggests using QSortFilterProxyModel for sorting +// and filtering. That seems like overkill but it might be something we want +// to do in the future. + +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_; + +QElapsedTimer busy_timer_; +const int busy_timeout_ = 65; // ms, approximately 15 fps +void PacketListModel::sort(int column, Qt::SortOrder order) +{ + // packet_list_store.c:packet_list_dissect_and_cache_all + if (!cap_file_ || visible_rows_.count() < 1) return; + if (column < 0) return; + + sort_column_ = column; + text_sort_column_ = PacketListRecord::textColumn(column); + sort_order_ = order; + sort_cap_file_ = cap_file_; + + gboolean stop_flag = FALSE; + QString col_title = get_column_title(column); + + busy_timer_.start(); + emit pushProgressStatus(tr("Dissecting"), true, true, &stop_flag); + int row_num = 0; + foreach (PacketListRecord *row, physical_rows_) { + row->columnString(sort_cap_file_, column); + row_num++; + if (busy_timer_.elapsed() > busy_timeout_) { + if (stop_flag) { + emit popProgressStatus(); + return; + } + emit updateProgressStatus(row_num * 100 / physical_rows_.count()); + // What's the least amount of processing that we can do which will draw + // the progress indicator? + wsApp->processEvents(QEventLoop::AllEvents, 1); + busy_timer_.restart(); + } + } + emit popProgressStatus(); + + // XXX Use updateProgress instead. We'd have to switch from std::sort to + // something we can interrupt. + if (!col_title.isEmpty()) { + QString busy_msg = tr("Sorting \"%1\"").arg(col_title); + emit pushBusyStatus(busy_msg); + } + + busy_timer_.restart(); + sort_column_is_numeric_ = isNumericColumn(sort_column_); + std::sort(physical_rows_.begin(), physical_rows_.end(), recordLessThan); + + beginResetModel(); + visible_rows_.resize(0); + number_to_row_.fill(0); + foreach (PacketListRecord *record, physical_rows_) { + frame_data *fdata = record->frameData(); + + if (fdata->flags.passed_dfilter || fdata->flags.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] = visible_rows_.count(); + } + } + endResetModel(); + + if (!col_title.isEmpty()) { + emit popBusyStatus(); + } + + if (cap_file_->current_frame) { + emit goToPacket(cap_file_->current_frame->num); + } +} + +bool PacketListModel::isNumericColumn(int column) +{ + if (column < 0 || sort_cap_file_->cinfo.columns[column].col_fmt != COL_CUSTOM) { + return false; + } + + gchar **fields = g_regex_split_simple(COL_CUSTOM_PRIME_REGEX, + sort_cap_file_->cinfo.columns[column].col_custom_fields, + G_REGEX_ANCHORED, G_REGEX_MATCH_ANCHORED); + + for (guint i = 0; i < g_strv_length(fields); i++) { + if (!*fields[i]) { + continue; + } + + header_field_info *hfi = proto_registrar_get_byname(fields[i]); + /* + * 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)) || + !(((IS_FT_INT(hfi->type) || IS_FT_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))) { + g_strfreev(fields); + return false; + } + } + + g_strfreev(fields); + return true; +} + +bool PacketListModel::recordLessThan(PacketListRecord *r1, PacketListRecord *r2) +{ + int cmp_val = 0; + + // 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_) { + // What's the least amount of processing that we can do which will draw + // the busy indicator? + wsApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers, 1); + 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 { + if (r1->columnString(sort_cap_file_, sort_column_).constData() == r2->columnString(sort_cap_file_, sort_column_).constData()) { + cmp_val = 0; + } else if (sort_cap_file_->cinfo.columns[sort_column_].col_fmt == COL_CUSTOM) { + // Column comes from custom data + if (sort_column_is_numeric_) { + // Attempt to convert to numbers. + // XXX This is slow. Can we avoid doing this? + bool ok_r1, ok_r2; + double num_r1 = parseNumericColumn(r1->columnString(sort_cap_file_, sort_column_), &ok_r1); + double num_r2 = parseNumericColumn(r2->columnString(sort_cap_file_, sort_column_), &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 || (ok_r1 && num_r1 > num_r2)) { + cmp_val = 1; + } + } else { + cmp_val = strcmp(r1->columnString(sort_cap_file_, sort_column_).constData(), r2->columnString(sort_cap_file_, sort_column_).constData()); + } + } else { + cmp_val = strcmp(r1->columnString(sort_cap_file_, sort_column_).constData(), r2->columnString(sort_cap_file_, sort_column_).constData()); + } + + 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 &parent) const +{ + if (parent.column() >= prefs.num_cols) + return 0; + + return 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->flags.ignored) { + color = &prefs.gui_ignored_bg; + } else if (fdata->flags.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->flags.ignored) { + color = &prefs.gui_ignored_fg; + } else if (fdata->flags.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(); + QByteArray 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 get_column_title(section); + case Qt::ToolTipRole: + { + gchar *tooltip = get_column_tooltip(section); + QVariant data(tooltip); + g_free (tooltip); + return data; + } + default: + break; + } + } + + return QVariant(); +} + +void PacketListModel::flushVisibleRows() +{ + gint pos = visible_rows_.count(); + + if (new_visible_rows_.count() > 0) { + beginInsertRows(QModelIndex(), pos, pos + new_visible_rows_.count()); + foreach (PacketListRecord *record, new_visible_rows_) { + frame_data *fdata = record->frameData(); + + visible_rows_ << record; + if (number_to_row_.size() <= (int)fdata->num) { + number_to_row_.resize(fdata->num + 10000); + } + number_to_row_[fdata->num] = 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(idle_dissection_interval_, this, SLOT(dissectIdle())); + } else { + idle_dissection_timer_->invalidate(); + } + + // report colorization progress + 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); + gint 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->flags.passed_dfilter || fdata->flags.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, SLOT(flushVisibleRows())); + } + pos = visible_rows_.count() + new_visible_rows_.count() - 1; + } + + return pos; +} + +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->columnString(cap_file_, 1, true); + } +} + +int PacketListModel::visibleIndexOf(frame_data *fdata) const +{ + int row = 0; + foreach (PacketListRecord *record, visible_rows_) { + if (record->frameData() == fdata) { + return row; + } + row++; + } + + return -1; +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/packet_list_model.h b/ui/qt/models/packet_list_model.h new file mode 100644 index 0000000000..3dadb55011 --- /dev/null +++ b/ui/qt/models/packet_list_model.h @@ -0,0 +1,138 @@ +/* packet_list_model.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 "packet_list_record.h" + +#include "cfile.h" + +class QElapsedTimer; + +class PacketListModel : public QAbstractItemModel +{ + Q_OBJECT +public: + 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 | Qt::ToolTipRole) const; + + gint appendPacket(frame_data *fdata); + frame_data *getRowFdata(int row); + void ensureRowColorized(int row); + int visibleIndexOf(frame_data *fdata) const; + void resetColumns(); + void resetColorized(); + void toggleFrameMark(const QModelIndex &fm_index); + void setDisplayedFrameMark(gboolean set); + void toggleFrameIgnore(const QModelIndex &i_index); + void setDisplayedFrameIgnore(gboolean set); + void toggleFrameRefTime(const QModelIndex &rt_index); + void unsetAllFrameRefTime(); + void applyTimeShift(); + + void setMaximiumRowHeight(int height); + +signals: + void goToPacket(int); + void maxLineCountChanged(const QModelIndex &ih_index) const; + void itemHeightChanged(const QModelIndex &ih_index); + void pushBusyStatus(const QString &status); + void popBusyStatus(); + + void pushProgressStatus(const QString &status, bool animate, bool terminate_is_stop, gboolean *stop_flag); + void updateProgressStatus(int value); + void popProgressStatus(); + + void bgColorizationProgress(int first, int last); + +public slots: + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + 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); + + 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 + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/packet_list_record.cpp b/ui/qt/models/packet_list_record.cpp new file mode 100644 index 0000000000..bf6f866702 --- /dev/null +++ b/ui/qt/models/packet_list_record.cpp @@ -0,0 +1,333 @@ +/* packet_list_record.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "packet_list_record.h" + +#include <file.h> + +#include <epan/epan_dissect.h> +#include <epan/column-info.h> +#include <epan/column.h> +#include <epan/conversation.h> +#include <epan/wmem/wmem.h> + +#include <epan/color_filters.h> + +#include "frame_tvbuff.h" + +#include <QStringList> + +class ColumnTextList : public QList<const char *> { +public: + // Allocate our records using wmem. + static void *operator new(size_t size) { + return wmem_alloc(wmem_file_scope(), size); + } + + static void operator delete(void *) {} +}; + +QMap<int, int> PacketListRecord::cinfo_column_; +unsigned PacketListRecord::col_data_ver_ = 1; + +PacketListRecord::PacketListRecord(frame_data *frameData) : + col_text_(0), + fdata_(frameData), + lines_(1), + line_count_changed_(false), + data_ver_(0), + colorized_(false), + conv_(NULL) +{ +} + +void *PacketListRecord::operator new(size_t size) +{ + return wmem_alloc(wmem_file_scope(), size); +} + +// We might want to return a const char * instead. This would keep us from +// creating excessive QByteArrays, e.g. in PacketListModel::recordLessThan. +const QByteArray PacketListRecord::columnString(capture_file *cap_file, int column, bool colorized) +{ + // packet_list_store.c:packet_list_get_value + g_assert(fdata_); + + if (!cap_file || column < 0 || column > cap_file->cinfo.num_cols) { + return QByteArray(); + } + + bool dissect_color = colorized && !colorized_; + if (!col_text_ || column >= col_text_->size() || !col_text_->at(column) || data_ver_ != col_data_ver_ || dissect_color) { + dissect(cap_file, dissect_color); + } + + return col_text_->value(column, QByteArray()); +} + +void PacketListRecord::resetColumns(column_info *cinfo) +{ + col_data_ver_++; + + 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::resetColorized() +{ + colorized_ = false; +} + +void PacketListRecord::dissect(capture_file *cap_file, 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; + struct wtap_pkthdr phdr; /* Packet header */ + Buffer buf; /* Packet data */ + + if (!col_text_) col_text_ = new ColumnTextList; + gboolean dissect_columns = col_text_->isEmpty() || data_ver_ != col_data_ver_; + + if (!cap_file) { + return; + } + + memset(&phdr, 0, sizeof(struct wtap_pkthdr)); + + if (dissect_columns) { + cinfo = &cap_file->cinfo; + } + + ws_buffer_init(&buf, 1500); + if (!cf_read_record_r(cap_file, fdata_, &phdr, &buf)) { + /* + * 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); + 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_->flags.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, &phdr, frame_tvbuff_new_buffer(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; + } + data_ver_ = col_data_ver_; + + packet_info *pi = &edt.pi; + conv_ = find_conversation(pi->num, &pi->src, &pi->dst, pi->ptype, + pi->srcport, pi->destport, 0); + + epan_dissect_cleanup(&edt); + ws_buffer_free(&buf); +} + +// This assumes only one packet list. We might want to move this to +// PacketListModel (or replace this with a wmem allocator). +struct _GStringChunk *PacketListRecord::string_pool_ = g_string_chunk_new(1 * 1024 * 1024); +void PacketListRecord::clearStringPool() +{ + g_string_chunk_clear(string_pool_); +} + +//#define MINIMIZE_STRING_COPYING 1 +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; + } + + if (col_text_) { + col_text_->clear(); + } else { + col_text_ = new ColumnTextList; + } + lines_ = 1; + line_count_changed_ = false; + + for (int column = 0; column < cinfo->num_cols; ++column) { + int col_lines = 1; + +#ifdef MINIMIZE_STRING_COPYING + int text_col = cinfo_column_.value(column, -1); + + /* Column based on frame_data or it already contains a value */ + if (text_col < 0) { + col_fill_in_frame_data(fdata_, cinfo, column, FALSE); + col_text_->append(cinfo->columns[column].col_data); + continue; + } + + switch (cinfo->col_fmt[column]) { + case COL_PROTOCOL: + case COL_INFO: + case COL_IF_DIR: + case COL_DCE_CALL: + case COL_8021Q_VLAN_ID: + case COL_EXPERT: + case COL_FREQ_CHAN: + if (cinfo->columns[column].col_data && cinfo->columns[column].col_data != cinfo->columns[column].col_buf) { + /* This is a constant string, so we don't have to copy it */ + // XXX - ui/gtk/packet_list_store.c uses G_MAXUSHORT. We don't do proper UTF8 + // truncation in either case. + int col_text_len = MIN(qstrlen(cinfo->col_data[column]) + 1, COL_MAX_INFO_LEN); + col_text_->append(QByteArray::fromRawData(cinfo->columns[column].col_data, col_text_len)); + break; + } + /* !! FALL-THROUGH!! */ + + case COL_DEF_SRC: + case COL_RES_SRC: /* COL_DEF_SRC is currently just like COL_RES_SRC */ + case COL_UNRES_SRC: + case COL_DEF_DL_SRC: + case COL_RES_DL_SRC: + case COL_UNRES_DL_SRC: + case COL_DEF_NET_SRC: + case COL_RES_NET_SRC: + case COL_UNRES_NET_SRC: + case COL_DEF_DST: + case COL_RES_DST: /* COL_DEF_DST is currently just like COL_RES_DST */ + case COL_UNRES_DST: + case COL_DEF_DL_DST: + case COL_RES_DL_DST: + case COL_UNRES_DL_DST: + case COL_DEF_NET_DST: + case COL_RES_NET_DST: + case COL_UNRES_NET_DST: + default: + if (!get_column_resolved(column) && cinfo->col_expr.col_expr_val[column]) { + /* Use the unresolved value in col_expr_val */ + // XXX Use QContiguousCache? + col_text_->append(cinfo->col_expr.col_expr_val[column]); + } else { + col_text_->append(cinfo->columns[column].col_data); + } + break; + } +#else // MINIMIZE_STRING_COPYING + const char *col_str; + if (!get_column_resolved(column) && cinfo->col_expr.col_expr_val[column]) { + /* Use the unresolved value in col_expr_val */ + col_str = cinfo->col_expr.col_expr_val[column]; + } else { + int text_col = cinfo_column_.value(column, -1); + + if (text_col < 0) { + col_fill_in_frame_data(fdata_, cinfo, column, FALSE); + } + col_str = cinfo->columns[column].col_data; + } + // g_string_chunk_insert_const manages a hash table of pointers to + // strings: + // https://git.gnome.org/browse/glib/tree/glib/gstringchunk.c + // We might be better off adding the equivalent functionality to + // wmem_tree. + col_text_->append(g_string_chunk_insert_const(string_pool_, col_str)); + for (int i = 0; col_str[i]; i++) { + if (col_str[i] == '\n') col_lines++; + } + if (col_lines > lines_) { + lines_ = col_lines; + line_count_changed_ = true; + } +#endif // MINIMIZE_STRING_COPYING + } +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/packet_list_record.h b/ui/qt/models/packet_list_record.h new file mode 100644 index 0000000000..80d9cf7efa --- /dev/null +++ b/ui/qt/models/packet_list_record.h @@ -0,0 +1,106 @@ +/* packet_list_record.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PACKET_LIST_RECORD_H +#define PACKET_LIST_RECORD_H + +#include <config.h> + +#include <glib.h> + +#include "cfile.h" + +#include <epan/column-info.h> +#include <epan/packet.h> + +#include <QByteArray> +#include <QList> +#include <QVariant> + +struct conversation; +struct _GStringChunk; + +class ColumnTextList; + +class PacketListRecord +{ +public: + PacketListRecord(frame_data *frameData); + + // Allocate our records using wmem. + static void *operator new(size_t size); + static void operator delete(void *) {} + + // Return the string value for a column. Data is cached if possible. + const QByteArray 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_; } + struct conversation *conversation() { return conv_; } + + int columnTextSize(const char *str); + static void resetColumns(column_info *cinfo); + void resetColorized(); + inline int lineCount() { return lines_; } + inline int lineCountChanged() { return line_count_changed_; } + + static void clearStringPool(); + +private: + /** The column text for some columns */ + ColumnTextList *col_text_; + + frame_data *fdata_; + int lines_; + bool line_count_changed_; + static QMap<int, int> cinfo_column_; + + /** Data versions. Used to invalidate col_text_ */ + static unsigned col_data_ver_; + unsigned data_ver_; + /** Has this record been colorized? */ + bool colorized_; + + /** Conversation. Used by RelatedPacketDelegate */ + struct conversation *conv_; + + void dissect(capture_file *cap_file, bool dissect_color = false); + void cacheColumnStrings(column_info *cinfo); + + static struct _GStringChunk *string_pool_; + +}; + +#endif // PACKET_LIST_RECORD_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/path_chooser_delegate.cpp b/ui/qt/models/path_chooser_delegate.cpp new file mode 100644 index 0000000000..8538c75ea9 --- /dev/null +++ b/ui/qt/models/path_chooser_delegate.cpp @@ -0,0 +1,152 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include "epan/prefs.h" +#include "ui/last_open_dir.h" + +#include <ui/qt/models/path_chooser_delegate.h> + +#include <QHBoxLayout> +#include <QPushButton> +#include <QFileDialog> +#include <QWidget> +#include <QLineEdit> + +PathChooserDelegate::PathChooserDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + +QWidget* PathChooserDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &) const +{ + QWidget * pathEditor = new QWidget(parent); + QHBoxLayout *hbox = new QHBoxLayout(pathEditor); + pathEditor->setLayout(hbox); + QLineEdit * lineEdit = new QLineEdit(pathEditor); + QPushButton *btnBrowse = new QPushButton(pathEditor); + + btnBrowse->setText(tr("Browse")); + + hbox->setContentsMargins(0, 0, 0, 0); + hbox->addWidget(lineEdit); + hbox->addWidget(btnBrowse); + hbox->setSizeConstraint(QLayout::SetMinimumSize); + + // Grow the item to match the editor. According to the QAbstractItemDelegate + // documenation we're supposed to reimplement sizeHint but this seems to work. + QSize size = option.rect.size(); + size.setHeight(qMax(option.rect.height(), hbox->sizeHint().height())); + + lineEdit->selectAll(); + pathEditor->setFocusProxy(lineEdit); + pathEditor->setFocusPolicy(lineEdit->focusPolicy()); + + connect(btnBrowse, SIGNAL(pressed()), this, SLOT(browse_button_clicked())); + return pathEditor; +} + +void PathChooserDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &) const +{ + QRect rect = option.rect; + + // Make sure the editor doesn't get squashed. + editor->adjustSize(); + rect.setHeight(qMax(option.rect.height(), editor->height())); + editor->setGeometry(rect); +} + +void PathChooserDelegate::browse_button_clicked() +{ + char *open_dir = NULL; + + switch ( prefs.gui_fileopen_style ) + { + + case FO_STYLE_LAST_OPENED: + open_dir = get_last_open_dir(); + break; + + case FO_STYLE_SPECIFIED: + if ( prefs.gui_fileopen_dir[0] != '\0' ) + open_dir = prefs.gui_fileopen_dir; + break; + } + + QString file_name = QFileDialog::getOpenFileName(new QWidget(), tr("Open Pipe"), open_dir); + if ( !file_name.isEmpty() ) + { + QWidget * parent = ((QPushButton *)sender())->parentWidget(); + QLineEdit * lineEdit = parent->findChild<QLineEdit*>(); + if ( lineEdit ) + { + lineEdit->setText(file_name); + emit commitData(parent); + + } + } +} + +void PathChooserDelegate::setEditorData(QWidget *editor, const QModelIndex &idx) const +{ + if ( idx.isValid() ) + { + QString content = idx.data().toString(); + QLineEdit * lineEdit = editor->findChild<QLineEdit*>(); + if ( lineEdit ) + { + lineEdit->setText(content); + } + } + else + QStyledItemDelegate::setEditorData(editor, idx); +} + +void PathChooserDelegate::setModelData(QWidget *editor, QAbstractItemModel * model, const QModelIndex &idx) const +{ + if ( idx.isValid() ) + { + QLineEdit * lineEdit = editor->findChild<QLineEdit*>(); + if ( lineEdit ) + { + model->setData(idx, lineEdit->text()); + } + } + else + { + QStyledItemDelegate::setModelData(editor, model, idx); + } +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/path_chooser_delegate.h b/ui/qt/models/path_chooser_delegate.h new file mode 100644 index 0000000000..32ecd96eab --- /dev/null +++ b/ui/qt/models/path_chooser_delegate.h @@ -0,0 +1,58 @@ +/* path_chooser_delegate.h + * Delegate to select a file path for a treeview entry + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef PATH_CHOOSER_DELEGATE_H_ +#define PATH_CHOOSER_DELEGATE_H_ + +#include <QStyledItemDelegate> + +class PathChooserDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + PathChooserDelegate(QObject *parent = 0); + +protected: + QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &idx) const; + void updateEditorGeometry (QWidget * editor, const QStyleOptionViewItem & option, const QModelIndex & idx) const; + void setEditorData(QWidget *editor, const QModelIndex &idx) const; + void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &idx) const; + +private slots: + void browse_button_clicked(); +}; + +#endif /* PATH_CHOOSER_DELEGATE_H_ */ + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/percent_bar_delegate.cpp b/ui/qt/models/percent_bar_delegate.cpp new file mode 100644 index 0000000000..5158ac6ca0 --- /dev/null +++ b/ui/qt/models/percent_bar_delegate.cpp @@ -0,0 +1,116 @@ +/* percent_bar_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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()); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/percent_bar_delegate.h b/ui/qt/models/percent_bar_delegate.h new file mode 100644 index 0000000000..2c8d47288f --- /dev/null +++ b/ui/qt/models/percent_bar_delegate.h @@ -0,0 +1,79 @@ +/* percent_bar_delegate.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 +{ + Q_OBJECT + +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 + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/related_packet_delegate.cpp b/ui/qt/models/related_packet_delegate.cpp new file mode 100644 index 0000000000..6d02224f70 --- /dev/null +++ b/ui/qt/models/related_packet_delegate.cpp @@ -0,0 +1,273 @@ +/* related_packet_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <ui/qt/models/related_packet_delegate.h> +#include "packet_list_record.h" + +#include <ui/qt/utils/color_utils.h> + +#include <QApplication> +#include <QPainter> + +// 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 +{ +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + QStyleOptionViewItemV4 option_vi = option; +#else + QStyleOptionViewItem option_vi = option; +#endif + 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()); + +#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) + option_vi.features |= QStyleOptionViewItemV4::HasDecoration; +#else + option_vi.features |= QStyleOptionViewItem::HasDecoration; +#endif + 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; + } + + 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 (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. + if (setup_frame > 0 && last_frame > 0 && setup_frame != last_frame) { + if (fd->num == setup_frame) { + QPoint start_line[] = { + QPoint(en_w - 1, height / 2), + QPoint(0, height / 2), + QPoint(0, height) + }; + painter->drawPolyline(start_line, 3); + } else if (fd->num > setup_frame && fd->num < last_frame) { + painter->save(); + if (conv_ != record->conversation()) { + QPen other_pen(line_pen); + other_pen.setStyle(Qt::DashLine); + painter->setPen(other_pen); + } + painter->drawLine(0, 0, 0, height); + painter->restore(); + } else if (fd->num == last_frame) { + QPoint end_line[] = { + QPoint(en_w - 1, height / 2), + QPoint(0, height / 2), + QPoint(0, 0) + }; + painter->drawPolyline(end_line, 3); + } + } + + // 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_frames_.contains(fd->num)) { + painter->setBrush(fg); + switch (related_frames_[fd->num]) { + // 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_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 { + 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::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::addRelatedFrame(int frame_num, ft_framenum_type_t framenum_type) +{ + 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; +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/related_packet_delegate.h b/ui/qt/models/related_packet_delegate.h new file mode 100644 index 0000000000..152448dfe6 --- /dev/null +++ b/ui/qt/models/related_packet_delegate.h @@ -0,0 +1,78 @@ +/* related_packet_delegate.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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) { current_frame_ = 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 drawCheckMark(QPainter *painter, const QRect bbox) const; +signals: + + +}; + +#endif // RELATED_PACKET_DELEGATE_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/sparkline_delegate.cpp b/ui/qt/models/sparkline_delegate.cpp new file mode 100644 index 0000000000..2c3fc9c232 --- /dev/null +++ b/ui/qt/models/sparkline_delegate.cpp @@ -0,0 +1,120 @@ +/* sparkline_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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; + 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 (option_vi.state & QStyle::State_Selected) { + painter->setPen(option_vi.palette.color(cg, QPalette::HighlightedText)); + } else { + painter->setPen(option_vi.palette.color(cg, QPalette::Text)); + } + + 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)); + +// 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; +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/sparkline_delegate.h b/ui/qt/models/sparkline_delegate.h new file mode 100644 index 0000000000..62599abea6 --- /dev/null +++ b/ui/qt/models/sparkline_delegate.h @@ -0,0 +1,60 @@ +/* sparkline_delegate.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/timeline_delegate.cpp b/ui/qt/models/timeline_delegate.cpp new file mode 100644 index 0000000000..c9b0122b6a --- /dev/null +++ b/ui/qt/models/timeline_delegate.cpp @@ -0,0 +1,102 @@ +/* timeline_delegate.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <ui/qt/models/timeline_delegate.h> + +#include <ui/qt/utils/color_utils.h> + +#include <QApplication> +#include <QPainter> + +// 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) +{} + +void TimelineDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, + const QModelIndex &index) const +{ + QStyleOptionViewItem option_vi = option; + QStyledItemDelegate::initStyleOption(&option_vi, index); + + struct timeline_span span_px = index.data(Qt::UserRole).value<struct timeline_span>(); + + // Paint our rect with no text using the current style, then draw our + // bar and text over it. + QStyledItemDelegate::paint(painter, option, index); + + 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(); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/timeline_delegate.h b/ui/qt/models/timeline_delegate.h new file mode 100644 index 0000000000..bc64c24877 --- /dev/null +++ b/ui/qt/models/timeline_delegate.h @@ -0,0 +1,84 @@ +/* timeline_delegate.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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; +}; + +Q_DECLARE_METATYPE(timeline_span) + +class TimelineDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + TimelineDelegate(QWidget *parent = 0); + + // 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; +private: +}; + +#endif // TIMELINE_DELEGATE_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/uat_delegate.cpp b/ui/qt/models/uat_delegate.cpp new file mode 100644 index 0000000000..ee149a2fc8 --- /dev/null +++ b/ui/qt/models/uat_delegate.cpp @@ -0,0 +1,217 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <ui/qt/models/uat_delegate.h> +#include "epan/value_string.h" +#include <QComboBox> +#include <QEvent> +#include <QFileDialog> +#include <QLineEdit> +#include <QCheckBox> + +#include <ui/qt/widgets/display_filter_edit.h> + +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); + + switch (field->mode) { + case PT_TXTMOD_DIRECTORYNAME: + case PT_TXTMOD_FILENAME: + // TODO tab navigation from this field is broken. + // Do not create editor, a dialog will be opened in editorEvent + return 0; + + case PT_TXTMOD_ENUM: + { + // Note: the string repr. is written, not the integer value. + QComboBox *editor = new QComboBox(parent); + const value_string *enum_vals = (const value_string *)field->fld_data; + for (int i = 0; enum_vals[i].strptr != NULL; i++) { + editor->addItem(enum_vals[i].strptr); + } + return editor; + } + + case PT_TXTMOD_STRING: + // TODO add a live validator? Should SyntaxLineEdit be used? + return QStyledItemDelegate::createEditor(parent, option, index); + + case PT_TXTMOD_DISPLAY_FILTER: + { + DisplayFilterEdit *editor = new DisplayFilterEdit(parent); + return editor; + } + 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. + QRegExp hexbytes_regex("([0-9a-f]{2}[ :]?)*"); + hexbytes_regex.setCaseSensitivity(Qt::CaseInsensitive); + // 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 *editor = static_cast<QLineEdit *>( + QStyledItemDelegate::createEditor(parent, option, index)); + editor->setValidator(new QRegExpValidator(hexbytes_regex, editor)); + return editor; + } + + case PT_TXTMOD_BOOL: + { + // model will handle creating checkbox + return 0; + } + + case PT_TXTMOD_NONE: + return 0; + + default: + g_assert_not_reached(); + return 0; + } +} + +void UatDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const +{ + uat_field_t *field = indexToField(index); + + switch (field->mode) { + case PT_TXTMOD_ENUM: + { + QComboBox *combobox = static_cast<QComboBox *>(editor); + const QString &data = index.model()->data(index, Qt::EditRole).toString(); +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + combobox->setCurrentText(data); +#else + int new_index = combobox->findText(data); + if (new_index >= 0) { + combobox->setCurrentIndex(new_index); + } +#endif + + 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_ENUM: + { + QComboBox *combobox = static_cast<QComboBox *>(editor); + const QString &data = combobox->currentText(); + model->setData(index, data, Qt::EditRole); + break; + } + + default: + QStyledItemDelegate::setModelData(editor, model, index); + } +} + +#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 + +bool UatDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index) +{ + uat_field_t *field = indexToField(index); + + switch (field->mode) { + case PT_TXTMOD_DIRECTORYNAME: + case PT_TXTMOD_FILENAME: + if (event && (event->type() == QEvent::MouseButtonRelease || + event->type() == QEvent::MouseButtonDblClick)) { + // Ignore these mouse events, only handle MouseButtonPress. + return false; + } + if (index.isValid()) { + QString filename_old = model->data(index, Qt::EditRole).toString(); + QString filename = openFileDialog(field, filename_old); + // TODO should this overwrite only when !filename.isEmpty()? + model->setData(index, filename, Qt::EditRole); + } + // returns false to ensure that QAbstractItemView::edit does not assume + // the editing state. This causes the view's currentIndex to be changed + // to the cell where this delegate was "created", as desired. + return false; + + default: + return QStyledItemDelegate::editorEvent(event, model, option, index); + } +} + +QString UatDelegate::openFileDialog(uat_field_t *field, const QString &cur_path) const +{ + // Note: file dialogs have their parent widget set to NULL because we do not + // have an editor nor the view that would attach us. + switch (field->mode) { + case PT_TXTMOD_DIRECTORYNAME: + return QFileDialog::getExistingDirectory(NULL, field->title, cur_path); + + case PT_TXTMOD_FILENAME: + return QFileDialog::getOpenFileName(NULL, field->title, cur_path, + QString(), NULL, QFileDialog::DontConfirmOverwrite); + + default: + g_assert_not_reached(); + return 0; + } +} + +/* * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/uat_delegate.h b/ui/qt/models/uat_delegate.h new file mode 100644 index 0000000000..795161a686 --- /dev/null +++ b/ui/qt/models/uat_delegate.h @@ -0,0 +1,57 @@ +/* uat_delegate.h + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef UAT_DELEGATE_H +#define UAT_DELEGATE_H + +#include <config.h> +#include <glib.h> +#include <epan/uat-int.h> +#include <QStyledItemDelegate> + +class UatDelegate : public QStyledItemDelegate +{ + Q_OBJECT + +public: + UatDelegate(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; + +#if 0 + void updateEditorGeometry(QWidget *editor, + const QStyleOptionViewItem &option, const QModelIndex &index) const; +#endif + + bool editorEvent(QEvent *event, QAbstractItemModel *model, const QStyleOptionViewItem &option, const QModelIndex &index); + +private: + uat_field_t *indexToField(const QModelIndex &index) const; + QString openFileDialog(uat_field_t *field, const QString &cur_path) 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 0000000000..5b0813f0a6 --- /dev/null +++ b/ui/qt/models/uat_model.cpp @@ -0,0 +1,417 @@ +/* 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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); + } +} + +Qt::ItemFlags UatModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return 0; + + 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); + + if (field->mode == 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; + } else if (field->mode == PT_TXTMOD_BOOL) { + return ""; + } else { + 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) + 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(); + } + + // 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; +} + +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 == 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 fields are valid, invoke the update callback + 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, TRUE); + } else { + uat_update_record(uat_, rec, FALSE); + } + 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()) +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + , roles +#endif + ); + } else { + + emit dataChanged(index, index +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + , roles +#endif + ); + } + 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; +} + +bool UatModel::copyRow(int dst_row, int src_row) +{ + if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) { + return false; + } + + const void *src_record = UAT_INDEX_PTR(uat_, src_row); + void *dst_record = UAT_INDEX_PTR(uat_, dst_row); + // 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, src_row); + uat_update_record(uat_, dst_record, src_valid); + record_errors[dst_row] = record_errors[src_row]; + dirty_records[dst_row] = true; + + QVector<int> roles; + roles << Qt::EditRole << Qt::BackgroundRole; + emit dataChanged(index(dst_row, 0), index(dst_row, columnCount()) +#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) + , roles +#endif + ); + + 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; +} + +/* * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ diff --git a/ui/qt/models/uat_model.h b/ui/qt/models/uat_model.h new file mode 100644 index 0000000000..c52646d7c3 --- /dev/null +++ b/ui/qt/models/uat_model.h @@ -0,0 +1,70 @@ +/* uat_model.h + * 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 + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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 +{ + Q_OBJECT + +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) 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()); + + bool copyRow(int dst_row, int src_row); + bool hasErrors() const; + + 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/voip_calls_info_model.cpp b/ui/qt/models/voip_calls_info_model.cpp new file mode 100644 index 0000000000..dbb691fe8b --- /dev/null +++ b/ui/qt/models/voip_calls_info_model.cpp @@ -0,0 +1,246 @@ +/* voip_calls_info_model.cpp + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "voip_calls_info_model.h" +#include <wsutil/utf8_entities.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 call_info->from_identity; + case To: + return 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 call_info->call_comment; + } + case ColumnCount: + g_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: + g_assert_not_reached(); + } + } + return QVariant(); +} + +int VoipCallsInfoModel::rowCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return 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::fromTime_t(nstime_to_sec(abs_ts)).toTimeSpec(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)); + } +} + +void VoipCallsInfoModel::updateCalls(GQueue *callsinfos) +{ + if (callsinfos) { + GList *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 = (voip_calls_info_t*) cur_call->data; + callinfos_.push_back(call_info); + cur_call = g_list_next(cur_call); + } + endInsertRows(); + } + } +} + + +// 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); +} + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ 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 0000000000..835d38e76a --- /dev/null +++ b/ui/qt/models/voip_calls_info_model.h @@ -0,0 +1,96 @@ +/* voip_calls_info_model.h + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#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); + void updateCalls(GQueue *callsinfos); + + 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 +{ + Q_OBJECT + +public: + VoipCallsInfoSortedModel(QObject *parent = 0); + +protected: + bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const; +}; + +#endif // VOIP_CALLS_INFO_MODEL_H + +/* + * Editor modelines + * + * Local Variables: + * c-basic-offset: 4 + * tab-width: 8 + * indent-tabs-mode: nil + * End: + * + * ex: set shiftwidth=4 tabstop=8 expandtab: + * :indentSize=4:tabSize=8:noTabs=true: + */ |