summaryrefslogtreecommitdiff
path: root/ui/qt
diff options
context:
space:
mode:
authorPeter Wu <peter@lekensteyn.nl>2016-10-04 01:47:26 +0200
committerAnders Broman <a.broman58@gmail.com>2016-10-12 03:53:27 +0000
commit1cd22559a8a907263f1d032e8066482b22f8fbe3 (patch)
tree44c7aae97f98276ce86eca0951bf847e47846f3c /ui/qt
parent9434f25275b1ff838a9976903f180de7da4b00e4 (diff)
downloadwireshark-1cd22559a8a907263f1d032e8066482b22f8fbe3.tar.gz
Qt: convert UatDialog to model/view pattern, improve UX
Fixes: - Fix crash (heap-use-after-free) on removing a record while editing. - Mark a record as invalid if any of the fields fail the validation. (Fixes crash in at least the ISAKMP dissector.) - Prevent saving of invalid UAT entries (e.g. empty UATs). - Do not close the dialog on pressing Enter/Escape while editing, close the editor instead. - Fix HTML injection in the error messages. Improvements: - Tab-navigation now works between fields. - The field editor is now closed once the focus is lost. - Fields that fail validation are marked (currently with a pink color). - The error hint selection has become smarter (see comments in UatDialog::checkForErrorHint). - Properly recognizes PT_TXTMOD_HEXBYTES formats like "aa:bb" (previously it would not expect the ":" and report a bad length). A validator prevents invalid strings from being entered. - The OK button is disabled when new/edited records are bad. Notably, existing (possibly invalid) records are skipped. (Bug 7471). Live validation (while typing in the editor) was dropped during conversion, but it can be added later if desired. Drag and drop reordering still needs to be implemented. Bug: 11714 Bug: 7471 Change-Id: Ic0b6a177f90503fbd65b5001d8a87a10e38f4d64 Reviewed-on: https://code.wireshark.org/review/17994 Petri-Dish: Peter Wu <peter@lekensteyn.nl> Tested-by: Petri Dish Buildbot <buildbot-no-reply@wireshark.org> Reviewed-by: Anders Broman <a.broman58@gmail.com>
Diffstat (limited to 'ui/qt')
-rw-r--r--ui/qt/CMakeLists.txt6
-rw-r--r--ui/qt/Makefile.am6
-rw-r--r--ui/qt/uat_delegate.cpp195
-rw-r--r--ui/qt/uat_delegate.h57
-rw-r--r--ui/qt/uat_dialog.cpp495
-rw-r--r--ui/qt/uat_dialog.h31
-rw-r--r--ui/qt/uat_dialog.ui13
-rw-r--r--ui/qt/uat_model.cpp314
-rw-r--r--ui/qt/uat_model.h66
-rw-r--r--ui/qt/uat_tree_view.cpp74
-rw-r--r--ui/qt/uat_tree_view.h44
11 files changed, 864 insertions, 437 deletions
diff --git a/ui/qt/CMakeLists.txt b/ui/qt/CMakeLists.txt
index fbd1610cb6..3472d4752d 100644
--- a/ui/qt/CMakeLists.txt
+++ b/ui/qt/CMakeLists.txt
@@ -150,7 +150,10 @@ set(WIRESHARK_QT_HEADERS
time_shift_dialog.h
timeline_delegate.h
traffic_table_dialog.h
+ uat_delegate.h
uat_dialog.h
+ uat_model.h
+ uat_tree_view.h
voip_calls_dialog.h
wireless_frame.h
wireshark_application.h
@@ -308,7 +311,10 @@ set(WIRESHARK_QT_SRC
time_shift_dialog.cpp
timeline_delegate.cpp
traffic_table_dialog.cpp
+ uat_delegate.cpp
uat_dialog.cpp
+ uat_model.cpp
+ uat_tree_view.cpp
voip_calls_dialog.cpp
wireless_frame.cpp
wireshark_application.cpp
diff --git a/ui/qt/Makefile.am b/ui/qt/Makefile.am
index ae6a4e5170..41ceee767e 100644
--- a/ui/qt/Makefile.am
+++ b/ui/qt/Makefile.am
@@ -283,7 +283,10 @@ MOC_HDRS = \
time_shift_dialog.h \
timeline_delegate.h \
traffic_table_dialog.h \
+ uat_delegate.h \
uat_dialog.h \
+ uat_model.h \
+ uat_tree_view.h \
voip_calls_dialog.h \
wireless_frame.h \
wireshark_application.h \
@@ -554,7 +557,10 @@ WIRESHARK_QT_SRC = \
time_shift_dialog.cpp \
timeline_delegate.cpp \
traffic_table_dialog.cpp \
+ uat_delegate.cpp \
uat_dialog.cpp \
+ uat_model.cpp \
+ uat_tree_view.cpp \
voip_calls_dialog.cpp \
wireless_frame.cpp \
wireshark_application.cpp \
diff --git a/ui/qt/uat_delegate.cpp b/ui/qt/uat_delegate.cpp
new file mode 100644
index 0000000000..7c847a109e
--- /dev/null
+++ b/ui/qt/uat_delegate.cpp
@@ -0,0 +1,195 @@
+/* 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 "uat_delegate.h"
+#include "epan/value_string.h"
+#include <QComboBox>
+#include <QEvent>
+#include <QFileDialog>
+#include <QLineEdit>
+
+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_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_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();
+ combobox->setCurrentText(data);
+ 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/uat_delegate.h b/ui/qt/uat_delegate.h
new file mode 100644
index 0000000000..795161a686
--- /dev/null
+++ b/ui/qt/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/uat_dialog.cpp b/ui/qt/uat_dialog.cpp
index cf703ea9d0..4d039bd33f 100644
--- a/ui/qt/uat_dialog.cpp
+++ b/ui/qt/uat_dialog.cpp
@@ -24,32 +24,26 @@
#include "wireshark_application.h"
#include "epan/strutil.h"
-#include "epan/to_str.h"
#include "epan/uat-int.h"
-#include "epan/value_string.h"
#include "ui/help_url.h"
#include <wsutil/report_err.h>
#include "qt_ui_utils.h"
-#include <QComboBox>
#include <QDesktopServices>
-#include <QFileDialog>
-#include <QFont>
-#include <QKeyEvent>
#include <QPushButton>
-#include <QTreeWidget>
-#include <QTreeWidgetItemIterator>
#include <QUrl>
#include <QDebug>
+// NOTE currently uat setter is always invoked in UatModel even if the uat checker fails.
+
UatDialog::UatDialog(QWidget *parent, epan_uat *uat) :
GeometryStateDialog(parent),
ui(new Ui::UatDialog),
- uat_(NULL),
- cur_line_edit_(NULL),
- cur_combo_box_(NULL)
+ uat_model_(NULL),
+ uat_delegate_(NULL),
+ uat_(NULL)
{
ui->setupUi(this);
if (uat) loadGeometry(0, 0, uat->name);
@@ -68,20 +62,27 @@ UatDialog::UatDialog(QWidget *parent, epan_uat *uat) :
setUat(uat);
+ // FIXME: this prevents the columns from being resized, even if the text
+ // within a combobox needs more space (e.g. in the USER DLT settings). For
+ // very long filenames in the SSL RSA keys dialog, it also results in a
+ // vertical scrollbar. Maybe remove this since the editor is not limited to
+ // the column width (and overlays other fields if more width is needed)?
#if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
- ui->uatTreeWidget->header()->setResizeMode(QHeaderView::ResizeToContents);
+ ui->uatTreeView->header()->setResizeMode(QHeaderView::ResizeToContents);
#else
- ui->uatTreeWidget->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+ ui->uatTreeView->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
#endif
// Need to add uat_move or uat_insert to the UAT API.
- ui->uatTreeWidget->setDragEnabled(false);
+ ui->uatTreeView->setDragEnabled(false);
qDebug() << "FIX Add drag reordering to UAT dialog";
}
UatDialog::~UatDialog()
{
delete ui;
+ delete uat_delegate_;
+ delete uat_model_;
}
void UatDialog::setUat(epan_uat *uat)
@@ -90,8 +91,6 @@ void UatDialog::setUat(epan_uat *uat)
uat_ = uat;
- ui->uatTreeWidget->clear();
- ui->uatTreeWidget->setColumnCount(0);
ui->pathLabel->clear();
ui->pathLabel->setEnabled(false);
help_button_->setEnabled(false);
@@ -107,17 +106,21 @@ void UatDialog::setUat(epan_uat *uat)
ui->pathLabel->setToolTip(tr("Open ") + uat->filename);
ui->pathLabel->setEnabled(true);
- ui->uatTreeWidget->setColumnCount(uat_->ncols);
-
- for (guint col = 0; col < uat->ncols; col++) {
- ui->uatTreeWidget->headerItem()->setText(col, uat_->fields[col].title);
- }
+ uat_model_ = new UatModel(NULL, uat);
+ uat_delegate_ = new UatDelegate;
+ ui->uatTreeView->setModel(uat_model_);
+ ui->uatTreeView->setItemDelegate(uat_delegate_);
- updateItems();
+ connect(uat_model_, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
+ this, SLOT(modelDataChanged(QModelIndex)));
+ connect(ui->uatTreeView, SIGNAL(currentItemChanged(QModelIndex,QModelIndex)),
+ this, SLOT(viewCurrentChanged(QModelIndex,QModelIndex)));
+ ok_button_->setEnabled(!uat_model_->hasErrors());
if (uat_->help && strlen(uat_->help) > 0) {
help_button_->setEnabled(true);
}
+
connect(this, SIGNAL(rejected()), this, SLOT(rejectChanges()));
connect(this, SIGNAL(accepted()), this, SLOT(acceptChanges()));
}
@@ -125,422 +128,92 @@ void UatDialog::setUat(epan_uat *uat)
setWindowTitle(title);
}
-void UatDialog::keyPressEvent(QKeyEvent *evt)
-{
- if (cur_line_edit_ && cur_line_edit_->hasFocus()) {
- switch (evt->key()) {
- case Qt::Key_Escape:
- cur_line_edit_->setText(saved_string_pref_);
- stringPrefEditingFinished();
- return;
- default:
- break;
- }
- } else if (cur_combo_box_ && cur_combo_box_->hasFocus()) {
- switch (evt->key()) {
- case Qt::Key_Escape:
- cur_combo_box_->setCurrentIndex(saved_combo_idx_);
- /* Fall Through */
- case Qt::Key_Enter:
- case Qt::Key_Return:
- // XXX The combo box eats enter and return
- enumPrefCurrentIndexChanged(cur_combo_box_->currentIndex());
- delete cur_combo_box_;
- return;
- default:
- break;
- }
- }
- QDialog::keyPressEvent(evt);
-}
-
-QString UatDialog::fieldString(guint row, guint column)
-{
- QString string_rep;
-
- if (!uat_) return string_rep;
-
- void *rec = UAT_INDEX_PTR(uat_, row);
- uat_field_t *field = &uat_->fields[column];
- guint length;
- char *str;
-
- field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data);
-
- switch(field->mode) {
- case PT_TXTMOD_NONE:
- case PT_TXTMOD_STRING:
- case PT_TXTMOD_ENUM:
- case PT_TXTMOD_FILENAME:
- case PT_TXTMOD_DIRECTORYNAME:
- string_rep = str;
- break;
- case PT_TXTMOD_HEXBYTES: {
- {
- char* temp_str = bytes_to_str(NULL, (const guint8 *) str, length);
- QString qstr(temp_str);
- string_rep = qstr;
- wmem_free(NULL, temp_str);
- }
- break;
- }
- default:
- g_assert_not_reached();
- break;
- }
-
- g_free(str);
- return string_rep;
-}
-
-void UatDialog::updateItem(QTreeWidgetItem &item)
-{
- if (!uat_) return;
- guint row = item.data(0, Qt::UserRole).toUInt();
- for (guint col = 0; col < uat_->ncols; col++) {
- item.setText(col, fieldString(row, col));
- }
-}
-
-void UatDialog::updateItems()
-{
- if (!uat_) return;
-
- // Forcibly sync ui->uaTreeWidget with uat_.
- // It would probably be more correct to create a UatModel and
- // use it in conjunction with a QTreeView.
- while (ui->uatTreeWidget->topLevelItemCount() > (int) uat_->raw_data->len) {
- delete (ui->uatTreeWidget->topLevelItem(0));
- }
- for (guint row = 0; row < uat_->raw_data->len; row++) {
- QTreeWidgetItem *item = ui->uatTreeWidget->topLevelItem(row);
- if (!item) item = new QTreeWidgetItem(ui->uatTreeWidget);
- item->setData(0, Qt::UserRole, qVariantFromValue(row));
- updateItem(*item);
- }
-}
-
-void UatDialog::activateLastItem()
+// Invoked when a field in the model changes (e.g. by closing the editor)
+void UatDialog::modelDataChanged(const QModelIndex &topLeft)
{
- int last_item = ui->uatTreeWidget->topLevelItemCount() - 1;
- if (last_item < 0) return;
-
- QModelIndex idx = ui->uatTreeWidget->model()->index(last_item, 0);
- ui->uatTreeWidget->clearSelection();
- ui->uatTreeWidget->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows);
- on_uatTreeWidget_itemActivated(ui->uatTreeWidget->topLevelItem(last_item), 0);
- ui->uatTreeWidget->setCurrentItem(ui->uatTreeWidget->topLevelItem(last_item));
+ checkForErrorHint(topLeft, QModelIndex());
+ ok_button_->setEnabled(!uat_model_->hasErrors());
}
-void UatDialog::on_uatTreeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous)
+// Invoked when a different field is selected. Note: when selecting a different
+// field after editing, this event is triggered after modelDataChanged.
+void UatDialog::viewCurrentChanged(const QModelIndex &current, const QModelIndex &previous)
{
- for (int col = 0; col < ui->uatTreeWidget->columnCount(); col++) {
- if (previous && ui->uatTreeWidget->itemWidget(previous, col)) {
- ui->uatTreeWidget->removeItemWidget(previous, col);
- updateItem(*previous);
- }
- }
- ui->uatTreeWidget->setCurrentItem(current);
-}
-
-void UatDialog::on_uatTreeWidget_itemActivated(QTreeWidgetItem *item, int column)
-{
- if (!uat_) return;
-
- cur_line_edit_ = NULL;
- cur_combo_box_ = NULL;
-
- uat_field_t *field = &uat_->fields[column];
- guint row = item->data(0, Qt::UserRole).toUInt();
- void *rec = UAT_INDEX_PTR(uat_, row);
- cur_column_ = column;
- QWidget *editor = NULL;
-
- // Reset any active items
- QTreeWidgetItemIterator uat_it(ui->uatTreeWidget);
- while (*uat_it) {
- for (int col = 0; col < ui->uatTreeWidget->columnCount(); col++) {
- if (ui->uatTreeWidget->itemWidget((*uat_it), col)) {
- ui->uatTreeWidget->removeItemWidget((*uat_it), col);
- updateItem(*(*uat_it));
- }
- }
- ++uat_it;
- }
-
- switch(field->mode) {
- case PT_TXTMOD_DIRECTORYNAME:
- {
- QString cur_path = fieldString(row, column);
- const QByteArray& new_path = QFileDialog::getExistingDirectory(this,
- field->title, cur_path).toUtf8();
- field->cb.set(rec, new_path.constData(), (unsigned) new_path.size(), field->cbdata.set, field->fld_data);
- updateItem(*item);
- uat_->changed = TRUE;
- break;
- }
-
- case PT_TXTMOD_FILENAME:
- {
- QString cur_path = fieldString(row, column);
- const QByteArray& new_path = QFileDialog::getOpenFileName(this,
- field->title, cur_path, QString(), NULL, QFileDialog::DontConfirmOverwrite).toUtf8();
- field->cb.set(rec, new_path.constData(), (unsigned) new_path.size(), field->cbdata.set, field->fld_data);
- updateItem(*item);
- uat_->changed = TRUE;
- break;
- }
-
- case PT_TXTMOD_STRING:
- case PT_TXTMOD_HEXBYTES: {
- cur_line_edit_ = new SyntaxLineEdit();
- break;
- }
-
- case PT_TXTMOD_ENUM: {
- cur_combo_box_ = new QComboBox();
-// const enum_val_t *ev;
- const value_string *enum_vals = (const value_string *)field->fld_data;
- for (int i = 0; enum_vals[i].strptr != NULL; i++) {
- cur_combo_box_->addItem(enum_vals[i].strptr, QVariant(enum_vals[i].value));
- if (item->text(column).compare(enum_vals[i].strptr) == 0) {
- cur_combo_box_->setCurrentIndex(cur_combo_box_->count() - 1);
- }
- }
- saved_combo_idx_ = cur_combo_box_->currentIndex();
- break;
- }
- case PT_TXTMOD_NONE: break;
- default:
- g_assert_not_reached();
- break;
- }
-
- if (cur_line_edit_) {
- editor = cur_line_edit_;
- cur_line_edit_->setText(fieldString(row, column));
- cur_line_edit_->selectAll();
- connect(cur_line_edit_, SIGNAL(destroyed()), this, SLOT(lineEditPrefDestroyed()));
- connect(cur_line_edit_, SIGNAL(textChanged(QString)), this, SLOT(stringPrefTextChanged(QString)));
- connect(cur_line_edit_, SIGNAL(editingFinished()), this, SLOT(stringPrefEditingFinished()));
- }
- if (cur_combo_box_) {
- editor = cur_combo_box_;
- connect(cur_combo_box_, SIGNAL(currentIndexChanged(int)), this, SLOT(enumPrefCurrentIndexChanged(int)));
- connect(cur_combo_box_, SIGNAL(destroyed()), this, SLOT(enumPrefDestroyed()));
- }
- if (editor) {
- QFrame *edit_frame = new QFrame();
- QHBoxLayout *hb = new QHBoxLayout();
- QSpacerItem *spacer = new QSpacerItem(5, 10);
-
- hb->addWidget(editor, 0);
- hb->addSpacerItem(spacer);
- hb->setStretch(1, 1);
- hb->setContentsMargins(0, 0, 0, 0);
-
- edit_frame->setLineWidth(0);
- edit_frame->setFrameStyle(QFrame::NoFrame);
- // The documentation suggests setting autoFillbackground. That looks silly
- // so we clear the item text instead.
- saved_string_pref_ = item->text(column);
- item->setText(column, "");
- edit_frame->setLayout(hb);
- ui->uatTreeWidget->setItemWidget(item, column, edit_frame);
- if (cur_line_edit_) {
- ui->uatTreeWidget->setCurrentItem(item);
- }
- editor->setFocus();
- }
-}
-
-void UatDialog::on_uatTreeWidget_itemSelectionChanged()
-{
- if (ui->uatTreeWidget->selectedItems().length() > 0) {
+ if (current.isValid()) {
ui->deleteToolButton->setEnabled(true);
ui->copyToolButton->setEnabled(true);
} else {
ui->deleteToolButton->setEnabled(false);
ui->copyToolButton->setEnabled(false);
}
-}
-void UatDialog::lineEditPrefDestroyed()
-{
- cur_line_edit_ = NULL;
+ checkForErrorHint(current, previous);
}
-void UatDialog::enumPrefDestroyed()
+// If the current field has errors, show them.
+// Otherwise if the row has not changed, but the previous field has errors, show them.
+// Otherwise pick the first error in the current row.
+// Otherwise show the error from the previous field (if any).
+// Otherwise clear the error hint.
+void UatDialog::checkForErrorHint(const QModelIndex &current, const QModelIndex &previous)
{
- cur_combo_box_ = NULL;
-}
-
-void UatDialog::enumPrefCurrentIndexChanged(int index)
-{
- QTreeWidgetItem *item = ui->uatTreeWidget->currentItem();
- if (!cur_combo_box_ || !item || index < 0) return;
- guint row = item->data(0, Qt::UserRole).toUInt();
- void *rec = UAT_INDEX_PTR(uat_, row);
- uat_field_t *field = &uat_->fields[cur_column_];
- const QByteArray& enum_txt = cur_combo_box_->itemText(index).toUtf8();
- char *err = NULL;
-
- if (field->cb.chk && !field->cb.chk(rec, enum_txt.constData(), (unsigned) enum_txt.size(), field->cbdata.chk, field->fld_data, &err)) {
- QString err_string = "<font color='red'>%1</font>";
- ui->hintLabel->setText(err_string.arg(err));
- g_free(err);
- ok_button_->setEnabled(false);
- uat_update_record(uat_, rec, FALSE);
- } else if (uat_->update_cb) {
- field->cb.set(rec, enum_txt.constData(), (unsigned) enum_txt.size(), field->cbdata.set, field->fld_data);
-
- if (!uat_->update_cb(rec, &err)) {
- QString err_string = "<font color='red'>%1</font>";
- ui->hintLabel->setText(err_string.arg(err));
- g_free(err);
- ok_button_->setEnabled(false);
- } else {
- ui->hintLabel->clear();
- ok_button_->setEnabled(true);
+ if (current.isValid()) {
+ if (trySetErrorHintFromField(current)) {
+ return;
}
- } else {
- ui->hintLabel->clear();
- field->cb.set(rec, enum_txt.constData(), (unsigned) enum_txt.size(), field->cbdata.set, field->fld_data);
- ok_button_->setEnabled(true);
- uat_update_record(uat_, rec, TRUE);
- }
- this->update();
- uat_->changed = TRUE;
-}
-
-const QByteArray UatDialog::unhexbytes(const QString input, QString &qt_err) {
- if (input.size() % 2) {
- qt_err = tr("Uneven number of chars hex string (%1)").arg(input.size());
- return NULL;
- }
-
- QByteArray output = QByteArray::fromHex(input.toUtf8());
+ const int row = current.row();
+ if (row == previous.row() && trySetErrorHintFromField(previous)) {
+ return;
+ }
- if (output.size() != (input.size()/2)) {
- qt_err = tr("Error parsing hex string");
+ for (int i = 0; i < uat_model_->columnCount(); i++) {
+ if (trySetErrorHintFromField(uat_model_->index(row, i))) {
+ return;
+ }
+ }
}
- return output;
-}
-
-void UatDialog::stringPrefTextChanged(const QString &text)
-{
- QTreeWidgetItem *item = ui->uatTreeWidget->currentItem();
- if (!cur_line_edit_) {
- cur_line_edit_ = new SyntaxLineEdit();
- }
- if (!cur_line_edit_ || !item) return;
- guint row = item->data(0, Qt::UserRole).toUInt();
- void *rec = UAT_INDEX_PTR(uat_, row);
- uat_field_t *field = &uat_->fields[cur_column_];
- SyntaxLineEdit::SyntaxState ss = SyntaxLineEdit::Empty;
- bool enable_ok = true;
- QString qt_err;
- const QByteArray &txt = (field->mode == PT_TXTMOD_HEXBYTES) ? unhexbytes(text, qt_err) : text.toUtf8();
- QString err_string = "<font color='red'>%1</font>";
- if (!qt_err.isEmpty()) {
- ui->hintLabel->setText(err_string.arg(qt_err));
- enable_ok = false;
- ss = SyntaxLineEdit::Invalid;
- } else {
- char *err = NULL;
- if (field->cb.chk && !field->cb.chk(rec, txt.constData(), (unsigned) txt.size(), field->cbdata.chk, field->fld_data, &err)) {
- ui->hintLabel->setText(err_string.arg(err));
- g_free(err);
- enable_ok = false;
- ss = SyntaxLineEdit::Invalid;
- uat_update_record(uat_, rec, FALSE);
- } else {
- ui->hintLabel->clear();
- field->cb.set(rec, txt.constData(), (unsigned) txt.size(), field->cbdata.set, field->fld_data);
- saved_string_pref_ = text;
- ss = SyntaxLineEdit::Valid;
- uat_update_record(uat_, rec, TRUE);
+ if (previous.isValid()) {
+ if (trySetErrorHintFromField(previous)) {
+ return;
}
}
- this->update();
- ok_button_->setEnabled(enable_ok);
- cur_line_edit_->setSyntaxState(ss);
- uat_->changed = TRUE;
+ ui->hintLabel->clear();
}
-void UatDialog::stringPrefEditingFinished()
+bool UatDialog::trySetErrorHintFromField(const QModelIndex &index)
{
- QTreeWidgetItem *item = ui->uatTreeWidget->currentItem();
- if (!cur_line_edit_ || !item) return;
-
- item->setText(cur_column_, saved_string_pref_);
- ok_button_->setEnabled(true);
-
- if (uat_ && uat_->update_cb) {
- gchar *err;
- void *rec = UAT_INDEX_PTR(uat_, item->data(0, Qt::UserRole).toUInt());
- if (!uat_->update_cb(rec, &err)) {
- QString err_string = "<font color='red'>%1</font>";
- ui->hintLabel->setText(err_string.arg(err));
- g_free(err);
- ok_button_->setEnabled(false);
- } else {
- ui->hintLabel->clear();
- }
- this->update();
+ const QVariant &data = uat_model_->data(index, Qt::UserRole + 1);
+ if (!data.isNull()) {
+ // use HTML instead of PlainText because that handles wordwrap properly
+ ui->hintLabel->setText(html_escape(data.toString()));
+ return true;
}
-
- cur_line_edit_ = NULL;
- updateItem(*item);
+ return false;
}
void UatDialog::addRecord(bool copy_from_current)
{
if (!uat_) return;
- void *rec = g_malloc0(uat_->record_size);
+ const QModelIndex &current = ui->uatTreeView->currentIndex();
+ if (copy_from_current && !current.isValid()) return;
- if (copy_from_current) {
- QTreeWidgetItem *item = ui->uatTreeWidget->currentItem();
- if (!item) return;
- guint row = item->data(0, Qt::UserRole).toUInt();
- if (uat_->copy_cb) {
- uat_->copy_cb(rec, UAT_INDEX_PTR(uat_, row), uat_->record_size);
- } else {
- /* According to documentation of uat_copy_cb_t memcpy should be used if uat_->copy_cb is NULL */
- memcpy(rec, UAT_INDEX_PTR(uat_, row), uat_->record_size);
- }
- } else {
- for (guint col = 0; col < uat_->ncols; col++) {
- uat_field_t *field = &uat_->fields[col];
- switch (field->mode) {
- case PT_TXTMOD_ENUM:
- guint length;
- char *str;
- field->cb.tostr(rec, &str, &length, field->cbdata.tostr, field->fld_data);
- field->cb.set(rec, str, length, field->cbdata.set, field->fld_data);
- g_free(str);
- break;
- case PT_TXTMOD_NONE:
- break;
- default:
- field->cb.set(rec, "", 0, field->cbdata.set, field->fld_data);
- break;
- }
- }
+ // should not fail, but you never know.
+ if (!uat_model_->insertRows(uat_model_->rowCount(), 1)) {
+ qDebug() << "Failed to add a new record";
+ return;
}
-
- uat_add_record(uat_, rec, TRUE);
- if (uat_->free_cb) {
- uat_->free_cb(rec);
+ const QModelIndex &new_index = uat_model_->index(uat_model_->rowCount() - 1, 0);
+ if (copy_from_current) {
+ uat_model_->copyRow(new_index.row(), current.row());
}
- g_free(rec);
- uat_->changed = TRUE;
- updateItems();
- activateLastItem();
+ // due to an EditTrigger, this will also start editing.
+ ui->uatTreeView->setCurrentIndex(new_index);
+ // trigger updating error messages and the OK button state.
+ modelDataChanged(new_index);
}
void UatDialog::on_newToolButton_clicked()
@@ -550,16 +223,12 @@ void UatDialog::on_newToolButton_clicked()
void UatDialog::on_deleteToolButton_clicked()
{
- QTreeWidgetItem *item = ui->uatTreeWidget->currentItem();
- if (!uat_ || !item) return;
-
- guint row = item->data(0, Qt::UserRole).toUInt();
-
- uat_remove_record_idx(uat_, row);
- updateItems();
-
- on_uatTreeWidget_itemSelectionChanged();
- uat_->changed = TRUE;
+ const QModelIndex &current = ui->uatTreeView->currentIndex();
+ if (uat_model_ && current.isValid()) {
+ if (!uat_model_->removeRows(current.row(), 1)) {
+ qDebug() << "Failed to remove row";
+ }
+ }
}
void UatDialog::on_copyToolButton_clicked()
diff --git a/ui/qt/uat_dialog.h b/ui/qt/uat_dialog.h
index c86ab97818..9bfa49a8b0 100644
--- a/ui/qt/uat_dialog.h
+++ b/ui/qt/uat_dialog.h
@@ -26,12 +26,12 @@
#include <glib.h>
-#include "syntax_line_edit.h"
#include "geometry_state_dialog.h"
+#include "uat_model.h"
+#include "uat_delegate.h"
class QComboBox;
class QPushButton;
-class QTreeWidgetItem;
struct epan_uat;
@@ -49,18 +49,9 @@ public:
void setUat(struct epan_uat *uat = NULL);
-protected:
- void keyPressEvent(QKeyEvent *evt);
-
private slots:
- void on_uatTreeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
- void on_uatTreeWidget_itemActivated(QTreeWidgetItem *item, int column);
- void on_uatTreeWidget_itemSelectionChanged();
- void lineEditPrefDestroyed();
- void enumPrefDestroyed();
- void enumPrefCurrentIndexChanged(int index);
- void stringPrefTextChanged(const QString & text);
- void stringPrefEditingFinished();
+ void modelDataChanged(const QModelIndex &topLeft);
+ void viewCurrentChanged(const QModelIndex &current, const QModelIndex &previous);
void acceptChanges();
void rejectChanges();
void on_newToolButton_clicked();
@@ -70,22 +61,16 @@ private slots:
private:
Ui::UatDialog *ui;
+ UatModel *uat_model_;
+ UatDelegate *uat_delegate_;
QPushButton *ok_button_;
QPushButton *help_button_;
struct epan_uat *uat_;
- int cur_column_;
- SyntaxLineEdit *cur_line_edit_;
- QString saved_string_pref_;
- QComboBox *cur_combo_box_;
- int saved_combo_idx_;
- QString fieldString(guint row, guint column);
- void updateItem(QTreeWidgetItem &item);
- void updateItems();
- void activateLastItem();
+ void checkForErrorHint(const QModelIndex &current, const QModelIndex &previous);
+ bool trySetErrorHintFromField(const QModelIndex &index);
void applyChanges();
void addRecord(bool copy_from_current = false);
- const QByteArray unhexbytes(const QString input, QString &err);
};
#endif // UAT_DIALOG_H
diff --git a/ui/qt/uat_dialog.ui b/ui/qt/uat_dialog.ui
index 7804d0462e..507e8e90d1 100644
--- a/ui/qt/uat_dialog.ui
+++ b/ui/qt/uat_dialog.ui
@@ -12,7 +12,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
- <widget class="QTreeWidget" name="uatTreeWidget">
+ <widget class="UatTreeView" name="uatTreeView">
<column>
<property name="text">
<string notr="true">1</string>
@@ -97,9 +97,15 @@
</item>
<item>
<widget class="QLabel" name="hintLabel">
+ <property name="styleSheet">
+ <string notr="true">QLabel { color: red; }</string>
+ </property>
<property name="text">
<string/>
</property>
+ <property name="textFormat">
+ <set>Qt::RichText</set>
+ </property>
<property name="wordWrap">
<bool>true</bool>
</property>
@@ -123,6 +129,11 @@
<extends>QLabel</extends>
<header>elided_label.h</header>
</customwidget>
+ <customwidget>
+ <class>UatTreeView</class>
+ <extends>QTreeView</extends>
+ <header>uat_tree_view.h</header>
+ </customwidget>
</customwidgets>
<resources>
<include location="../../image/toolbar.qrc"/>
diff --git a/ui/qt/uat_model.cpp b/ui/qt/uat_model.cpp
new file mode 100644
index 0000000000..e8c6ec1dcb
--- /dev/null
+++ b/ui/qt/uat_model.cpp
@@ -0,0 +1,314 @@
+/* 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_(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;
+
+ // Note: Qt::ItemNeverHasChildren is not just for optimization, it avoids crashes too with QTreeView...
+ Qt::ItemFlags flags = QAbstractTableModel::flags(index);
+ 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);
+ QString qstr(temp_str);
+ wmem_free(NULL, temp_str);
+ return qstr;
+ } else {
+ return QString::fromUtf8(str);
+ }
+ }
+
+ 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();
+}
+
+QVariant UatModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+ if (role != Qt::DisplayRole || orientation != Qt::Horizontal)
+ return QVariant();
+
+ return uat_->fields[section].title;
+}
+
+int UatModel::rowCount(const QModelIndex &/*parent*/) const
+{
+ return uat_->raw_data->len;
+}
+
+int UatModel::columnCount(const QModelIndex &/*parent*/) const
+{
+ return uat_->ncols;
+}
+
+bool UatModel::setData(const QModelIndex &index, const QVariant &value, int role)
+{
+ if (!index.isValid() || role != Qt::EditRole)
+ return false;
+
+ if (data(index, role) == value) {
+ // Data appears unchanged, do not do additional checks.
+ return true;
+ }
+
+ const int row = index.row();
+ uat_field_t *field = &uat_->fields[index.column()];
+ void *rec = UAT_INDEX_PTR(uat_, row);
+
+ //qDebug() << "Changing (" << row << "," << index.column() << ") from " << data(index, Qt::EditRole) << " to " << value;
+ 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);
+
+ 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()), 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;
+}
+
+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);
+ 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()), roles);
+ 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/uat_model.h b/ui/qt/uat_model.h
new file mode 100644
index 0000000000..36e79ebf83
--- /dev/null
+++ b/ui/qt/uat_model.h
@@ -0,0 +1,66 @@
+/* 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 = 0, epan_uat *uat = 0);
+
+ 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;
+
+private:
+ bool checkField(int row, int col, char **error) const;
+ QList<int> checkRow(int row);
+
+ epan_uat *uat_;
+ QList<bool> dirty_records;
+ QList<QMap<int, QString> > record_errors;
+};
+#endif // UAT_MODEL_H
diff --git a/ui/qt/uat_tree_view.cpp b/ui/qt/uat_tree_view.cpp
new file mode 100644
index 0000000000..bb2e8501b7
--- /dev/null
+++ b/ui/qt/uat_tree_view.cpp
@@ -0,0 +1,74 @@
+/* uat_tree_view.cpp
+ * Tree view of UAT data.
+ *
+ * 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_tree_view.h"
+
+UatTreeView::UatTreeView(QWidget *parent) : QTreeView(parent)
+{
+ // start editing as soon as the field is selected or when typing starts
+ setEditTriggers(editTriggers() | CurrentChanged | AnyKeyPressed);
+}
+
+// Note: if a QTableView is used, then this is not needed anymore since Tab
+// works as "expected" (move to next cell instead of row).
+// Note 2: this does not help with fields with no widget (like filename).
+QModelIndex UatTreeView::moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers)
+{
+ QModelIndex current = currentIndex();
+ // If an item is currently selected, interpret Next/Previous. Otherwise,
+ // fallback to the default selection (e.g. first row for Next).
+ if (current.isValid()) {
+ if (cursorAction == MoveNext) {
+ if (current.column() < model()->columnCount()) {
+ return current.sibling(current.row(), current.column() + 1);
+ }
+ return current;
+ } else if (cursorAction == MovePrevious) {
+ if (current.column() > 0) {
+ return current.sibling(current.row(), current.column() - 1);
+ }
+ return current;
+ }
+ }
+
+ return QTreeView::moveCursor(cursorAction, modifiers);
+}
+
+void UatTreeView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
+{
+ QTreeView::currentChanged(current, previous);
+ emit currentItemChanged(current, previous);
+}
+
+/* * 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/uat_tree_view.h b/ui/qt/uat_tree_view.h
new file mode 100644
index 0000000000..d7480b2e8a
--- /dev/null
+++ b/ui/qt/uat_tree_view.h
@@ -0,0 +1,44 @@
+/* uat_tree_view.h
+ * Tree view of UAT data.
+ *
+ * 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_TREE_VIEW_H
+#define UAT_TREE_VIEW_H
+
+#include <config.h>
+#include <QTreeView>
+
+class UatTreeView : public QTreeView
+{
+ Q_OBJECT
+public:
+ UatTreeView(QWidget *parent = 0);
+ QModelIndex moveCursor(CursorAction cursorAction, Qt::KeyboardModifiers modifiers);
+
+protected slots:
+ void currentChanged(const QModelIndex &current, const QModelIndex &previous);
+
+signals:
+ void currentItemChanged(const QModelIndex &current, const QModelIndex &previous);
+};
+#endif // UAT_TREE_VIEW_H