diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 20:34:10 +0000 |
commit | e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc (patch) | |
tree | 68cb5ef9081156392f1dd62a00c6ccc1451b93df /ui/qt/models/uat_model.cpp | |
parent | Initial commit. (diff) | |
download | wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.tar.xz wireshark-e4ba6dbc3f1e76890b22773807ea37fe8fa2b1bc.zip |
Adding upstream version 4.2.2.upstream/4.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ui/qt/models/uat_model.cpp')
-rw-r--r-- | ui/qt/models/uat_model.cpp | 545 |
1 files changed, 545 insertions, 0 deletions
diff --git a/ui/qt/models/uat_model.cpp b/ui/qt/models/uat_model.cpp new file mode 100644 index 00000000..a841ca8c --- /dev/null +++ b/ui/qt/models/uat_model.cpp @@ -0,0 +1,545 @@ +/* uat_model.cpp + * Data model for UAT records. + * + * Copyright 2016 Peter Wu <peter@lekensteyn.nl> + * + * Wireshark - Network traffic analyzer + * By Gerald Combs <gerald@wireshark.org> + * Copyright 1998 Gerald Combs + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "uat_model.h" +#include <epan/to_str.h> +#include <QBrush> +#include <QDebug> + +UatModel::UatModel(QObject *parent, epan_uat *uat) : + QAbstractTableModel(parent), + uat_(0) +{ + loadUat(uat); +} + +UatModel::UatModel(QObject * parent, QString tableName) : + QAbstractTableModel(parent), + uat_(0) +{ + loadUat(uat_get_table_by_name(tableName.toStdString().c_str())); +} + +void UatModel::loadUat(epan_uat * uat) +{ + uat_ = uat; + + dirty_records.reserve(uat_->raw_data->len); + // Validate existing data such that they can be marked as invalid if necessary. + record_errors.reserve(uat_->raw_data->len); + for (int i = 0; i < (int)uat_->raw_data->len; i++) { + record_errors.push_back(QMap<int, QString>()); + checkRow(i); + // Assume that records are initially not modified. + dirty_records.push_back(false); + } +} + +void UatModel::reloadUat() +{ + beginResetModel(); + loadUat(uat_); + endResetModel(); +} + +bool UatModel::applyChanges(QString &error) +{ + if (uat_->changed) { + gchar *err = NULL; + + if (!uat_save(uat_, &err)) { + error = QString("Error while saving %1: %2").arg(uat_->name).arg(err); + g_free(err); + } + + if (uat_->post_update_cb) { + uat_->post_update_cb(); + } + return true; + } + + return false; +} + +bool UatModel::revertChanges(QString &error) +{ + // Ideally this model should remember the changes made and try to undo them + // to avoid calling post_update_cb. Calling uat_clear + uat_load is a lazy + // option and might fail (e.g. when the UAT file is removed). + if (uat_->changed) { + gchar *err = NULL; + uat_clear(uat_); + if (!uat_load(uat_, NULL, &err)) { + error = QString("Error while loading %1: %2").arg(uat_->name).arg(err); + g_free(err); + } + return true; + } + + return false; +} + +Qt::ItemFlags UatModel::flags(const QModelIndex &index) const +{ + if (!index.isValid()) + return Qt::ItemFlags(); + + uat_field_t *field = &uat_->fields[index.column()]; + + Qt::ItemFlags flags = QAbstractTableModel::flags(index); + if (field->mode == PT_TXTMOD_BOOL) + { + flags |= Qt::ItemIsUserCheckable; + } + flags |= Qt::ItemIsEditable; + return flags; +} + +QVariant UatModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) { + return QVariant(); + } + + void *rec = UAT_INDEX_PTR(uat_, index.row()); + uat_field_t *field = &uat_->fields[index.column()]; + if (role == Qt::DisplayRole || role == Qt::EditRole) { + char *str = NULL; + guint length = 0; + field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data); + + switch (field->mode) { + case PT_TXTMOD_HEXBYTES: + { + char* temp_str = bytes_to_str(NULL, (const guint8 *) str, length); + g_free(str); + QString qstr(temp_str); + wmem_free(NULL, temp_str); + return qstr; + } + case PT_TXTMOD_BOOL: + case PT_TXTMOD_COLOR: + return QVariant(); + default: + { + QString qstr(str); + g_free(str); + return qstr; + } + } + } + + if ((role == Qt::CheckStateRole) && (field->mode == PT_TXTMOD_BOOL)) + { + char *str = NULL; + guint length = 0; + enum Qt::CheckState state = Qt::Unchecked; + field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data); + if ((g_strcmp0(str, "TRUE") == 0) || + (g_strcmp0(str, "Enabled") == 0)) + state = Qt::Checked; + + g_free(str); + return state; + } + + if (role == Qt::UserRole) { + return QVariant::fromValue(static_cast<void *>(field)); + } + + const QMap<int, QString> &errors = record_errors[index.row()]; + // mark fields that fail the validation. + if (role == Qt::BackgroundRole) { + if (errors.contains(index.column())) { + // TODO is it OK to color cells like this? Maybe some other marker is better? + return QBrush("pink"); + } + return QVariant(); + } + + if ((role == Qt::DecorationRole) && (field->mode == PT_TXTMOD_COLOR)) { + char *str = NULL; + guint length = 0; + field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data); + + return QColor(QString(str)); + } + + // expose error message if any. + if (role == Qt::UserRole + 1) { + if (errors.contains(index.column())) { + return errors[index.column()]; + } + return QVariant(); + } + + return QVariant(); +} + +QModelIndex UatModel::findRowForColumnContent(QVariant columnContent, int columnToCheckAgainst, int role) +{ + if (! columnContent.isValid()) + return QModelIndex(); + + for (int i = 0; i < rowCount(); i++) + { + QVariant r_expr = data(index(i, columnToCheckAgainst), role); + if (r_expr == columnContent) + return index(i, columnToCheckAgainst); + } + + return QModelIndex(); +} + +QVariant UatModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (orientation != Qt::Horizontal) { + return QVariant(); + } + + if (role == Qt::ToolTipRole && uat_->fields[section].desc) { + return uat_->fields[section].desc; + } + + if (role == Qt::DisplayRole) { + return uat_->fields[section].title; + } + + return QVariant(); +} + +int UatModel::rowCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return uat_->raw_data->len; +} + +int UatModel::columnCount(const QModelIndex &parent) const +{ + // there are no children + if (parent.isValid()) { + return 0; + } + + return uat_->ncols; +} + +QModelIndex UatModel::appendEntry(QVariantList rowData) +{ + // Don't add an empty row, or a row with more entries than we have columns, + // but a row with fewer can be added, where the remaining entries are empty + if (rowData.count() == 0 || rowData.count() > columnCount()) + return QModelIndex(); + + QModelIndex newIndex; + int row = rowCount(); + emit beginInsertRows(QModelIndex(), row, row); + + // Initialize with given values + void *record = g_malloc0(uat_->record_size); + for (int col = 0; col < columnCount(); col++) { + uat_field_t *field = &uat_->fields[col]; + + QString data; + if (rowData.count() > col) { + if (field->mode != PT_TXTMOD_BOOL) { + data = rowData[col].toString(); + } else { + if (rowData[col].toInt() == Qt::Checked) { + data = QString("TRUE"); + } else { + data = QString("FALSE"); + } + } + } + + QByteArray bytes = field->mode == PT_TXTMOD_HEXBYTES ? QByteArray::fromHex(data.toUtf8()) : data.toUtf8(); + field->cb.set(record, bytes.constData(), (unsigned) bytes.size(), field->cbdata.set, field->fld_data); + } + uat_insert_record_idx(uat_, row, record); + if (uat_->free_cb) { + uat_->free_cb(record); + } + g_free(record); + + record_errors.insert(row, QMap<int, QString>()); + // a new row is created. For the moment all fields are empty, so validation + // will likely mark everything as invalid. Ideally validation should be + // postponed until the row (in the view) is not selected anymore + checkRow(row); + dirty_records.insert(row, true); + uat_->changed = TRUE; + + emit endInsertRows(); + + newIndex = index(row, 0, QModelIndex()); + + return newIndex; +} + +bool UatModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (!index.isValid()) + return false; + + uat_field_t *field = &uat_->fields[index.column()]; + + if ((role != Qt::EditRole) && + ((field->mode == PT_TXTMOD_BOOL) && (role != Qt::CheckStateRole))) + return false; + + if (data(index, role) == value) { + // Data appears unchanged, do not do additional checks. + return true; + } + + const int row = index.row(); + void *rec = UAT_INDEX_PTR(uat_, row); + + //qDebug() << "Changing (" << row << "," << index.column() << ") from " << data(index, Qt::EditRole) << " to " << value; + if (field->mode != PT_TXTMOD_BOOL) { + const QByteArray &str = value.toString().toUtf8(); + const QByteArray &bytes = field->mode == PT_TXTMOD_HEXBYTES ? QByteArray::fromHex(str) : str; + field->cb.set(rec, bytes.constData(), (unsigned) bytes.size(), field->cbdata.set, field->fld_data); + } else { + if (value.toInt() == Qt::Checked) { + field->cb.set(rec, "TRUE", 4, field->cbdata.set, field->fld_data); + } else { + field->cb.set(rec, "FALSE", 5, field->cbdata.set, field->fld_data); + } + } + + QVector<int> roles; + roles << role; + + // Check validity of all rows, obtaining a list of columns where the + // validity status has changed. + const QList<int> &updated_cols = checkRow(row); + if (!updated_cols.isEmpty()) { + roles << Qt::BackgroundRole; + //qDebug() << "validation status changed:" << updated_cols; + } + + if (record_errors[row].isEmpty()) { + // If all individual fields are valid, invoke the update callback. This + // might detect additional issues in either individual fields, or the + // combination of them. + if (uat_->update_cb) { + char *err = NULL; + if (!uat_->update_cb(rec, &err)) { + // TODO the error is not exactly on the first column, but we need a way to show the error. + record_errors[row].insert(0, err); + g_free(err); + } + } + } + uat_update_record(uat_, rec, record_errors[row].isEmpty()); + dirty_records[row] = true; + uat_->changed = TRUE; + + if (updated_cols.size() > updated_cols.count(index.column())) { + // The validation status for other columns were also affected by + // changing this field, mark those as dirty! + emit dataChanged(this->index(row, updated_cols.first()), + this->index(row, updated_cols.last()), roles); + } else { + + emit dataChanged(index, index, roles); + } + return true; +} + +bool UatModel::insertRows(int row, int count, const QModelIndex &/*parent*/) +{ + // support insertion of just one item for now. + if (count != 1 || row < 0 || row > rowCount()) + return false; + + beginInsertRows(QModelIndex(), row, row); + + // Initialize with empty values, caller should use setData to populate it. + void *record = g_malloc0(uat_->record_size); + for (int col = 0; col < columnCount(); col++) { + uat_field_t *field = &uat_->fields[col]; + field->cb.set(record, "", 0, field->cbdata.set, field->fld_data); + } + uat_insert_record_idx(uat_, row, record); + if (uat_->free_cb) { + uat_->free_cb(record); + } + g_free(record); + + record_errors.insert(row, QMap<int, QString>()); + // a new row is created. For the moment all fields are empty, so validation + // will likely mark everything as invalid. Ideally validation should be + // postponed until the row (in the view) is not selected anymore + checkRow(row); + dirty_records.insert(row, true); + uat_->changed = TRUE; + endInsertRows(); + return true; +} + +bool UatModel::removeRows(int row, int count, const QModelIndex &/*parent*/) +{ + if (count != 1 || row < 0 || row >= rowCount()) + return false; + + beginRemoveRows(QModelIndex(), row, row); + uat_remove_record_idx(uat_, row); + record_errors.removeAt(row); + dirty_records.removeAt(row); + uat_->changed = TRUE; + endRemoveRows(); + return true; +} + +void UatModel::clearAll() +{ + if (rowCount() < 1) + return; + + beginResetModel(); + uat_clear(uat_); + record_errors.clear(); + dirty_records.clear(); + uat_->changed = TRUE; + endResetModel(); +} + +QModelIndex UatModel::copyRow(QModelIndex original) +{ + if (! original.isValid()) + return QModelIndex(); + + int newRow = rowCount(); + + beginInsertRows(QModelIndex(), newRow, newRow); + + // Initialize with empty values, caller should use setData to populate it. + void *record = g_malloc0(uat_->record_size); + for (int col = 0; col < columnCount(); col++) { + uat_field_t *field = &uat_->fields[col]; + field->cb.set(record, "", 0, field->cbdata.set, field->fld_data); + } + uat_insert_record_idx(uat_, newRow, record); + if (uat_->free_cb) { + uat_->free_cb(record); + } + g_free(record); + + record_errors.insert(newRow, QMap<int, QString>()); + // a new row is created. For the moment all fields are empty, so validation + // will likely mark everything as invalid. Ideally validation should be + // postponed until the row (in the view) is not selected anymore + checkRow(newRow); + dirty_records.insert(newRow, true); + + // the UAT record has been created, now it is filled with the infromation + const void *src_record = UAT_INDEX_PTR(uat_, original.row()); + void *dst_record = UAT_INDEX_PTR(uat_, newRow); + // insertRows always initializes the record with empty value. Before copying + // over the new values, be sure to clear the old fields. + if (uat_->free_cb) { + uat_->free_cb(dst_record); + } + if (uat_->copy_cb) { + uat_->copy_cb(dst_record, src_record, uat_->record_size); + } else { + /* According to documentation of uat_copy_cb_t memcpy should be used if uat_->copy_cb is NULL */ + memcpy(dst_record, src_record, uat_->record_size); + } + gboolean src_valid = g_array_index(uat_->valid_data, gboolean, original.row()); + uat_update_record(uat_, dst_record, src_valid); + record_errors[newRow] = record_errors[original.row()]; + dirty_records[newRow] = true; + + uat_->changed = TRUE; + + endInsertRows(); + + return index(newRow, 0, QModelIndex()); +} + +bool UatModel::moveRow(int src_row, int dst_row) +{ + if (src_row < 0 || src_row >= rowCount() || dst_row < 0 || dst_row >= rowCount()) + return false; + + int dst = src_row < dst_row ? dst_row + 1 : dst_row; + + beginMoveRows(QModelIndex(), src_row, src_row, QModelIndex(), dst); + uat_move_index(uat_, src_row, dst_row); + record_errors.move(src_row, dst_row); + dirty_records.move(src_row, dst_row); + uat_->changed = TRUE; + endMoveRows(); + + return true; +} + +bool UatModel::hasErrors() const +{ + for (int i = 0; i < rowCount(); i++) { + // Ignore errors on unmodified records, these should not prevent the OK + // button from saving modifications to other entries. + if (dirty_records[i] && !record_errors[i].isEmpty()) { + return true; + } + } + return false; +} + +bool UatModel::checkField(int row, int col, char **error) const +{ + uat_field_t *field = &uat_->fields[col]; + void *rec = UAT_INDEX_PTR(uat_, row); + + if (!field->cb.chk) { + return true; + } + + char *str = NULL; + guint length; + field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data); + + bool ok = field->cb.chk(rec, str, length, field->cbdata.chk, field->fld_data, error); + g_free(str); + return ok; +} + +// Validates all fields in the given record, setting error messages as needed. +// Returns the columns that have changed (not the columns with errors). +QList<int> UatModel::checkRow(int row) +{ + Q_ASSERT(0 <= row && row < rowCount()); + + QList<int> changed; + QMap<int, QString> &errors = record_errors[row]; + for (int col = 0; col < columnCount(); col++) { + char *err; + bool error_changed = errors.remove(col) > 0; + if (!checkField(row, col, &err)) { + errors.insert(col, err); + g_free(err); + error_changed = !error_changed; + } + if (error_changed) { + changed << col; + } + } + return changed; +} |