1 /* coloring_rules_dialog.cpp
3 * Wireshark - Network traffic analyzer
4 * By Gerald Combs <gerald@wireshark.org>
5 * Copyright 1998 Gerald Combs
7 * SPDX-License-Identifier: GPL-2.0-or-later
12 #include "coloring_rules_dialog.h"
13 #include <ui_coloring_rules_dialog.h>
15 #include "ui/simple_dialog.h"
16 #include "epan/prefs.h"
18 #include <wsutil/utf8_entities.h>
20 #include "wsutil/filesystem.h"
21 #include "epan/dfilter/dfilter.h"
23 #include "main_application.h"
25 #include "ui/qt/utils/qt_ui_utils.h"
26 #include "ui/qt/widgets/copy_from_profile_button.h"
27 #include "ui/qt/widgets/wireshark_file_dialog.h"
30 #include <QColorDialog>
31 #include <QMessageBox>
32 #include <QPushButton>
36 * @file Coloring Rules dialog
38 * Coloring rule editor for the current profile.
42 // - Make the filter column narrower? It's easy to run into Qt's annoying
43 // habit of horizontally scrolling QTreeWidgets here.
45 ColoringRulesDialog::ColoringRulesDialog(QWidget
*parent
, QString add_filter
) :
46 GeometryStateDialog(parent
),
47 ui(new Ui::ColoringRulesDialog
),
48 colorRuleModel_(palette().color(QPalette::Text
), palette().color(QPalette::Base
), this),
49 colorRuleDelegate_(this)
52 if (parent
) loadGeometry(parent
->width() * 2 / 3, parent
->height() * 4 / 5);
54 setWindowTitle(mainApp
->windowTitleString(tr("Coloring Rules %1").arg(get_profile_name())));
56 ui
->coloringRulesTreeView
->setModel(&colorRuleModel_
);
57 ui
->coloringRulesTreeView
->setItemDelegate(&colorRuleDelegate_
);
59 ui
->coloringRulesTreeView
->viewport()->setAcceptDrops(true);
61 for (int i
= 0; i
< colorRuleModel_
.columnCount(); i
++) {
62 ui
->coloringRulesTreeView
->resizeColumnToContents(i
);
65 ui
->newToolButton
->setStockIcon("list-add");
66 ui
->deleteToolButton
->setStockIcon("list-remove");
67 ui
->copyToolButton
->setStockIcon("list-copy");
68 ui
->clearToolButton
->setStockIcon("list-clear");
71 ui
->newToolButton
->setAttribute(Qt::WA_MacSmallSize
, true);
72 ui
->deleteToolButton
->setAttribute(Qt::WA_MacSmallSize
, true);
73 ui
->copyToolButton
->setAttribute(Qt::WA_MacSmallSize
, true);
74 ui
->clearToolButton
->setAttribute(Qt::WA_MacSmallSize
, true);
75 ui
->pathLabel
->setAttribute(Qt::WA_MacSmallSize
, true);
78 connect(ui
->coloringRulesTreeView
->selectionModel(), SIGNAL(selectionChanged(const QItemSelection
&, const QItemSelection
&)),
79 this, SLOT(colorRuleSelectionChanged(const QItemSelection
&, const QItemSelection
&)));
80 connect(&colorRuleDelegate_
, SIGNAL(invalidField(const QModelIndex
&, const QString
&)),
81 this, SLOT(invalidField(const QModelIndex
&, const QString
&)));
82 connect(&colorRuleDelegate_
, SIGNAL(validField(const QModelIndex
&)),
83 this, SLOT(validField(const QModelIndex
&)));
84 connect(ui
->coloringRulesTreeView
, &QTreeView::clicked
, this, &ColoringRulesDialog::treeItemClicked
);
85 connect(&colorRuleModel_
, SIGNAL(rowsInserted(const QModelIndex
&, int, int)), this, SLOT(rowCountChanged()));
86 connect(&colorRuleModel_
, SIGNAL(rowsRemoved(const QModelIndex
&, int, int)), this, SLOT(rowCountChanged()));
90 import_button_
= ui
->buttonBox
->addButton(tr("Import…"), QDialogButtonBox::ApplyRole
);
91 import_button_
->setToolTip(tr("Select a file and add its filters to the end of the list."));
92 export_button_
= ui
->buttonBox
->addButton(tr("Export…"), QDialogButtonBox::ApplyRole
);
93 export_button_
->setToolTip(tr("Save filters in a file."));
95 CopyFromProfileButton
* copy_button
= new CopyFromProfileButton(this, COLORFILTERS_FILE_NAME
, tr("Copy coloring rules from another profile."));
96 ui
->buttonBox
->addButton(copy_button
, QDialogButtonBox::ActionRole
);
97 connect(copy_button
, &CopyFromProfileButton::copyProfile
, this, &ColoringRulesDialog::copyFromProfile
);
99 QString abs_path
= gchar_free_to_qstring(get_persconffile_path(COLORFILTERS_FILE_NAME
, true));
100 if (file_exists(abs_path
.toUtf8().constData())) {
101 ui
->pathLabel
->setText(abs_path
);
102 ui
->pathLabel
->setUrl(QUrl::fromLocalFile(abs_path
).toString());
103 ui
->pathLabel
->setToolTip(tr("Open ") + COLORFILTERS_FILE_NAME
);
104 ui
->pathLabel
->setEnabled(true);
107 if (!add_filter
.isEmpty()) {
108 colorRuleModel_
.addColor(false, add_filter
, palette().color(QPalette::Text
), palette().color(QPalette::Base
));
110 //setup the buttons appropriately
111 ui
->coloringRulesTreeView
->setCurrentIndex(colorRuleModel_
.index(0, 0));
113 //set edit on display filter
114 ui
->coloringRulesTreeView
->edit(colorRuleModel_
.index(0, 1));
116 ui
->coloringRulesTreeView
->setCurrentIndex(QModelIndex());
122 ColoringRulesDialog::~ColoringRulesDialog()
127 void ColoringRulesDialog::copyFromProfile(QString filename
)
131 if (!colorRuleModel_
.importColors(filename
, err
)) {
132 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
, "%s", err
.toUtf8().constData());
135 for (int i
= 0; i
< colorRuleModel_
.columnCount(); i
++) {
136 ui
->coloringRulesTreeView
->resizeColumnToContents(i
);
140 void ColoringRulesDialog::showEvent(QShowEvent
*)
142 ui
->fGPushButton
->setFixedHeight(ui
->copyToolButton
->geometry().height());
143 ui
->bGPushButton
->setFixedHeight(ui
->copyToolButton
->geometry().height());
145 ui
->displayFilterPushButton
->setFixedHeight(ui
->copyToolButton
->geometry().height());
149 void ColoringRulesDialog::rowCountChanged()
151 ui
->clearToolButton
->setEnabled(colorRuleModel_
.rowCount() > 0);
154 bool ColoringRulesDialog::isValidFilter(QString filter
, QString
* error
)
156 dfilter_t
*dfp
= NULL
;
157 df_error_t
*df_err
= NULL
;
159 if (dfilter_compile(filter
.toUtf8().constData(), &dfp
, &df_err
)) {
166 error
->append(df_err
->msg
);
167 df_error_free(&df_err
);
173 void ColoringRulesDialog::treeItemClicked(const QModelIndex
&index
)
175 QModelIndex idx
= ui
->coloringRulesTreeView
->model()->index(index
.row(), ColoringRulesModel::colFilter
);
176 QString filter
= idx
.data(Qt::DisplayRole
).toString();
178 if (! isValidFilter(filter
, &err
) && index
.data(Qt::CheckStateRole
).toInt() == Qt::Checked
)
180 errors_
.insert(index
, err
);
185 QList
<QModelIndex
> keys
= errors_
.keys();
187 foreach (QModelIndex key
, keys
)
189 if (key
.row() == index
.row())
201 void ColoringRulesDialog::invalidField(const QModelIndex
&index
, const QString
& errMessage
)
203 errors_
.insert(index
, errMessage
);
207 void ColoringRulesDialog::validField(const QModelIndex
&index
)
209 QList
<QModelIndex
> keys
= errors_
.keys();
211 foreach (QModelIndex key
, keys
)
213 if (key
.row() == index
.row())
224 void ColoringRulesDialog::updateHint(QModelIndex idx
)
226 QString hint
= "<small><i>";
228 bool enable_save
= true;
230 if (errors_
.count() > 0) {
231 //take the list of QModelIndexes and sort them so first color rule error is displayed
232 //This isn't the most efficient algorithm, but the list shouldn't be large to matter
233 QList
<QModelIndex
> keys
= errors_
.keys();
235 //list is not guaranteed to be sorted, so force it
236 std::sort(keys
.begin(), keys
.end());
237 const QModelIndex
& error_key
= keys
[0];
238 error_text
= QStringLiteral("%1: %2")
239 .arg(colorRuleModel_
.data(colorRuleModel_
.index(error_key
.row(), ColoringRulesModel::colName
), Qt::DisplayRole
).toString())
240 .arg(errors_
[error_key
]);
243 if (error_text
.isEmpty()) {
244 hint
+= tr("Double click to edit. Drag to move. Rules are processed in order until a match is found.");
249 QModelIndex fiIdx
= ui
->coloringRulesTreeView
->model()->index(idx
.row(), ColoringRulesModel::colName
);
250 if (fiIdx
.data(Qt::CheckStateRole
).toInt() == Qt::Checked
)
257 hint
+= "</i></small>";
258 ui
->hintLabel
->setText(hint
);
260 ui
->buttonBox
->button(QDialogButtonBox::Ok
)->setEnabled(enable_save
);
263 void ColoringRulesDialog::setColorButtons(QModelIndex
&index
)
265 QString color_button_ss
=
267 " border: 1px solid palette(Dark);"
268 " padding-left: %1px;"
269 " padding-right: %1px;"
271 " background-color: %3;"
274 int one_em
= fontMetrics().height();
275 QVariant fg
= colorRuleModel_
.data(index
, Qt::ForegroundRole
);
276 QVariant bg
= colorRuleModel_
.data(index
, Qt::BackgroundRole
);
277 if (fg
.isNull() || bg
.isNull()) {
278 //should never happen
279 ui
->fGPushButton
->setVisible(false);
280 ui
->bGPushButton
->setVisible(false);
282 QString fg_color
= fg
.toString();
283 QString bg_color
= bg
.toString();
285 ui
->fGPushButton
->setStyleSheet(color_button_ss
.arg(one_em
).arg(bg_color
).arg(fg_color
));
286 ui
->bGPushButton
->setStyleSheet(color_button_ss
.arg(one_em
).arg(fg_color
).arg(bg_color
));
290 void ColoringRulesDialog::colorRuleSelectionChanged(const QItemSelection
&, const QItemSelection
&)
292 QModelIndexList selectedList
= ui
->coloringRulesTreeView
->selectionModel()->selectedIndexes();
294 //determine the number of unique rows
295 QHash
<int, QModelIndex
> selectedRows
;
296 foreach (const QModelIndex
&index
, selectedList
) {
297 selectedRows
.insert(index
.row(), index
);
300 qsizetype num_selected
= selectedRows
.count();
301 if (num_selected
== 1) {
302 setColorButtons(selectedList
[0]);
305 ui
->copyToolButton
->setEnabled(num_selected
== 1);
306 ui
->deleteToolButton
->setEnabled(num_selected
> 0);
307 ui
->fGPushButton
->setVisible(num_selected
== 1);
308 ui
->bGPushButton
->setVisible(num_selected
== 1);
309 ui
->displayFilterPushButton
->setVisible(num_selected
== 1);
312 void ColoringRulesDialog::changeColor(bool foreground
)
314 QModelIndex current
= ui
->coloringRulesTreeView
->currentIndex();
315 if (!current
.isValid())
318 QColorDialog
*color_dlg
= new QColorDialog(this);
319 color_dlg
->setCurrentColor(colorRuleModel_
.data(current
, foreground
? Qt::ForegroundRole
: Qt::BackgroundRole
).toString());
321 connect(color_dlg
, &QColorDialog::colorSelected
, std::bind(&ColoringRulesDialog::colorChanged
, this, foreground
, std::placeholders::_1
));
322 color_dlg
->setWindowModality(Qt::ApplicationModal
);
323 color_dlg
->setAttribute(Qt::WA_DeleteOnClose
);
327 void ColoringRulesDialog::colorChanged(bool foreground
, const QColor
&cc
)
329 QModelIndex current
= ui
->coloringRulesTreeView
->currentIndex();
330 if (!current
.isValid())
333 colorRuleModel_
.setData(current
, cc
, foreground
? Qt::ForegroundRole
: Qt::BackgroundRole
);
334 setColorButtons(current
);
337 void ColoringRulesDialog::on_fGPushButton_clicked()
342 void ColoringRulesDialog::on_bGPushButton_clicked()
347 void ColoringRulesDialog::on_displayFilterPushButton_clicked()
349 QModelIndex current
= ui
->coloringRulesTreeView
->currentIndex();
350 if (!current
.isValid())
353 QString filter
= colorRuleModel_
.data(colorRuleModel_
.index(current
.row(), ColoringRulesModel::colFilter
), Qt::DisplayRole
).toString();
354 emit
filterAction(filter
, FilterAction::ActionApply
, FilterAction::ActionTypePlain
);
357 void ColoringRulesDialog::addRule(bool copy_from_current
)
359 const QModelIndex
¤t
= ui
->coloringRulesTreeView
->currentIndex();
360 if (copy_from_current
&& !current
.isValid())
363 //always add rules at the top of the list
364 if (copy_from_current
) {
365 colorRuleModel_
.copyRow(colorRuleModel_
.index(0, 0).row(), current
.row());
367 if (!colorRuleModel_
.insertRows(0, 1)) {
372 //set edit on display filter
373 ui
->coloringRulesTreeView
->edit(colorRuleModel_
.index(0, 1));
376 void ColoringRulesDialog::on_newToolButton_clicked()
381 void ColoringRulesDialog::on_deleteToolButton_clicked()
383 QModelIndexList selectedList
= ui
->coloringRulesTreeView
->selectionModel()->selectedIndexes();
384 qsizetype num_selected
= selectedList
.count() / colorRuleModel_
.columnCount();
385 if (num_selected
> 0) {
386 //list is not guaranteed to be sorted, so force it
387 std::sort(selectedList
.begin(), selectedList
.end());
389 //walk the list from the back because deleting a value in
390 //the middle will leave the selectedList out of sync and
391 //delete the wrong elements
392 for (int i
= static_cast<int>(selectedList
.count()) - 1; i
>= 0; i
--) {
393 QModelIndex deleteIndex
= selectedList
[i
];
394 //selectedList includes all cells, use first column as key to remove row
395 if (deleteIndex
.isValid() && (deleteIndex
.column() == 0)) {
396 colorRuleModel_
.removeRows(deleteIndex
.row(), 1);
402 void ColoringRulesDialog::on_copyToolButton_clicked()
407 void ColoringRulesDialog::on_clearToolButton_clicked()
409 colorRuleModel_
.removeRows(0, colorRuleModel_
.rowCount());
412 void ColoringRulesDialog::on_buttonBox_clicked(QAbstractButton
*button
)
416 if (button
== import_button_
) {
417 QString file_name
= WiresharkFileDialog::getOpenFileName(this, mainApp
->windowTitleString(tr("Import Coloring Rules")),
418 mainApp
->openDialogInitialDir().path());
419 if (!file_name
.isEmpty()) {
420 if (!colorRuleModel_
.importColors(file_name
, err
)) {
421 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
, "%s", err
.toUtf8().constData());
424 } else if (button
== export_button_
) {
425 int num_items
= static_cast<int>(ui
->coloringRulesTreeView
->selectionModel()->selectedIndexes().count()) / colorRuleModel_
.columnCount();
428 num_items
= colorRuleModel_
.rowCount();
434 QString caption
= mainApp
->windowTitleString(tr("Export %1 Coloring Rules").arg(num_items
));
435 QString file_name
= WiresharkFileDialog::getSaveFileName(this, caption
,
436 mainApp
->openDialogInitialDir().path());
437 if (!file_name
.isEmpty()) {
438 if (!colorRuleModel_
.exportColors(file_name
, err
)) {
439 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
, "%s", err
.toUtf8().constData());
445 void ColoringRulesDialog::on_buttonBox_accepted()
448 int ret
= QDialog::Accepted
;
449 if (!colorRuleModel_
.writeColors(err
)) {
450 simple_dialog(ESD_TYPE_ERROR
, ESD_BTN_OK
, "%s", err
.toUtf8().constData());
451 ret
= QDialog::Rejected
;
456 void ColoringRulesDialog::on_buttonBox_helpRequested()
458 mainApp
->helpTopicAction(HELP_COLORING_RULES_DIALOG
);