1 /* uat_frame.cpp
2 *
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
6 *
7 * SPDX-License-Identifier: GPL-2.0-or-later
8 */
9
10 #include "config.h"
11
12 #include <glib.h>
13
14 #include <epan/filter_expressions.h>
15
16 #include "uat_frame.h"
17 #include <ui_uat_frame.h>
18 #include <ui/qt/widgets/display_filter_edit.h>
19 #include "wireshark_application.h"
20
21 #include <ui/qt/widgets/copy_from_profile_button.h>
22 #include <ui/qt/utils/qt_ui_utils.h>
23 #include <wsutil/report_message.h>
24
25 #include <QLineEdit>
26 #include <QKeyEvent>
27 #include <QTreeWidgetItemIterator>
28 #include <QUrl>
29
30 #include <QDebug>
31
UatFrame(QWidget * parent)32 UatFrame::UatFrame(QWidget *parent) :
33 QFrame(parent),
34 ui(new Ui::UatFrame),
35 uat_model_(NULL),
36 uat_delegate_(NULL),
37 uat_(NULL)
38 {
39 ui->setupUi(this);
40
41 ui->newToolButton->setStockIcon("list-add");
42 ui->deleteToolButton->setStockIcon("list-remove");
43 ui->copyToolButton->setStockIcon("list-copy");
44 ui->moveUpToolButton->setStockIcon("list-move-up");
45 ui->moveDownToolButton->setStockIcon("list-move-down");
46 ui->clearToolButton->setStockIcon("list-clear");
47
48 #ifdef Q_OS_MAC
49 ui->newToolButton->setAttribute(Qt::WA_MacSmallSize, true);
50 ui->deleteToolButton->setAttribute(Qt::WA_MacSmallSize, true);
51 ui->copyToolButton->setAttribute(Qt::WA_MacSmallSize, true);
52 ui->moveUpToolButton->setAttribute(Qt::WA_MacSmallSize, true);
53 ui->moveDownToolButton->setAttribute(Qt::WA_MacSmallSize, true);
54 ui->clearToolButton->setAttribute(Qt::WA_MacSmallSize, true);
55 ui->pathLabel->setAttribute(Qt::WA_MacSmallSize, true);
56 #endif
57
58 // FIXME: this prevents the columns from being resized, even if the text
59 // within a combobox needs more space (e.g. in the USER DLT settings). For
60 // very long filenames in the TLS RSA keys dialog, it also results in a
61 // vertical scrollbar. Maybe remove this since the editor is not limited to
62 // the column width (and overlays other fields if more width is needed)?
63 ui->uatTreeView->header()->setSectionResizeMode(QHeaderView::Interactive);
64
65 // start editing as soon as the field is selected or when typing starts
66 ui->uatTreeView->setEditTriggers(ui->uatTreeView->editTriggers() |
67 QAbstractItemView::CurrentChanged | QAbstractItemView::AnyKeyPressed);
68
69 // XXX - Need to add uat_move or uat_insert to the UAT API for drag/drop
70 }
71
~UatFrame()72 UatFrame::~UatFrame()
73 {
74 delete ui;
75 delete uat_delegate_;
76 delete uat_model_;
77 }
78
setUat(epan_uat * uat)79 void UatFrame::setUat(epan_uat *uat)
80 {
81 QString title(tr("Unknown User Accessible Table"));
82
83 uat_ = uat;
84
85 ui->pathLabel->clear();
86 ui->pathLabel->setEnabled(false);
87
88 if (uat_) {
89 if (uat_->name) {
90 title = uat_->name;
91 }
92
93 if (uat->from_profile) {
94 ui->copyFromProfileButton->setFilename(uat->filename);
95 connect(ui->copyFromProfileButton, &CopyFromProfileButton::copyProfile, this, &UatFrame::copyFromProfile);
96 }
97
98 QString abs_path = gchar_free_to_qstring(uat_get_actual_filename(uat_, FALSE));
99 if (abs_path.length() > 0) {
100 ui->pathLabel->setText(abs_path);
101 ui->pathLabel->setUrl(QUrl::fromLocalFile(abs_path).toString());
102 ui->pathLabel->setToolTip(tr("Open ") + uat->filename);
103 } else {
104 ui->pathLabel->setText(uat_->filename);
105 }
106 ui->pathLabel->setEnabled(true);
107
108 uat_model_ = new UatModel(NULL, uat);
109 uat_delegate_ = new UatDelegate;
110 ui->uatTreeView->setModel(uat_model_);
111 ui->uatTreeView->setItemDelegate(uat_delegate_);
112 resizeColumns();
113 ui->clearToolButton->setEnabled(uat_model_->rowCount() != 0);
114
115 connect(uat_model_, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
116 this, SLOT(modelDataChanged(QModelIndex)));
117 connect(uat_model_, SIGNAL(rowsRemoved(QModelIndex, int, int)),
118 this, SLOT(modelRowsRemoved()));
119 connect(uat_model_, SIGNAL(modelReset()), this, SLOT(modelRowsReset()));
120 }
121
122 setWindowTitle(title);
123 }
124
copyFromProfile(QString filename)125 void UatFrame::copyFromProfile(QString filename)
126 {
127 gchar *err = NULL;
128 if (uat_load(uat_, filename.toUtf8().constData(), &err)) {
129 uat_->changed = TRUE;
130 uat_model_->reloadUat();
131 } else {
132 report_failure("Error while loading %s: %s", uat_->name, err);
133 g_free(err);
134 }
135 }
136
showEvent(QShowEvent *)137 void UatFrame::showEvent(QShowEvent *)
138 {
139 #ifndef Q_OS_MAC
140 ui->copyFromProfileButton->setFixedHeight(ui->copyToolButton->geometry().height());
141 #endif
142 }
143
applyChanges()144 void UatFrame::applyChanges()
145 {
146 if (!uat_) return;
147
148 if (uat_->flags & UAT_AFFECTS_FIELDS) {
149 /* Recreate list with new fields and redissect packets */
150 wsApp->queueAppSignal(WiresharkApplication::FieldsChanged);
151 }
152 if (uat_->flags & UAT_AFFECTS_DISSECTION) {
153 /* Just redissect packets if we have any */
154 wsApp->queueAppSignal(WiresharkApplication::PacketDissectionChanged);
155 }
156 }
157
acceptChanges()158 void UatFrame::acceptChanges()
159 {
160 if (!uat_model_) return;
161
162 QString error;
163 if (uat_model_->applyChanges(error)) {
164 if (!error.isEmpty()) {
165 report_failure("%s", qPrintable(error));
166 }
167 applyChanges();
168 }
169 }
170
rejectChanges()171 void UatFrame::rejectChanges()
172 {
173 if (!uat_model_) return;
174
175 QString error;
176 if (uat_model_->revertChanges(error)) {
177 if (!error.isEmpty()) {
178 report_failure("%s", qPrintable(error));
179 }
180 }
181 }
182
addRecord(bool copy_from_current)183 void UatFrame::addRecord(bool copy_from_current)
184 {
185 if (!uat_) return;
186
187 QModelIndex current = ui->uatTreeView->currentIndex();
188 if (copy_from_current && !current.isValid()) return;
189
190 QModelIndex new_index;
191 if (copy_from_current) {
192 new_index = uat_model_->copyRow(current);
193 } else {
194 // should not fail, but you never know.
195 if (!uat_model_->insertRows(uat_model_->rowCount(), 1)) {
196 qDebug() << "Failed to add a new record";
197 return;
198 }
199 new_index = uat_model_->index(uat_model_->rowCount() - 1, 0);
200 }
201
202 // due to an EditTrigger, this will also start editing.
203 ui->uatTreeView->setCurrentIndex(new_index);
204 // trigger updating error messages and the OK button state.
205 modelDataChanged(new_index);
206 }
207
208 // Invoked when a different field is selected. Note: when selecting a different
209 // field after editing, this event is triggered after modelDataChanged.
on_uatTreeView_currentItemChanged(const QModelIndex & current,const QModelIndex & previous)210 void UatFrame::on_uatTreeView_currentItemChanged(const QModelIndex ¤t, const QModelIndex &previous)
211 {
212 if (current.isValid()) {
213 ui->deleteToolButton->setEnabled(true);
214 ui->clearToolButton->setEnabled(true);
215 ui->copyToolButton->setEnabled(true);
216 ui->moveUpToolButton->setEnabled(current.row() != 0);
217 ui->moveDownToolButton->setEnabled(current.row() != (uat_model_->rowCount() - 1));
218 } else {
219 ui->deleteToolButton->setEnabled(false);
220 ui->clearToolButton->setEnabled(false);
221 ui->copyToolButton->setEnabled(false);
222 ui->moveUpToolButton->setEnabled(false);
223 ui->moveDownToolButton->setEnabled(false);
224 }
225
226 checkForErrorHint(current, previous);
227 }
228
229 // Invoked when a field in the model changes (e.g. by closing the editor)
modelDataChanged(const QModelIndex & topLeft)230 void UatFrame::modelDataChanged(const QModelIndex &topLeft)
231 {
232 checkForErrorHint(topLeft, QModelIndex());
233 resizeColumns();
234 }
235
236 // Invoked after a row has been removed from the model.
modelRowsRemoved()237 void UatFrame::modelRowsRemoved()
238 {
239 const QModelIndex ¤t = ui->uatTreeView->currentIndex();
240
241 // Because currentItemChanged() is called before the row is removed from the model
242 // we also need to check for button enabling here.
243 if (current.isValid()) {
244 ui->moveUpToolButton->setEnabled(current.row() != 0);
245 ui->moveDownToolButton->setEnabled(current.row() != (uat_model_->rowCount() - 1));
246 } else {
247 ui->moveUpToolButton->setEnabled(false);
248 ui->moveDownToolButton->setEnabled(false);
249 }
250
251 checkForErrorHint(current, QModelIndex());
252 }
253
modelRowsReset()254 void UatFrame::modelRowsReset()
255 {
256 ui->deleteToolButton->setEnabled(false);
257 ui->clearToolButton->setEnabled(uat_model_->rowCount() != 0);
258 ui->copyToolButton->setEnabled(false);
259 ui->moveUpToolButton->setEnabled(false);
260 ui->moveDownToolButton->setEnabled(false);
261 }
262
263 // If the current field has errors, show them.
264 // Otherwise if the row has not changed, but the previous field has errors, show them.
265 // Otherwise pick the first error in the current row.
266 // Otherwise show the error from the previous field (if any).
267 // Otherwise clear the error hint.
checkForErrorHint(const QModelIndex & current,const QModelIndex & previous)268 void UatFrame::checkForErrorHint(const QModelIndex ¤t, const QModelIndex &previous)
269 {
270 if (current.isValid()) {
271 if (trySetErrorHintFromField(current)) {
272 return;
273 }
274
275 const int row = current.row();
276 if (row == previous.row() && trySetErrorHintFromField(previous)) {
277 return;
278 }
279
280 for (int i = 0; i < uat_model_->columnCount(); i++) {
281 if (trySetErrorHintFromField(uat_model_->index(row, i))) {
282 return;
283 }
284 }
285 }
286
287 if (previous.isValid()) {
288 if (trySetErrorHintFromField(previous)) {
289 return;
290 }
291 }
292
293 ui->hintLabel->clear();
294 }
295
trySetErrorHintFromField(const QModelIndex & index)296 bool UatFrame::trySetErrorHintFromField(const QModelIndex &index)
297 {
298 const QVariant &data = uat_model_->data(index, Qt::UserRole + 1);
299 if (!data.isNull()) {
300 // use HTML instead of PlainText because that handles wordwrap properly
301 ui->hintLabel->setText("<small><i>" + html_escape(data.toString()) + "</i></small>");
302 return true;
303 }
304 return false;
305 }
306
on_newToolButton_clicked()307 void UatFrame::on_newToolButton_clicked()
308 {
309 addRecord();
310 }
311
on_deleteToolButton_clicked()312 void UatFrame::on_deleteToolButton_clicked()
313 {
314 const QModelIndex ¤t = ui->uatTreeView->currentIndex();
315 if (uat_model_ && current.isValid()) {
316 if (!uat_model_->removeRows(current.row(), 1)) {
317 qDebug() << "Failed to remove row";
318 }
319 }
320 }
321
on_copyToolButton_clicked()322 void UatFrame::on_copyToolButton_clicked()
323 {
324 addRecord(true);
325 }
326
on_moveUpToolButton_clicked()327 void UatFrame::on_moveUpToolButton_clicked()
328 {
329 const QModelIndex ¤t = ui->uatTreeView->currentIndex();
330 int current_row = current.row();
331 if (uat_model_ && current.isValid() && current_row > 0) {
332 if (!uat_model_->moveRow(current_row, current_row - 1)) {
333 qDebug() << "Failed to move row up";
334 return;
335 }
336 current_row--;
337 ui->moveUpToolButton->setEnabled(current_row > 0);
338 ui->moveDownToolButton->setEnabled(current_row < (uat_model_->rowCount() - 1));
339 }
340 }
341
on_moveDownToolButton_clicked()342 void UatFrame::on_moveDownToolButton_clicked()
343 {
344 const QModelIndex ¤t = ui->uatTreeView->currentIndex();
345 int current_row = current.row();
346 if (uat_model_ && current.isValid() && current_row < (uat_model_->rowCount() - 1)) {
347 if (!uat_model_->moveRow(current_row, current_row + 1)) {
348 qDebug() << "Failed to move row down";
349 return;
350 }
351 current_row++;
352 ui->moveUpToolButton->setEnabled(current_row > 0);
353 ui->moveDownToolButton->setEnabled(current_row < (uat_model_->rowCount() - 1));
354 }
355 }
356
on_clearToolButton_clicked()357 void UatFrame::on_clearToolButton_clicked()
358 {
359 if (uat_model_) {
360 uat_model_->clearAll();
361 }
362 }
363
resizeColumns()364 void UatFrame::resizeColumns()
365 {
366 ui->uatTreeView->setVisible(false);
367 for (int i = 0; i < uat_model_->columnCount(); i++) {
368 ui->uatTreeView->resizeColumnToContents(i);
369 if (i == 0) {
370 ui->uatTreeView->setColumnWidth(i, ui->uatTreeView->columnWidth(i)+ui->uatTreeView->indentation());
371 }
372 }
373 ui->uatTreeView->setVisible(true);
374 }
375