1 /* module_preferences_scroll_area.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 "module_preferences_scroll_area.h"
11 #include <ui_module_preferences_scroll_area.h>
12 #include <ui/qt/widgets/syntax_line_edit.h>
13 #include "ui/qt/widgets/wireshark_file_dialog.h"
14 #include <ui/qt/utils/qt_ui_utils.h>
15 #include "uat_dialog.h"
16 #include "wireshark_application.h"
17 
18 #include <ui/qt/utils/variant_pointer.h>
19 
20 #include <epan/prefs-int.h>
21 
22 #include <wsutil/utf8_entities.h>
23 
24 #include <QAbstractButton>
25 #include <QButtonGroup>
26 #include <QCheckBox>
27 #include <QComboBox>
28 #include <QHBoxLayout>
29 #include <QLabel>
30 #include <QLineEdit>
31 #include <QPushButton>
32 #include <QRadioButton>
33 #include <QScrollBar>
34 #include <QSpacerItem>
35 
36 const char *pref_prop_ = "pref_ptr";
37 
38 // Escape our ampersands so that Qt won't try to interpret them as
39 // mnemonics.
title_to_shortcut(const char * title)40 static const QString title_to_shortcut(const char *title) {
41     QString shortcut_str(title);
42     shortcut_str.replace('&', "&&");
43     return shortcut_str;
44 }
45 
46 
47 extern "C" {
48 // Callbacks prefs routines
49 
50 /* Add a single preference to the QVBoxLayout of a preference page */
51 static guint
pref_show(pref_t * pref,gpointer layout_ptr)52 pref_show(pref_t *pref, gpointer layout_ptr)
53 {
54     QVBoxLayout *vb = static_cast<QVBoxLayout *>(layout_ptr);
55 
56     if (!pref || !vb) return 0;
57 
58     // Convert the pref description from plain text to rich text.
59     QString description = html_escape(prefs_get_description(pref));
60     description.replace('\n', "<br>");
61     QString tooltip = QString("<span>%1</span>").arg(description);
62 
63     switch (prefs_get_type(pref)) {
64     case PREF_UINT:
65     case PREF_DECODE_AS_UINT:
66     {
67         QHBoxLayout *hb = new QHBoxLayout();
68         QLabel *label = new QLabel(prefs_get_title(pref));
69         label->setToolTip(tooltip);
70         hb->addWidget(label);
71         QLineEdit *uint_le = new QLineEdit();
72         uint_le->setToolTip(tooltip);
73         uint_le->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
74         uint_le->setMinimumWidth(uint_le->fontMetrics().height() * 8);
75         hb->addWidget(uint_le);
76         hb->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
77         vb->addLayout(hb);
78         break;
79     }
80     case PREF_BOOL:
81     {
82         QCheckBox *bool_cb = new QCheckBox(title_to_shortcut(prefs_get_title(pref)));
83         bool_cb->setToolTip(tooltip);
84         bool_cb->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
85         vb->addWidget(bool_cb);
86         break;
87     }
88     case PREF_ENUM:
89     {
90         const enum_val_t *ev;
91         ev = prefs_get_enumvals(pref);
92         if (!ev || !ev->description)
93             return 0;
94 
95         if (prefs_get_enum_radiobuttons(pref)) {
96             QLabel *label = new QLabel(prefs_get_title(pref));
97             label->setToolTip(tooltip);
98             vb->addWidget(label);
99             QButtonGroup *enum_bg = new QButtonGroup(vb);
100             while (ev->description) {
101                 QRadioButton *enum_rb = new QRadioButton(title_to_shortcut(ev->description));
102                 enum_rb->setToolTip(tooltip);
103                 QStyleOption style_opt;
104                 enum_rb->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
105                 enum_rb->setStyleSheet(QString(
106                                       "QRadioButton {"
107                                       "  margin-left: %1px;"
108                                       "}"
109                                       )
110                                   .arg(enum_rb->style()->subElementRect(QStyle::SE_CheckBoxContents, &style_opt).left()));
111                 enum_bg->addButton(enum_rb, ev->value);
112                 vb->addWidget(enum_rb);
113                 ev++;
114             }
115         } else {
116             QHBoxLayout *hb = new QHBoxLayout();
117             QComboBox *enum_cb = new QComboBox();
118             enum_cb->setToolTip(tooltip);
119             enum_cb->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
120             for (ev = prefs_get_enumvals(pref); ev && ev->description; ev++) {
121                 enum_cb->addItem(ev->description, QVariant(ev->value));
122             }
123             hb->addWidget(new QLabel(prefs_get_title(pref)));
124             hb->addWidget(enum_cb);
125             hb->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
126             vb->addLayout(hb);
127         }
128         break;
129     }
130     case PREF_STRING:
131     {
132         QHBoxLayout *hb = new QHBoxLayout();
133         QLabel *label = new QLabel(prefs_get_title(pref));
134         label->setToolTip(tooltip);
135         hb->addWidget(label);
136         QLineEdit *string_le = new QLineEdit();
137         string_le->setToolTip(tooltip);
138         string_le->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
139         string_le->setMinimumWidth(string_le->fontMetrics().height() * 20);
140         hb->addWidget(string_le);
141         hb->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
142         vb->addLayout(hb);
143         break;
144     }
145     case PREF_DECODE_AS_RANGE:
146     case PREF_RANGE:
147     {
148         QHBoxLayout *hb = new QHBoxLayout();
149         QLabel *label = new QLabel(prefs_get_title(pref));
150         label->setToolTip(tooltip);
151         hb->addWidget(label);
152         SyntaxLineEdit *range_se = new SyntaxLineEdit();
153         range_se->setToolTip(tooltip);
154         range_se->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
155         range_se->setMinimumWidth(range_se->fontMetrics().height() * 20);
156         hb->addWidget(range_se);
157         hb->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
158         vb->addLayout(hb);
159         break;
160     }
161     case PREF_STATIC_TEXT:
162     {
163         QLabel *label = new QLabel(prefs_get_title(pref));
164         label->setToolTip(tooltip);
165         label->setWordWrap(true);
166         vb->addWidget(label);
167         break;
168     }
169     case PREF_UAT:
170     {
171         QHBoxLayout *hb = new QHBoxLayout();
172         QLabel *label = new QLabel(prefs_get_title(pref));
173         label->setToolTip(tooltip);
174         hb->addWidget(label);
175         QPushButton *uat_pb = new QPushButton(QObject::tr("Edit…"));
176         uat_pb->setToolTip(tooltip);
177         uat_pb->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
178         hb->addWidget(uat_pb);
179         hb->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
180         vb->addLayout(hb);
181         break;
182     }
183     case PREF_SAVE_FILENAME:
184     case PREF_OPEN_FILENAME:
185     case PREF_DIRNAME:
186     {
187         QLabel *label = new QLabel(prefs_get_title(pref));
188         label->setToolTip(tooltip);
189         vb->addWidget(label);
190         QHBoxLayout *hb = new QHBoxLayout();
191         QLineEdit *path_le = new QLineEdit();
192         path_le->setToolTip(tooltip);
193         QStyleOption style_opt;
194         path_le->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
195         path_le->setMinimumWidth(path_le->fontMetrics().height() * 20);
196         path_le->setStyleSheet(QString(
197                               "QLineEdit {"
198                               "  margin-left: %1px;"
199                               "}"
200                               )
201                           .arg(path_le->style()->subElementRect(QStyle::SE_CheckBoxContents, &style_opt).left()));
202         hb->addWidget(path_le);
203         QPushButton *path_pb = new QPushButton(QObject::tr("Browse…"));
204         path_pb->setProperty(pref_prop_, VariantPointer<pref_t>::asQVariant(pref));
205         hb->addWidget(path_pb);
206         hb->addSpacerItem(new QSpacerItem(1, 1, QSizePolicy::Expanding, QSizePolicy::Minimum));
207         vb->addLayout(hb);
208         break;
209     }
210     case PREF_COLOR:
211     {
212         // XXX - Not needed yet. When it is needed we can add a label + QFrame which pops up a
213         // color picker similar to the Font and Colors prefs.
214         break;
215     }
216     default:
217         break;
218     }
219     return 0;
220 }
221 
222 } // extern "C"
223 
ModulePreferencesScrollArea(module_t * module,QWidget * parent)224 ModulePreferencesScrollArea::ModulePreferencesScrollArea(module_t *module, QWidget *parent) :
225     QScrollArea(parent),
226     ui(new Ui::ModulePreferencesScrollArea),
227     module_(module)
228 {
229     ui->setupUi(this);
230 
231     if (!module) return;
232 
233     /* Show the preference's description at the top of the page */
234     QFont font;
235     font.setBold(TRUE);
236     QLabel *label = new QLabel(module->description);
237     label->setFont(font);
238     ui->verticalLayout->addWidget(label);
239 
240     /* Add items for each of the preferences */
241     prefs_pref_foreach(module, pref_show, (gpointer) ui->verticalLayout);
242 
243     foreach (QLineEdit *le, findChildren<QLineEdit *>()) {
244         pref_t *pref = VariantPointer<pref_t>::asPtr(le->property(pref_prop_));
245         if (!pref) continue;
246 
247         switch (prefs_get_type(pref)) {
248         case PREF_DECODE_AS_UINT:
249             connect(le, &QLineEdit::textEdited, this, &ModulePreferencesScrollArea::uintLineEditTextEdited);
250             break;
251         case PREF_UINT:
252             connect(le, &QLineEdit::textEdited, this, &ModulePreferencesScrollArea::uintLineEditTextEdited);
253             break;
254         case PREF_STRING:
255         case PREF_SAVE_FILENAME:
256         case PREF_OPEN_FILENAME:
257         case PREF_DIRNAME:
258             connect(le, &QLineEdit::textEdited, this, &ModulePreferencesScrollArea::stringLineEditTextEdited);
259             break;
260         case PREF_RANGE:
261         case PREF_DECODE_AS_RANGE:
262             connect(le, &QLineEdit::textEdited, this, &ModulePreferencesScrollArea::rangeSyntaxLineEditTextEdited);
263             break;
264         default:
265             break;
266         }
267     }
268 
269     foreach (QCheckBox *cb, findChildren<QCheckBox *>()) {
270         pref_t *pref = VariantPointer<pref_t>::asPtr(cb->property(pref_prop_));
271         if (!pref) continue;
272 
273         if (prefs_get_type(pref) == PREF_BOOL) {
274             connect(cb, &QCheckBox::toggled, this, &ModulePreferencesScrollArea::boolCheckBoxToggled);
275         }
276     }
277 
278     foreach (QRadioButton *rb, findChildren<QRadioButton *>()) {
279         pref_t *pref = VariantPointer<pref_t>::asPtr(rb->property(pref_prop_));
280         if (!pref) continue;
281 
282         if (prefs_get_type(pref) == PREF_ENUM && prefs_get_enum_radiobuttons(pref)) {
283             connect(rb, &QRadioButton::toggled, this, &ModulePreferencesScrollArea::enumRadioButtonToggled);
284         }
285     }
286 
287     foreach (QComboBox *combo, findChildren<QComboBox *>()) {
288         pref_t *pref = VariantPointer<pref_t>::asPtr(combo->property(pref_prop_));
289         if (!pref) continue;
290 
291         if (prefs_get_type(pref) == PREF_ENUM && !prefs_get_enum_radiobuttons(pref)) {
292             connect(combo, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
293                     this, &ModulePreferencesScrollArea::enumComboBoxCurrentIndexChanged);
294         }
295     }
296 
297     foreach (QPushButton *pb, findChildren<QPushButton *>()) {
298         pref_t *pref = VariantPointer<pref_t>::asPtr(pb->property(pref_prop_));
299         if (!pref) continue;
300 
301         switch (prefs_get_type(pref)) {
302         case PREF_UAT:
303             connect(pb, &QPushButton::clicked, this, &ModulePreferencesScrollArea::uatPushButtonClicked);
304             break;
305         case PREF_SAVE_FILENAME:
306             connect(pb, &QPushButton::clicked, this, &ModulePreferencesScrollArea::saveFilenamePushButtonClicked);
307             break;
308         case PREF_OPEN_FILENAME:
309             connect(pb, &QPushButton::clicked, this, &ModulePreferencesScrollArea::openFilenamePushButtonClicked);
310             break;
311         case PREF_DIRNAME:
312             connect(pb, &QPushButton::clicked, this, &ModulePreferencesScrollArea::dirnamePushButtonClicked);
313             break;
314         }
315     }
316 
317     ui->verticalLayout->addSpacerItem(new QSpacerItem(10, 1, QSizePolicy::Minimum, QSizePolicy::Expanding));
318 }
319 
~ModulePreferencesScrollArea()320 ModulePreferencesScrollArea::~ModulePreferencesScrollArea()
321 {
322     delete ui;
323 }
324 
showEvent(QShowEvent *)325 void ModulePreferencesScrollArea::showEvent(QShowEvent *)
326 {
327     updateWidgets();
328 }
329 
resizeEvent(QResizeEvent * evt)330 void ModulePreferencesScrollArea::resizeEvent(QResizeEvent *evt)
331 {
332     QScrollArea::resizeEvent(evt);
333 
334     if (verticalScrollBar()->isVisible()) {
335         setFrameStyle(QFrame::StyledPanel);
336     } else {
337         setFrameStyle(QFrame::NoFrame);
338     }
339 }
340 
updateWidgets()341 void ModulePreferencesScrollArea::updateWidgets()
342 {
343     foreach (QLineEdit *le, findChildren<QLineEdit *>()) {
344         pref_t *pref = VariantPointer<pref_t>::asPtr(le->property(pref_prop_));
345         if (!pref) continue;
346 
347         le->setText(gchar_free_to_qstring(prefs_pref_to_str(pref, pref_stashed)).remove(QRegExp("\n\t")));
348     }
349 
350     foreach (QCheckBox *cb, findChildren<QCheckBox *>()) {
351         pref_t *pref = VariantPointer<pref_t>::asPtr(cb->property(pref_prop_));
352         if (!pref) continue;
353 
354         if (prefs_get_type(pref) == PREF_BOOL) {
355             cb->setChecked(prefs_get_bool_value(pref, pref_stashed));
356         }
357     }
358 
359     foreach (QRadioButton *enum_rb, findChildren<QRadioButton *>()) {
360         pref_t *pref = VariantPointer<pref_t>::asPtr(enum_rb->property(pref_prop_));
361         if (!pref) continue;
362 
363         QButtonGroup *enum_bg = enum_rb->group();
364         if (!enum_bg) continue;
365 
366         if (prefs_get_type(pref) == PREF_ENUM && prefs_get_enum_radiobuttons(pref)) {
367             if (prefs_get_enum_value(pref, pref_stashed) == enum_bg->id(enum_rb)) {
368                 enum_rb->setChecked(true);
369             }
370         }
371     }
372 
373     foreach (QComboBox *enum_cb, findChildren<QComboBox *>()) {
374         pref_t *pref = VariantPointer<pref_t>::asPtr(enum_cb->property(pref_prop_));
375         if (!pref) continue;
376 
377         if (prefs_get_type(pref) == PREF_ENUM && !prefs_get_enum_radiobuttons(pref)) {
378             for (int i = 0; i < enum_cb->count(); i++) {
379                 if (prefs_get_enum_value(pref, pref_stashed) == enum_cb->itemData(i).toInt()) {
380                     enum_cb->setCurrentIndex(i);
381                 }
382             }
383         }
384     }
385 }
386 
uintLineEditTextEdited(const QString & new_str)387 void ModulePreferencesScrollArea::uintLineEditTextEdited(const QString &new_str)
388 {
389     QLineEdit *uint_le = qobject_cast<QLineEdit*>(sender());
390     if (!uint_le) return;
391 
392     pref_t *pref = VariantPointer<pref_t>::asPtr(uint_le->property(pref_prop_));
393     if (!pref) return;
394 
395     bool ok;
396     uint new_uint = new_str.toUInt(&ok, 0);
397     if (ok) {
398         prefs_set_uint_value(pref, new_uint, pref_stashed);
399     }
400 }
401 
boolCheckBoxToggled(bool checked)402 void ModulePreferencesScrollArea::boolCheckBoxToggled(bool checked)
403 {
404     QCheckBox *bool_cb = qobject_cast<QCheckBox*>(sender());
405     if (!bool_cb) return;
406 
407     pref_t *pref = VariantPointer<pref_t>::asPtr(bool_cb->property(pref_prop_));
408     if (!pref) return;
409 
410     prefs_set_bool_value(pref, checked, pref_stashed);
411 }
412 
enumRadioButtonToggled(bool checked)413 void ModulePreferencesScrollArea::enumRadioButtonToggled(bool checked)
414 {
415     if (!checked) return;
416     QRadioButton *enum_rb = qobject_cast<QRadioButton*>(sender());
417     if (!enum_rb) return;
418 
419     QButtonGroup *enum_bg = enum_rb->group();
420     if (!enum_bg) return;
421 
422     pref_t *pref = VariantPointer<pref_t>::asPtr(enum_rb->property(pref_prop_));
423     if (!pref) return;
424 
425     if (enum_bg->checkedId() >= 0) {
426         prefs_set_enum_value(pref, enum_bg->checkedId(), pref_stashed);
427     }
428 }
429 
enumComboBoxCurrentIndexChanged(int index)430 void ModulePreferencesScrollArea::enumComboBoxCurrentIndexChanged(int index)
431 {
432     QComboBox *enum_cb = qobject_cast<QComboBox*>(sender());
433     if (!enum_cb) return;
434 
435     pref_t *pref = VariantPointer<pref_t>::asPtr(enum_cb->property(pref_prop_));
436     if (!pref) return;
437 
438     prefs_set_enum_value(pref, enum_cb->itemData(index).toInt(), pref_stashed);
439 }
440 
stringLineEditTextEdited(const QString & new_str)441 void ModulePreferencesScrollArea::stringLineEditTextEdited(const QString &new_str)
442 {
443     QLineEdit *string_le = qobject_cast<QLineEdit*>(sender());
444     if (!string_le) return;
445 
446     pref_t *pref = VariantPointer<pref_t>::asPtr(string_le->property(pref_prop_));
447     if (!pref) return;
448 
449     prefs_set_string_value(pref, new_str.toStdString().c_str(), pref_stashed);
450 }
451 
rangeSyntaxLineEditTextEdited(const QString & new_str)452 void ModulePreferencesScrollArea::rangeSyntaxLineEditTextEdited(const QString &new_str)
453 {
454     SyntaxLineEdit *range_se = qobject_cast<SyntaxLineEdit*>(sender());
455     if (!range_se) return;
456 
457     pref_t *pref = VariantPointer<pref_t>::asPtr(range_se->property(pref_prop_));
458     if (!pref) return;
459 
460     if (prefs_set_stashed_range_value(pref, new_str.toUtf8().constData())) {
461         if (new_str.isEmpty()) {
462             range_se->setSyntaxState(SyntaxLineEdit::Empty);
463         } else {
464             range_se->setSyntaxState(SyntaxLineEdit::Valid);
465         }
466     } else {
467         range_se->setSyntaxState(SyntaxLineEdit::Invalid);
468     }
469 }
470 
uatPushButtonClicked()471 void ModulePreferencesScrollArea::uatPushButtonClicked()
472 {
473     QPushButton *uat_pb = qobject_cast<QPushButton*>(sender());
474     if (!uat_pb) return;
475 
476     pref_t *pref = VariantPointer<pref_t>::asPtr(uat_pb->property(pref_prop_));
477     if (!pref) return;
478 
479     UatDialog *uat_dlg = new UatDialog(this, prefs_get_uat_value(pref));
480     uat_dlg->setWindowModality(Qt::ApplicationModal);
481     uat_dlg->setAttribute(Qt::WA_DeleteOnClose);
482     uat_dlg->show();
483 }
484 
saveFilenamePushButtonClicked()485 void ModulePreferencesScrollArea::saveFilenamePushButtonClicked()
486 {
487     QPushButton *filename_pb = qobject_cast<QPushButton*>(sender());
488     if (!filename_pb) return;
489 
490     pref_t *pref = VariantPointer<pref_t>::asPtr(filename_pb->property(pref_prop_));
491     if (!pref) return;
492 
493     QString filename = WiresharkFileDialog::getSaveFileName(this, wsApp->windowTitleString(prefs_get_title(pref)),
494                                                     prefs_get_string_value(pref, pref_stashed));
495 
496     if (!filename.isEmpty()) {
497         prefs_set_string_value(pref, QDir::toNativeSeparators(filename).toStdString().c_str(), pref_stashed);
498         updateWidgets();
499     }
500 }
501 
openFilenamePushButtonClicked()502 void ModulePreferencesScrollArea::openFilenamePushButtonClicked()
503 {
504     QPushButton *filename_pb = qobject_cast<QPushButton*>(sender());
505     if (!filename_pb) return;
506 
507     pref_t *pref = VariantPointer<pref_t>::asPtr(filename_pb->property(pref_prop_));
508     if (!pref) return;
509 
510     QString filename = WiresharkFileDialog::getOpenFileName(this, wsApp->windowTitleString(prefs_get_title(pref)),
511                                                     prefs_get_string_value(pref, pref_stashed));
512     if (!filename.isEmpty()) {
513         prefs_set_string_value(pref, QDir::toNativeSeparators(filename).toStdString().c_str(), pref_stashed);
514         updateWidgets();
515     }
516 }
517 
dirnamePushButtonClicked()518 void ModulePreferencesScrollArea::dirnamePushButtonClicked()
519 {
520     QPushButton *dirname_pb = qobject_cast<QPushButton*>(sender());
521     if (!dirname_pb) return;
522 
523     pref_t *pref = VariantPointer<pref_t>::asPtr(dirname_pb->property(pref_prop_));
524     if (!pref) return;
525 
526     QString dirname = WiresharkFileDialog::getExistingDirectory(this, wsApp->windowTitleString(prefs_get_title(pref)),
527                                                  prefs_get_string_value(pref, pref_stashed));
528 
529     if (!dirname.isEmpty()) {
530         prefs_set_string_value(pref, QDir::toNativeSeparators(dirname).toStdString().c_str(), pref_stashed);
531         updateWidgets();
532     }
533 }
534