/* interface_tree_cache_model.cpp * Model caching interface changes before sending them to global storage * * Wireshark - Network traffic analyzer * By Gerald Combs * 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/interface_tree_cache_model.h" #include "glib.h" #include "epan/prefs.h" #include "qt_ui_utils.h" #include "ui/capture_globals.h" #include "wsutil/utf8_entities.h" #include "wiretap/wtap.h" #include "wireshark_application.h" 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 *>(); 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 *>(); } else { if ( storage->count() > row ) storage->remove(storage->keys().at(row)); } } void InterfaceTreeCacheModel::saveNewDevices() { QList::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(&(*it)); bool useDevice = false; QMap * 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 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 * dataField = storage->value(idx, 0); /* Handle the storing of values for this device here */ if ( dataField ) { QMap::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(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(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(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::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 * 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(); 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 * 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 * 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 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: */