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