wireshark/ui/qt/coloring_rules_dialog.cpp

346 lines
11 KiB
C++

/* coloring_rules_dialog.cpp
*
* Wireshark - Network traffic analyzer
* By Gerald Combs <gerald@wireshark.org>
* Copyright 1998 Gerald Combs
*
* SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "config.h"
#include "coloring_rules_dialog.h"
#include <ui_coloring_rules_dialog.h>
#include "ui/simple_dialog.h"
#include "epan/prefs.h"
#include <wsutil/utf8_entities.h>
#include "wsutil/filesystem.h"
#include "wireshark_application.h"
#include <QColorDialog>
#include <QFileDialog>
#include <QMessageBox>
#include <QPushButton>
/*
* @file Coloring Rules dialog
*
* Coloring rule editor for the current profile.
*/
// To do:
// - Make the filter column narrower? It's easy to run into Qt's annoying
// habit of horizontally scrolling QTreeWidgets here.
ColoringRulesDialog::ColoringRulesDialog(QWidget *parent, QString add_filter) :
GeometryStateDialog(parent),
ui(new Ui::ColoringRulesDialog),
colorRuleModel_(palette().color(QPalette::Text), palette().color(QPalette::Base), this),
colorRuleDelegate_(this)
{
ui->setupUi(this);
if (parent) loadGeometry(parent->width() * 2 / 3, parent->height() * 4 / 5);
setWindowTitle(wsApp->windowTitleString(tr("Coloring Rules %1").arg(get_profile_name())));
ui->coloringRulesTreeView->setModel(&colorRuleModel_);
ui->coloringRulesTreeView->setItemDelegate(&colorRuleDelegate_);
ui->coloringRulesTreeView->viewport()->setAcceptDrops(true);
for (int i = 0; i < colorRuleModel_.columnCount(); i++) {
ui->coloringRulesTreeView->resizeColumnToContents(i);
}
connect(ui->coloringRulesTreeView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection &, const QItemSelection &)),
this, SLOT(colorRuleSelectionChanged(const QItemSelection &, const QItemSelection &)));
connect(&colorRuleDelegate_, SIGNAL(invalidField(const QModelIndex&, const QString&)),
this, SLOT(invalidField(const QModelIndex&, const QString&)));
connect(&colorRuleDelegate_, SIGNAL(validField(const QModelIndex&)),
this, SLOT(validField(const QModelIndex&)));
import_button_ = ui->buttonBox->addButton(tr("Import" UTF8_HORIZONTAL_ELLIPSIS), QDialogButtonBox::ApplyRole);
import_button_->setToolTip(tr("Select a file and add its filters to the end of the list."));
export_button_ = ui->buttonBox->addButton(tr("Export" UTF8_HORIZONTAL_ELLIPSIS), QDialogButtonBox::ApplyRole);
export_button_->setToolTip(tr("Save filters in a file."));
if (!add_filter.isEmpty()) {
colorRuleModel_.addColor(false, add_filter, palette().color(QPalette::Text), palette().color(QPalette::Base));
//setup the buttons appropriately
ui->coloringRulesTreeView->setCurrentIndex(colorRuleModel_.index(0, 0));
//set edit on display filter
ui->coloringRulesTreeView->edit(colorRuleModel_.index(0, 1));
}else {
ui->coloringRulesTreeView->setCurrentIndex(QModelIndex());
}
updateHint();
}
ColoringRulesDialog::~ColoringRulesDialog()
{
delete ui;
}
void ColoringRulesDialog::showEvent(QShowEvent *)
{
ui->fGPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
ui->bGPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
ui->displayFilterPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
}
void ColoringRulesDialog::invalidField(const QModelIndex &index, const QString& errMessage)
{
errors_.insert(index, errMessage);
updateHint();
}
void ColoringRulesDialog::validField(const QModelIndex &index)
{
if (errors_.remove(index) > 0) {
updateHint();
}
}
void ColoringRulesDialog::updateHint()
{
QString hint = "<small><i>";
QString error_text;
bool enable_save = true;
if (errors_.count() > 0) {
//take the list of QModelIndexes and sort them so first color rule error is displayed
//This isn't the most efficent algorithm, but the list shouldn't be large to matter
QList<QModelIndex> keys = errors_.keys();
//list is not guaranteed to be sorted, so force it
qSort(keys.begin(), keys.end());
const QModelIndex& error_key = keys[0];
error_text = QString("%1: %2")
.arg(colorRuleModel_.data(colorRuleModel_.index(error_key.row(), ColoringRulesModel::colName), Qt::DisplayRole).toString())
.arg(errors_[error_key]);
}
if (error_text.isEmpty()) {
hint += tr("Double click to edit. Drag to move. Rules are processed in order until a match is found.");
} else {
hint += error_text;
enable_save = false;
}
hint += "</i></small>";
ui->hintLabel->setText(hint);
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable_save);
}
void ColoringRulesDialog::setColorButtons(QModelIndex &index)
{
QString color_button_ss =
"QPushButton {"
" border: 1px solid palette(Dark);"
" padding-left: %1px;"
" padding-right: %1px;"
" color: %2;"
" background-color: %3;"
"}";
int one_em = fontMetrics().height();
QVariant fg = colorRuleModel_.data(index, Qt::ForegroundRole);
QVariant bg = colorRuleModel_.data(index, Qt::BackgroundRole);
if (fg.isNull() || bg.isNull()) {
//should never happen
ui->fGPushButton->setVisible(false);
ui->bGPushButton->setVisible(false);
} else {
QString fg_color = fg.toString();
QString bg_color = bg.toString();
ui->fGPushButton->setStyleSheet(color_button_ss.arg(one_em).arg(bg_color).arg(fg_color));
ui->bGPushButton->setStyleSheet(color_button_ss.arg(one_em).arg(fg_color).arg(bg_color));
}
}
void ColoringRulesDialog::colorRuleSelectionChanged(const QItemSelection&, const QItemSelection&)
{
QModelIndexList selectedList = ui->coloringRulesTreeView->selectionModel()->selectedIndexes();
//determine the number of unique rows
QHash<int, QModelIndex> selectedRows;
foreach (const QModelIndex &index, selectedList) {
selectedRows.insert(index.row(), index);
}
int num_selected = selectedRows.count();
if (num_selected == 1) {
setColorButtons(selectedList[0]);
}
ui->copyToolButton->setEnabled(num_selected == 1);
ui->deleteToolButton->setEnabled(num_selected > 0);
ui->fGPushButton->setVisible(num_selected == 1);
ui->bGPushButton->setVisible(num_selected == 1);
ui->displayFilterPushButton->setVisible(num_selected == 1);
}
void ColoringRulesDialog::changeColor(bool foreground)
{
QModelIndex current = ui->coloringRulesTreeView->currentIndex();
if (!current.isValid())
return;
QColorDialog color_dlg;
color_dlg.setCurrentColor(colorRuleModel_.data(current, foreground ? Qt::ForegroundRole : Qt::BackgroundRole).toString());
if (color_dlg.exec() == QDialog::Accepted) {
colorRuleModel_.setData(current, color_dlg.currentColor(), foreground ? Qt::ForegroundRole : Qt::BackgroundRole);
setColorButtons(current);
}
}
void ColoringRulesDialog::on_fGPushButton_clicked()
{
changeColor();
}
void ColoringRulesDialog::on_bGPushButton_clicked()
{
changeColor(false);
}
void ColoringRulesDialog::on_displayFilterPushButton_clicked()
{
QModelIndex current = ui->coloringRulesTreeView->currentIndex();
if (!current.isValid())
return;
QString filter = colorRuleModel_.data(colorRuleModel_.index(current.row(), ColoringRulesModel::colFilter), Qt::DisplayRole).toString();
emit filterAction(filter, FilterAction::ActionApply, FilterAction::ActionTypePlain);
}
void ColoringRulesDialog::addRule(bool copy_from_current)
{
const QModelIndex &current = ui->coloringRulesTreeView->currentIndex();
if (copy_from_current && !current.isValid())
return;
//always add rules at the top of the list
if (copy_from_current) {
colorRuleModel_.copyRow(colorRuleModel_.index(0, 0).row(), current.row());
} else {
if (!colorRuleModel_.insertRows(0, 1)) {
return;
}
}
//set edit on display filter
ui->coloringRulesTreeView->edit(colorRuleModel_.index(0, 1));
}
void ColoringRulesDialog::on_newToolButton_clicked()
{
addRule();
}
void ColoringRulesDialog::on_deleteToolButton_clicked()
{
QModelIndexList selectedList = ui->coloringRulesTreeView->selectionModel()->selectedIndexes();
int num_selected = selectedList.count()/colorRuleModel_.columnCount();
if (num_selected > 0) {
//list is not guaranteed to be sorted, so force it
qSort(selectedList.begin(), selectedList.end());
//walk the list from the back because deleting a value in
//the middle will leave the selectedList out of sync and
//delete the wrong elements
for (int i = selectedList.count()-1; i >= 0; i--) {
QModelIndex deleteIndex = selectedList[i];
//selectedList includes all cells, use first column as key to remove row
if (deleteIndex.isValid() && (deleteIndex.column() == 0)) {
colorRuleModel_.removeRows(deleteIndex.row(), 1);
}
}
}
}
void ColoringRulesDialog::on_copyToolButton_clicked()
{
addRule(true);
}
void ColoringRulesDialog::on_buttonBox_clicked(QAbstractButton *button)
{
QString err;
if (button == import_button_) {
QString file_name = QFileDialog::getOpenFileName(this, wsApp->windowTitleString(tr("Import Coloring Rules")),
wsApp->lastOpenDir().path());
if (!file_name.isEmpty()) {
if (!colorRuleModel_.importColors(file_name, err)) {
simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
}
}
} else if (button == export_button_) {
int num_items = ui->coloringRulesTreeView->selectionModel()->selectedIndexes().count()/colorRuleModel_.columnCount();
if (num_items < 1) {
num_items = colorRuleModel_.rowCount();
}
if (num_items < 1)
return;
QString caption = wsApp->windowTitleString(tr("Export %1 Coloring Rules").arg(num_items));
QString file_name = QFileDialog::getSaveFileName(this, caption,
wsApp->lastOpenDir().path());
if (!file_name.isEmpty()) {
if (!colorRuleModel_.exportColors(file_name, err)) {
simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
}
}
}
}
void ColoringRulesDialog::on_buttonBox_accepted()
{
if (prefs.unknown_colorfilters) {
QMessageBox mb;
mb.setText(tr("Your coloring rules file contains unknown rules"));
mb.setInformativeText(tr("Wireshark doesn't recognize one or more of your coloring rules. "
"They have been disabled."));
mb.setStandardButtons(QMessageBox::Ok);
int result = mb.exec();
if (result != QMessageBox::Save) return;
}
QString err;
if (!colorRuleModel_.writeColors(err)) {
simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
}
}
void ColoringRulesDialog::on_buttonBox_helpRequested()
{
wsApp->helpTopicAction(HELP_COLORING_RULES_DIALOG);
}
/*
* Editor modelines
*
* Local Variables:
* c-basic-offset: 4
* tab-width: 8
* indent-tabs-mode: nil
* End:
*
* ex: set shiftwidth=4 tabstop=8 expandtab:
* :indentSize=4:tabSize=8:noTabs=true:
*/