Qt: Add back pathLabel in some dialogs
[metze/wireshark/wip.git] / ui / qt / coloring_rules_dialog.cpp
1 /* coloring_rules_dialog.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 "config.h"
11
12 #include "coloring_rules_dialog.h"
13 #include <ui_coloring_rules_dialog.h>
14
15 #include "ui/simple_dialog.h"
16 #include "epan/prefs.h"
17
18 #include <wsutil/utf8_entities.h>
19
20 #include "wsutil/filesystem.h"
21
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"
26
27 #include <QColorDialog>
28 #include <QMessageBox>
29 #include <QPushButton>
30 #include <QUrl>
31
32 /*
33  * @file Coloring Rules dialog
34  *
35  * Coloring rule editor for the current profile.
36  */
37
38 // To do:
39 // - Make the filter column narrower? It's easy to run into Qt's annoying
40 //   habit of horizontally scrolling QTreeWidgets here.
41
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)
48 {
49     ui->setupUi(this);
50     if (parent) loadGeometry(parent->width() * 2 / 3, parent->height() * 4 / 5);
51
52     setWindowTitle(wsApp->windowTitleString(tr("Coloring Rules %1").arg(get_profile_name())));
53
54     ui->coloringRulesTreeView->setModel(&colorRuleModel_);
55     ui->coloringRulesTreeView->setItemDelegate(&colorRuleDelegate_);
56
57     ui->coloringRulesTreeView->viewport()->setAcceptDrops(true);
58
59     for (int i = 0; i < colorRuleModel_.columnCount(); i++) {
60         ui->coloringRulesTreeView->resizeColumnToContents(i);
61     }
62
63 #ifdef Q_OS_MAC
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);
69 #endif
70
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()));
79
80     rowCountChanged();
81
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."));
86
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 *)));
93
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);
100     }
101
102     if (!add_filter.isEmpty()) {
103         colorRuleModel_.addColor(false, add_filter, palette().color(QPalette::Text), palette().color(QPalette::Base));
104
105         //setup the buttons appropriately
106         ui->coloringRulesTreeView->setCurrentIndex(colorRuleModel_.index(0, 0));
107
108         //set edit on display filter
109         ui->coloringRulesTreeView->edit(colorRuleModel_.index(0, 1));
110     }else {
111         ui->coloringRulesTreeView->setCurrentIndex(QModelIndex());
112     }
113
114     checkUnknownColorfilters();
115
116     updateHint();
117 }
118
119 ColoringRulesDialog::~ColoringRulesDialog()
120 {
121     delete copy_from_menu_;
122     delete ui;
123 }
124
125 void ColoringRulesDialog::checkUnknownColorfilters()
126 {
127     if (prefs.unknown_colorfilters) {
128         QMessageBox mb;
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);
133
134         mb.exec();
135         prefs.unknown_colorfilters = FALSE;
136     }
137 }
138
139 void ColoringRulesDialog::copyFromProfile(QAction *action)
140 {
141     QString filename = action->data().toString();
142     QString err;
143
144     if (!colorRuleModel_.importColors(filename, err)) {
145         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
146     }
147
148     for (int i = 0; i < colorRuleModel_.columnCount(); i++) {
149         ui->coloringRulesTreeView->resizeColumnToContents(i);
150     }
151
152     checkUnknownColorfilters();
153 }
154
155 void ColoringRulesDialog::showEvent(QShowEvent *)
156 {
157     ui->fGPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
158     ui->bGPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
159 #ifndef Q_OS_MAC
160     ui->displayFilterPushButton->setFixedHeight(ui->copyToolButton->geometry().height());
161 #endif
162 }
163
164 void ColoringRulesDialog::rowCountChanged()
165 {
166     ui->clearToolButton->setEnabled(colorRuleModel_.rowCount() > 0);
167 }
168
169 void ColoringRulesDialog::invalidField(const QModelIndex &index, const QString& errMessage)
170 {
171     errors_.insert(index, errMessage);
172     updateHint();
173 }
174
175 void ColoringRulesDialog::validField(const QModelIndex &index)
176 {
177     if (errors_.remove(index) > 0) {
178         updateHint();
179     }
180 }
181
182 void ColoringRulesDialog::updateHint()
183 {
184     QString hint = "<small><i>";
185     QString error_text;
186     bool enable_save = true;
187
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();
192
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]);
199     }
200
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.");
203     } else {
204         hint += error_text;
205         enable_save = false;
206     }
207
208     hint += "</i></small>";
209     ui->hintLabel->setText(hint);
210
211     ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(enable_save);
212 }
213
214 void ColoringRulesDialog::setColorButtons(QModelIndex &index)
215 {
216     QString color_button_ss =
217             "QPushButton {"
218             "  border: 1px solid palette(Dark);"
219             "  padding-left: %1px;"
220             "  padding-right: %1px;"
221             "  color: %2;"
222             "  background-color: %3;"
223             "}";
224
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);
232     } else {
233         QString fg_color = fg.toString();
234         QString bg_color = bg.toString();
235
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));
238     }
239 }
240
241 void ColoringRulesDialog::colorRuleSelectionChanged(const QItemSelection&, const QItemSelection&)
242 {
243     QModelIndexList selectedList = ui->coloringRulesTreeView->selectionModel()->selectedIndexes();
244
245     //determine the number of unique rows
246     QHash<int, QModelIndex> selectedRows;
247     foreach (const QModelIndex &index, selectedList) {
248         selectedRows.insert(index.row(), index);
249     }
250
251     int num_selected = selectedRows.count();
252     if (num_selected == 1) {
253         setColorButtons(selectedList[0]);
254     }
255
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);
261 }
262
263 void ColoringRulesDialog::changeColor(bool foreground)
264 {
265     QModelIndex current = ui->coloringRulesTreeView->currentIndex();
266     if (!current.isValid())
267         return;
268
269     QColorDialog color_dlg;
270
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);
275     }
276 }
277
278 void ColoringRulesDialog::on_fGPushButton_clicked()
279 {
280     changeColor();
281 }
282
283 void ColoringRulesDialog::on_bGPushButton_clicked()
284 {
285     changeColor(false);
286 }
287
288 void ColoringRulesDialog::on_displayFilterPushButton_clicked()
289 {
290     QModelIndex current = ui->coloringRulesTreeView->currentIndex();
291     if (!current.isValid())
292         return;
293
294     QString filter = colorRuleModel_.data(colorRuleModel_.index(current.row(), ColoringRulesModel::colFilter), Qt::DisplayRole).toString();
295     emit filterAction(filter, FilterAction::ActionApply, FilterAction::ActionTypePlain);
296 }
297
298 void ColoringRulesDialog::addRule(bool copy_from_current)
299 {
300     const QModelIndex &current = ui->coloringRulesTreeView->currentIndex();
301     if (copy_from_current && !current.isValid())
302         return;
303
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());
307     } else {
308         if (!colorRuleModel_.insertRows(0, 1)) {
309             return;
310         }
311     }
312
313     //set edit on display filter
314     ui->coloringRulesTreeView->edit(colorRuleModel_.index(0, 1));
315 }
316
317 void ColoringRulesDialog::on_newToolButton_clicked()
318 {
319     addRule();
320 }
321
322 void ColoringRulesDialog::on_deleteToolButton_clicked()
323 {
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());
329
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);
338             }
339         }
340     }
341 }
342
343 void ColoringRulesDialog::on_copyToolButton_clicked()
344 {
345     addRule(true);
346 }
347
348 void ColoringRulesDialog::on_clearToolButton_clicked()
349 {
350     colorRuleModel_.removeRows(0, colorRuleModel_.rowCount());
351 }
352
353 void ColoringRulesDialog::on_buttonBox_clicked(QAbstractButton *button)
354 {
355     QString err;
356
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());
363             }
364         }
365     } else if (button == export_button_) {
366         int num_items = ui->coloringRulesTreeView->selectionModel()->selectedIndexes().count()/colorRuleModel_.columnCount();
367
368         if (num_items < 1) {
369             num_items = colorRuleModel_.rowCount();
370         }
371
372         if (num_items < 1)
373             return;
374
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());
381             }
382         }
383     }
384 }
385
386 void ColoringRulesDialog::on_buttonBox_accepted()
387 {
388     QString err;
389     if (!colorRuleModel_.writeColors(err)) {
390         simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, "%s", err.toUtf8().constData());
391     }
392 }
393
394 void ColoringRulesDialog::on_buttonBox_helpRequested()
395 {
396     wsApp->helpTopicAction(HELP_COLORING_RULES_DIALOG);
397 }
398
399 /*
400  * Editor modelines
401  *
402  * Local Variables:
403  * c-basic-offset: 4
404  * tab-width: 8
405  * indent-tabs-mode: nil
406  * End:
407  *
408  * ex: set shiftwidth=4 tabstop=8 expandtab:
409  * :indentSize=4:tabSize=8:noTabs=true:
410  */