LATER... ei_kerberos_kdc_session_key ...
[wireshark-sm.git] / ui / qt / coloring_rules_dialog.cpp
blob77353d7e20bc0847a639c0719cd3bcbb4337fa88
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
8 */
10 #include "config.h"
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"
29 #include <functional>
30 #include <QColorDialog>
31 #include <QMessageBox>
32 #include <QPushButton>
33 #include <QUrl>
36 * @file Coloring Rules dialog
38 * Coloring rule editor for the current profile.
41 // To do:
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)
51 ui->setupUi(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");
70 #ifdef Q_OS_MAC
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);
76 #endif
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()));
88 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));
115 }else {
116 ui->coloringRulesTreeView->setCurrentIndex(QModelIndex());
119 updateHint();
122 ColoringRulesDialog::~ColoringRulesDialog()
124 delete ui;
127 void ColoringRulesDialog::copyFromProfile(QString filename)
129 QString err;
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());
144 #ifndef Q_OS_MAC
145 ui->displayFilterPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
146 #endif
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)) {
160 dfilter_free(dfp);
161 return true;
164 if (df_err)
166 error->append(df_err->msg);
167 df_error_free(&df_err);
170 return false;
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();
177 QString err;
178 if (! isValidFilter(filter, &err) && index.data(Qt::CheckStateRole).toInt() == Qt::Checked)
180 errors_.insert(index, err);
181 updateHint(index);
183 else
185 QList<QModelIndex> keys = errors_.keys();
186 bool update = false;
187 foreach (QModelIndex key, keys)
189 if (key.row() == index.row())
191 errors_.remove(key);
192 update = true;
196 if (update)
197 updateHint(index);
201 void ColoringRulesDialog::invalidField(const QModelIndex &index, const QString& errMessage)
203 errors_.insert(index, errMessage);
204 updateHint(index);
207 void ColoringRulesDialog::validField(const QModelIndex &index)
209 QList<QModelIndex> keys = errors_.keys();
210 bool update = false;
211 foreach (QModelIndex key, keys)
213 if (key.row() == index.row())
215 errors_.remove(key);
216 update = true;
220 if (update)
221 updateHint(index);
224 void ColoringRulesDialog::updateHint(QModelIndex idx)
226 QString hint = "<small><i>";
227 QString error_text;
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.");
245 } else {
246 hint += error_text;
247 if (idx.isValid())
249 QModelIndex fiIdx = ui->coloringRulesTreeView->model()->index(idx.row(), ColoringRulesModel::colName);
250 if (fiIdx.data(Qt::CheckStateRole).toInt() == Qt::Checked)
251 enable_save = false;
253 else
254 enable_save = false;
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 =
266 "QPushButton {"
267 " border: 1px solid palette(Dark);"
268 " padding-left: %1px;"
269 " padding-right: %1px;"
270 " color: %2;"
271 " background-color: %3;"
272 "}";
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);
281 } else {
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())
316 return;
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);
324 color_dlg->show();
327 void ColoringRulesDialog::colorChanged(bool foreground, const QColor &cc)
329 QModelIndex current = ui->coloringRulesTreeView->currentIndex();
330 if (!current.isValid())
331 return;
333 colorRuleModel_.setData(current, cc, foreground ? Qt::ForegroundRole : Qt::BackgroundRole);
334 setColorButtons(current);
337 void ColoringRulesDialog::on_fGPushButton_clicked()
339 changeColor();
342 void ColoringRulesDialog::on_bGPushButton_clicked()
344 changeColor(false);
347 void ColoringRulesDialog::on_displayFilterPushButton_clicked()
349 QModelIndex current = ui->coloringRulesTreeView->currentIndex();
350 if (!current.isValid())
351 return;
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 &current = ui->coloringRulesTreeView->currentIndex();
360 if (copy_from_current && !current.isValid())
361 return;
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());
366 } else {
367 if (!colorRuleModel_.insertRows(0, 1)) {
368 return;
372 //set edit on display filter
373 ui->coloringRulesTreeView->edit(colorRuleModel_.index(0, 1));
376 void ColoringRulesDialog::on_newToolButton_clicked()
378 addRule();
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()
404 addRule(true);
407 void ColoringRulesDialog::on_clearToolButton_clicked()
409 colorRuleModel_.removeRows(0, colorRuleModel_.rowCount());
412 void ColoringRulesDialog::on_buttonBox_clicked(QAbstractButton *button)
414 QString err;
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();
427 if (num_items < 1) {
428 num_items = colorRuleModel_.rowCount();
431 if (num_items < 1)
432 return;
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()
447 QString err;
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;
453 done(ret);
456 void ColoringRulesDialog::on_buttonBox_helpRequested()
458 mainApp->helpTopicAction(HELP_COLORING_RULES_DIALOG);