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"
22 #include "wireshark_application.h"
23 #include "ui/qt/utils/qt_ui_utils.h"
24 #include "ui/qt/widgets/copy_from_profile_menu.h"
25 #include "ui/qt/widgets/wireshark_file_dialog.h"
27 #include <QColorDialog>
28 #include <QMessageBox>
29 #include <QPushButton>
33 * @file Coloring Rules dialog
35 * Coloring rule editor for the current profile.
39 // - Make the filter column narrower? It's easy to run into Qt's annoying
40 // habit of horizontally scrolling QTreeWidgets here.
42 ColoringRulesDialog::ColoringRulesDialog(QWidget *parent, QString add_filter) :
43 GeometryStateDialog(parent),
44 ui(new Ui::ColoringRulesDialog),
45 copy_from_menu_(NULL),
46 colorRuleModel_(palette().color(QPalette::Text), palette().color(QPalette::Base), this),
47 colorRuleDelegate_(this)
50 if (parent) loadGeometry(parent->width() * 2 / 3, parent->height() * 4 / 5);
52 setWindowTitle(wsApp->windowTitleString(tr("Coloring Rules %1").arg(get_profile_name())));
54 ui->coloringRulesTreeView->setModel(&colorRuleModel_);
55 ui->coloringRulesTreeView->setItemDelegate(&colorRuleDelegate_);
57 ui->coloringRulesTreeView->viewport()->setAcceptDrops(true);
59 for (int i = 0; i < colorRuleModel_.columnCount(); i++) {
60 ui->coloringRulesTreeView->resizeColumnToContents(i);
64 ui->newToolButton->setAttribute(Qt::WA_MacSmallSize, true);
65 ui->deleteToolButton->setAttribute(Qt::WA_MacSmallSize, true);
66 ui->copyToolButton->setAttribute(Qt::WA_MacSmallSize, true);
67 ui->clearToolButton->setAttribute(Qt::WA_MacSmallSize, true);
68 ui->pathLabel->setAttribute(Qt::WA_MacSmallSize, true);
71 connect(ui->coloringRulesTreeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
72 this, SLOT(colorRuleSelectionChanged(const QItemSelection &, const QItemSelection &)));
73 connect(&colorRuleDelegate_, SIGNAL(invalidField(const QModelIndex&, const QString&)),
74 this, SLOT(invalidField(const QModelIndex&, const QString&)));
75 connect(&colorRuleDelegate_, SIGNAL(validField(const QModelIndex&)),
76 this, SLOT(validField(const QModelIndex&)));
77 connect(&colorRuleModel_, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SLOT(rowCountChanged()));
78 connect(&colorRuleModel_, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SLOT(rowCountChanged()));
82 import_button_ = ui->buttonBox->addButton(tr("Import" UTF8_HORIZONTAL_ELLIPSIS), QDialogButtonBox::ApplyRole);
83 import_button_->setToolTip(tr("Select a file and add its filters to the end of the list."));
84 export_button_ = ui->buttonBox->addButton(tr("Export" UTF8_HORIZONTAL_ELLIPSIS), QDialogButtonBox::ApplyRole);
85 export_button_->setToolTip(tr("Save filters in a file."));
87 QPushButton *copy_button = ui->buttonBox->addButton(tr("Copy from"), QDialogButtonBox::ActionRole);
88 copy_from_menu_ = new CopyFromProfileMenu(COLORFILTERS_FILE_NAME);
89 copy_button->setMenu(copy_from_menu_);
90 copy_button->setToolTip(tr("Copy coloring rules from another profile."));
91 copy_button->setEnabled(copy_from_menu_->haveProfiles());
92 connect(copy_from_menu_, SIGNAL(triggered(QAction *)), this, SLOT(copyFromProfile(QAction *)));
94 QString abs_path = gchar_free_to_qstring(get_persconffile_path(COLORFILTERS_FILE_NAME, TRUE));
95 if (file_exists(abs_path.toUtf8().constData())) {
96 ui->pathLabel->setText(abs_path);
97 ui->pathLabel->setUrl(QUrl::fromLocalFile(abs_path).toString());
98 ui->pathLabel->setToolTip(tr("Open ") + COLORFILTERS_FILE_NAME);
99 ui->pathLabel->setEnabled(true);
102 if (!add_filter.isEmpty()) {
103 colorRuleModel_.addColor(false, add_filter, palette().color(QPalette::Text), palette().color(QPalette::Base));
105 //setup the buttons appropriately
106 ui->coloringRulesTreeView->setCurrentIndex(colorRuleModel_.index(0, 0));
108 //set edit on display filter
109 ui->coloringRulesTreeView->edit(colorRuleModel_.index(0, 1));
111 ui->coloringRulesTreeView->setCurrentIndex(QModelIndex());
114 checkUnknownColorfilters();
119 ColoringRulesDialog::~ColoringRulesDialog()
121 delete copy_from_menu_;
125 void ColoringRulesDialog::checkUnknownColorfilters()
127 if (prefs.unknown_colorfilters) {
129 mb.setText(tr("Your coloring rules file contains unknown rules"));
130 mb.setInformativeText(tr("Wireshark doesn't recognize one or more of your coloring rules. "
131 "They have been disabled."));
132 mb.setStandardButtons(QMessageBox::Ok);
135 prefs.unknown_colorfilters = FALSE;
139 void ColoringRulesDialog::copyFromProfile(QAction *action)
141 QString filename = action->data().toString();
144 if (!colorRuleModel_.importColors(filename, err)) {
145 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
148 for (int i = 0; i < colorRuleModel_.columnCount(); i++) {
149 ui->coloringRulesTreeView->resizeColumnToContents(i);
152 checkUnknownColorfilters();
155 void ColoringRulesDialog::showEvent(QShowEvent *)
157 ui->fGPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
158 ui->bGPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
160 ui->displayFilterPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
164 void ColoringRulesDialog::rowCountChanged()
166 ui->clearToolButton->setEnabled(colorRuleModel_.rowCount() > 0);
169 void ColoringRulesDialog::invalidField(const QModelIndex &index, const QString& errMessage)
171 errors_.insert(index, errMessage);
175 void ColoringRulesDialog::validField(const QModelIndex &index)
177 if (errors_.remove(index) > 0) {
182 void ColoringRulesDialog::updateHint()
184 QString hint = "<small><i>";
186 bool enable_save = true;
188 if (errors_.count() > 0) {
189 //take the list of QModelIndexes and sort them so first color rule error is displayed
190 //This isn't the most efficent algorithm, but the list shouldn't be large to matter
191 QList<QModelIndex> keys = errors_.keys();
193 //list is not guaranteed to be sorted, so force it
194 qSort(keys.begin(), keys.end());
195 const QModelIndex& error_key = keys[0];
196 error_text = QString("%1: %2")
197 .arg(colorRuleModel_.data(colorRuleModel_.index(error_key.row(), ColoringRulesModel::colName), Qt::DisplayRole).toString())
198 .arg(errors_[error_key]);
201 if (error_text.isEmpty()) {
202 hint += tr("Double click to edit. Drag to move. Rules are processed in order until a match is found.");
208 hint += "</i></small>";
209 ui->hintLabel->setText(hint);
211 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable_save);
214 void ColoringRulesDialog::setColorButtons(QModelIndex &index)
216 QString color_button_ss =
218 " border: 1px solid palette(Dark);"
219 " padding-left: %1px;"
220 " padding-right: %1px;"
222 " background-color: %3;"
225 int one_em = fontMetrics().height();
226 QVariant fg = colorRuleModel_.data(index, Qt::ForegroundRole);
227 QVariant bg = colorRuleModel_.data(index, Qt::BackgroundRole);
228 if (fg.isNull() || bg.isNull()) {
229 //should never happen
230 ui->fGPushButton->setVisible(false);
231 ui->bGPushButton->setVisible(false);
233 QString fg_color = fg.toString();
234 QString bg_color = bg.toString();
236 ui->fGPushButton->setStyleSheet(color_button_ss.arg(one_em).arg(bg_color).arg(fg_color));
237 ui->bGPushButton->setStyleSheet(color_button_ss.arg(one_em).arg(fg_color).arg(bg_color));
241 void ColoringRulesDialog::colorRuleSelectionChanged(const QItemSelection&, const QItemSelection&)
243 QModelIndexList selectedList = ui->coloringRulesTreeView->selectionModel()->selectedIndexes();
245 //determine the number of unique rows
246 QHash<int, QModelIndex> selectedRows;
247 foreach (const QModelIndex &index, selectedList) {
248 selectedRows.insert(index.row(), index);
251 int num_selected = selectedRows.count();
252 if (num_selected == 1) {
253 setColorButtons(selectedList[0]);
256 ui->copyToolButton->setEnabled(num_selected == 1);
257 ui->deleteToolButton->setEnabled(num_selected > 0);
258 ui->fGPushButton->setVisible(num_selected == 1);
259 ui->bGPushButton->setVisible(num_selected == 1);
260 ui->displayFilterPushButton->setVisible(num_selected == 1);
263 void ColoringRulesDialog::changeColor(bool foreground)
265 QModelIndex current = ui->coloringRulesTreeView->currentIndex();
266 if (!current.isValid())
269 QColorDialog color_dlg;
271 color_dlg.setCurrentColor(colorRuleModel_.data(current, foreground ? Qt::ForegroundRole : Qt::BackgroundRole).toString());
272 if (color_dlg.exec() == QDialog::Accepted) {
273 colorRuleModel_.setData(current, color_dlg.currentColor(), foreground ? Qt::ForegroundRole : Qt::BackgroundRole);
274 setColorButtons(current);
278 void ColoringRulesDialog::on_fGPushButton_clicked()
283 void ColoringRulesDialog::on_bGPushButton_clicked()
288 void ColoringRulesDialog::on_displayFilterPushButton_clicked()
290 QModelIndex current = ui->coloringRulesTreeView->currentIndex();
291 if (!current.isValid())
294 QString filter = colorRuleModel_.data(colorRuleModel_.index(current.row(), ColoringRulesModel::colFilter), Qt::DisplayRole).toString();
295 emit filterAction(filter, FilterAction::ActionApply, FilterAction::ActionTypePlain);
298 void ColoringRulesDialog::addRule(bool copy_from_current)
300 const QModelIndex ¤t = ui->coloringRulesTreeView->currentIndex();
301 if (copy_from_current && !current.isValid())
304 //always add rules at the top of the list
305 if (copy_from_current) {
306 colorRuleModel_.copyRow(colorRuleModel_.index(0, 0).row(), current.row());
308 if (!colorRuleModel_.insertRows(0, 1)) {
313 //set edit on display filter
314 ui->coloringRulesTreeView->edit(colorRuleModel_.index(0, 1));
317 void ColoringRulesDialog::on_newToolButton_clicked()
322 void ColoringRulesDialog::on_deleteToolButton_clicked()
324 QModelIndexList selectedList = ui->coloringRulesTreeView->selectionModel()->selectedIndexes();
325 int num_selected = selectedList.count()/colorRuleModel_.columnCount();
326 if (num_selected > 0) {
327 //list is not guaranteed to be sorted, so force it
328 qSort(selectedList.begin(), selectedList.end());
330 //walk the list from the back because deleting a value in
331 //the middle will leave the selectedList out of sync and
332 //delete the wrong elements
333 for (int i = selectedList.count()-1; i >= 0; i--) {
334 QModelIndex deleteIndex = selectedList[i];
335 //selectedList includes all cells, use first column as key to remove row
336 if (deleteIndex.isValid() && (deleteIndex.column() == 0)) {
337 colorRuleModel_.removeRows(deleteIndex.row(), 1);
343 void ColoringRulesDialog::on_copyToolButton_clicked()
348 void ColoringRulesDialog::on_clearToolButton_clicked()
350 colorRuleModel_.removeRows(0, colorRuleModel_.rowCount());
353 void ColoringRulesDialog::on_buttonBox_clicked(QAbstractButton *button)
357 if (button == import_button_) {
358 QString file_name = WiresharkFileDialog::getOpenFileName(this, wsApp->windowTitleString(tr("Import Coloring Rules")),
359 wsApp->lastOpenDir().path());
360 if (!file_name.isEmpty()) {
361 if (!colorRuleModel_.importColors(file_name, err)) {
362 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
365 } else if (button == export_button_) {
366 int num_items = ui->coloringRulesTreeView->selectionModel()->selectedIndexes().count()/colorRuleModel_.columnCount();
369 num_items = colorRuleModel_.rowCount();
375 QString caption = wsApp->windowTitleString(tr("Export %1 Coloring Rules").arg(num_items));
376 QString file_name = WiresharkFileDialog::getSaveFileName(this, caption,
377 wsApp->lastOpenDir().path());
378 if (!file_name.isEmpty()) {
379 if (!colorRuleModel_.exportColors(file_name, err)) {
380 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
386 void ColoringRulesDialog::on_buttonBox_accepted()
389 if (!colorRuleModel_.writeColors(err)) {
390 simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
394 void ColoringRulesDialog::on_buttonBox_helpRequested()
396 wsApp->helpTopicAction(HELP_COLORING_RULES_DIALOG);
405 * indent-tabs-mode: nil
408 * ex: set shiftwidth=4 tabstop=8 expandtab:
409 * :indentSize=4:tabSize=8:noTabs=true: