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 &current, 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 &current = 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 &current, 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 &current = 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 &current = 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 &current = 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