summaryrefslogtreecommitdiffstats
path: root/ui/qt/models/uat_model.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ui/qt/models/uat_model.cpp')
-rw-r--r--ui/qt/models/uat_model.cpp545
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;
+}