client: Unify CoreHighlightSettingsPage tooltips
[quassel.git] / src / qtui / settingspages / corehighlightsettingspage.cpp
1 /***************************************************************************
2  *   Copyright (C) 2005-2018 by the Quassel Project                        *
3  *   devel@quassel-irc.org                                                 *
4  *                                                                         *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) version 3.                                           *
9  *                                                                         *
10  *   This program is distributed in the hope that it will be useful,       *
11  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
12  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
13  *   GNU General Public License for more details.                          *
14  *                                                                         *
15  *   You should have received a copy of the GNU General Public License     *
16  *   along with this program; if not, write to the                         *
17  *   Free Software Foundation, Inc.,                                       *
18  *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
19  ***************************************************************************/
20
21 #include <QHeaderView>
22 #include <QMessageBox>
23 #include <QTableWidget>
24
25 #include "client.h"
26 #include "corehighlightsettingspage.h"
27 #include "icon.h"
28 #include "qtui.h"
29
30 CoreHighlightSettingsPage::CoreHighlightSettingsPage(QWidget *parent)
31     : SettingsPage(tr("Interface"),
32                    // In Monolithic mode, local highlights are replaced by remote highlights
33                    Quassel::runMode() == Quassel::Monolithic ?
34                        tr("Highlights") : tr("Remote Highlights"),
35                    parent)
36 {
37     ui.setupUi(this);
38
39     setupRuleTable(ui.highlightTable);
40     setupRuleTable(ui.ignoredTable);
41
42     ui.highlightNicksComboBox->addItem(tr("All Nicks from Identity"), QVariant(HighlightRuleManager::AllNicks));
43     ui.highlightNicksComboBox->addItem(tr("Current Nick"), QVariant(HighlightRuleManager::CurrentNick));
44     ui.highlightNicksComboBox->addItem(tr("None"), QVariant(HighlightRuleManager::NoNick));
45
46     coreConnectionStateChanged(Client::isConnected()); // need a core connection!
47     connect(Client::instance(), SIGNAL(coreConnectionStateChanged(bool)), this, SLOT(coreConnectionStateChanged(bool)));
48
49     connect(ui.highlightAdd, SIGNAL(clicked(bool)), this, SLOT(addNewHighlightRow()));
50     connect(ui.highlightRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedHighlightRows()));
51     connect(ui.highlightImport, SIGNAL(clicked(bool)), this, SLOT(importRules()));
52
53     connect(ui.ignoredAdd, SIGNAL(clicked(bool)), this, SLOT(addNewIgnoredRow()));
54     connect(ui.ignoredRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedIgnoredRows()));
55
56     // TODO: search for a better signal (one that emits everytime a selection has been changed for one item)
57     connect(ui.highlightTable,
58             SIGNAL(itemClicked(QTableWidgetItem * )),
59             this,
60             SLOT(selectHighlightRow(QTableWidgetItem * )));
61     connect(ui.ignoredTable,
62             SIGNAL(itemClicked(QTableWidgetItem * )),
63             this,
64             SLOT(selectIgnoredRow(QTableWidgetItem * )));
65
66     // Update the "Case sensitive" checkbox
67     connect(ui.highlightNicksComboBox,
68             SIGNAL(currentIndexChanged(int)),
69             this,
70             SLOT(highlightNicksChanged(int)));
71
72     connect(ui.highlightNicksComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
73     connect(ui.nicksCaseSensitive, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
74
75     connect(ui.highlightAdd, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
76     connect(ui.highlightRemove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
77
78     connect(ui.ignoredAdd, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
79     connect(ui.ignoredRemove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
80
81     connect(ui.highlightTable,
82             SIGNAL(itemChanged(QTableWidgetItem * )),
83             this,
84             SLOT(highlightTableChanged(QTableWidgetItem * )));
85
86     connect(ui.ignoredTable,
87             SIGNAL(itemChanged(QTableWidgetItem * )),
88             this,
89             SLOT(ignoredTableChanged(QTableWidgetItem * )));
90
91     connect(Client::instance(), SIGNAL(connected()), this, SLOT(clientConnected()));
92
93     // Warning icon
94     ui.coreUnsupportedIcon->setPixmap(icon::get("dialog-warning").pixmap(16));
95
96     // Set up client/monolithic remote highlights information
97     if (Quassel::runMode() == Quassel::Monolithic) {
98         // We're running in Monolithic mode, local highlights are considered legacy
99         ui.highlightImport->setText(tr("Import Legacy"));
100         ui.highlightImport->setToolTip(tr("Import highlight rules configured in <i>%1</i>.")
101                                        .arg(tr("Legacy Highlights").replace(" ", "&nbsp;")));
102         // Re-use translations of "Legacy Highlights" as this is a word-for-word reference, forcing
103         // all spaces to be non-breaking
104     } else {
105         // We're running in client/split mode, local highlights are distinguished from remote
106         ui.highlightImport->setText(tr("Import Local"));
107         ui.highlightImport->setToolTip(tr("Import highlight rules configured in <i>%1</i>.")
108                                        .arg(tr("Local Highlights").replace(" ", "&nbsp;")));
109         // Re-use translations of "Local Highlights" as this is a word-for-word reference, forcing
110         // all spaces to be non-breaking
111     }
112 }
113
114
115 void CoreHighlightSettingsPage::coreConnectionStateChanged(bool state)
116 {
117     updateCoreSupportStatus(state);
118     setEnabled(state);
119     if (state) {
120         load();
121     } else {
122         revert();
123     }
124 }
125
126
127 void CoreHighlightSettingsPage::setupRuleTable(QTableWidget *table) const
128 {
129     table->verticalHeader()->hide();
130     table->setShowGrid(false);
131
132     setupTableTooltips(table->horizontalHeaderItem(CoreHighlightSettingsPage::EnableColumn),
133                        table->horizontalHeaderItem(CoreHighlightSettingsPage::NameColumn),
134                        table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn),
135                        table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn),
136                        table->horizontalHeaderItem(CoreHighlightSettingsPage::SenderColumn),
137                        table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn));
138
139 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
140     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
141     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
142     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
143     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
144     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
145 #else
146     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
147     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
148     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
149     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
150     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
151 #endif
152 }
153
154
155 QString CoreHighlightSettingsPage::getTableTooltip(column tableColumn) const
156 {
157     switch (tableColumn) {
158     case CoreHighlightSettingsPage::EnableColumn:
159         return tr("Enable/disable this rule");
160
161     case CoreHighlightSettingsPage::NameColumn:
162         return tr("Phrase to match");
163
164     case CoreHighlightSettingsPage::RegExColumn:
165         return tr("<b>RegEx</b>: This option determines if the highlight rule, <i>Sender</i>, and "
166                   "<i>Channel</i> should be interpreted as <b>regular expressions</b> or just as "
167                   "keywords.");
168
169     case CoreHighlightSettingsPage::CsColumn:
170         return tr("<b>CS</b>: This option determines if the highlight rule, <i>Sender</i>, and "
171                   "<i>Channel</i> should be interpreted <b>case sensitive</b>.");
172
173     case CoreHighlightSettingsPage::SenderColumn:
174         return tr("<p><b>Sender</b>: Semicolon separated list of <i>nick!ident@host</i> names, "
175                   "leave blank to match any nickname.</p>"
176                   "<p><i>Example:</i><br />"
177                   "<i>Alice!*; Bob!*@example.com; Carol*!*; !Caroline!*</i><br />"
178                   "would match on <i>Alice</i>, <i>Bob</i> with hostmask <i>example.com</i>, and "
179                   "any nickname starting with <i>Carol</i> except for <i>Caroline</i><br />"
180                   "<p>If only inverted names are specified, it will match anything except for "
181                   "what's specified (implicit wildcard).</p>"
182                   "<p><i>Example:</i><br />"
183                   "<i>!Announce*!*; !Wheatley!aperture@*</i><br />"
184                   "would match anything except for <i>Wheatley</i> with ident <i>aperture</i> or "
185                   "any nickname starting with <i>Announce</i></p>");
186
187     case CoreHighlightSettingsPage::ChanColumn:
188         return tr("<p><b>Channel</b>: Semicolon separated list of channel names, leave blank to "
189                   "match any name.</p>"
190                   "<p><i>Example:</i><br />"
191                   "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
192                   "would match on <i>#foobar</i> and any channel starting with <i>#quassel</i> "
193                   "except for <i>#quasseldroid</i><br />"
194                   "<p>If only inverted names are specified, it will match anything except for "
195                   "what's specified (implicit wildcard).</p>"
196                   "<p><i>Example:</i><br />"
197                   "<i>!#quassel*; !#foobar</i><br />"
198                   "would match anything except for <i>#foobar</i> or any channel starting with "
199                   "<i>#quassel</i></p>");
200
201     default:
202         // This shouldn't happen
203         return "Invalid column type in CoreHighlightSettingsPage::getTableTooltip()";
204     }
205 }
206
207
208 void CoreHighlightSettingsPage::setupTableTooltips(QWidget *enableWidget, QWidget *nameWidget,
209                                                    QWidget *regExWidget, QWidget *csWidget,
210                                                    QWidget *senderWidget, QWidget *chanWidget) const
211 {
212     // Make sure everything's valid
213     Q_ASSERT(enableWidget);
214     Q_ASSERT(nameWidget);
215     Q_ASSERT(regExWidget);
216     Q_ASSERT(csWidget);
217     Q_ASSERT(senderWidget);
218     Q_ASSERT(chanWidget);
219
220     // Set tooltips and "What's this?" prompts
221     // Enabled
222     enableWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::EnableColumn));
223     enableWidget->setWhatsThis(enableWidget->toolTip());
224
225     // Name
226     nameWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::NameColumn));
227     nameWidget->setWhatsThis(nameWidget->toolTip());
228
229     // RegEx enabled
230     regExWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::RegExColumn));
231     regExWidget->setWhatsThis(regExWidget->toolTip());
232
233     // Case-sensitivity
234     csWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::CsColumn));
235     csWidget->setWhatsThis(csWidget->toolTip());
236
237     // Sender
238     senderWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::SenderColumn));
239     senderWidget->setWhatsThis(senderWidget->toolTip());
240
241     // Channel/buffer name
242     chanWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::ChanColumn));
243     chanWidget->setWhatsThis(chanWidget->toolTip());
244 }
245
246
247 void CoreHighlightSettingsPage::setupTableTooltips(QTableWidgetItem *enableWidget,
248                                                    QTableWidgetItem *nameWidget,
249                                                    QTableWidgetItem *regExWidget,
250                                                    QTableWidgetItem *csWidget,
251                                                    QTableWidgetItem *senderWidget,
252                                                    QTableWidgetItem *chanWidget) const
253 {
254     // Make sure everything's valid
255     Q_ASSERT(enableWidget);
256     Q_ASSERT(nameWidget);
257     Q_ASSERT(regExWidget);
258     Q_ASSERT(csWidget);
259     Q_ASSERT(senderWidget);
260     Q_ASSERT(chanWidget);
261
262     // Set tooltips and "What's this?" prompts
263     // Enabled
264     enableWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::EnableColumn));
265     enableWidget->setWhatsThis(enableWidget->toolTip());
266
267     // Name
268     nameWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::NameColumn));
269     nameWidget->setWhatsThis(nameWidget->toolTip());
270
271     // RegEx enabled
272     regExWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::RegExColumn));
273     regExWidget->setWhatsThis(regExWidget->toolTip());
274
275     // Case-sensitivity
276     csWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::CsColumn));
277     csWidget->setWhatsThis(csWidget->toolTip());
278
279     // Sender
280     senderWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::SenderColumn));
281     senderWidget->setWhatsThis(senderWidget->toolTip());
282
283     // Channel/buffer name
284     chanWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::ChanColumn));
285     chanWidget->setWhatsThis(chanWidget->toolTip());
286 }
287
288
289 void CoreHighlightSettingsPage::updateCoreSupportStatus(bool state)
290 {
291     // Assume connected state as enforced by the settings page UI
292     if (!state || Client::isCoreFeatureEnabled(Quassel::Feature::CoreSideHighlights)) {
293         // Either disconnected or core supports highlights, enable highlight configuration and hide
294         // warning.  Don't show the warning needlessly when disconnected.
295         ui.highlightsConfigWidget->setEnabled(true);
296         ui.coreUnsupportedWidget->setVisible(false);
297     } else {
298         // Core does not support highlights, show warning and disable highlight configuration
299         ui.highlightsConfigWidget->setEnabled(false);
300         ui.coreUnsupportedWidget->setVisible(true);
301     }
302 }
303
304
305 void CoreHighlightSettingsPage::clientConnected()
306 {
307     connect(Client::highlightRuleManager(), SIGNAL(updated()), SLOT(revert()));
308 }
309
310
311 void CoreHighlightSettingsPage::revert()
312 {
313     if (!hasChanged())
314         return;
315
316     setChangedState(false);
317     load();
318 }
319
320
321 bool CoreHighlightSettingsPage::hasDefaults() const
322 {
323     return true;
324 }
325
326
327 void CoreHighlightSettingsPage::defaults()
328 {
329     int highlightNickType = HighlightRuleManager::HighlightNickType::CurrentNick;
330     int defaultIndex = ui.highlightNicksComboBox->findData(QVariant(highlightNickType));
331     ui.highlightNicksComboBox->setCurrentIndex(defaultIndex);
332     ui.nicksCaseSensitive->setChecked(false);
333     emptyHighlightTable();
334     emptyIgnoredTable();
335
336     widgetHasChanged();
337 }
338
339
340 void CoreHighlightSettingsPage::addNewHighlightRow(bool enable, int id, const QString &name, bool regex, bool cs,
341                                                    const QString &sender, const QString &chanName, bool self)
342 {
343     ui.highlightTable->setRowCount(ui.highlightTable->rowCount() + 1);
344
345     if (id < 0) {
346         id = nextId();
347     }
348
349     auto *nameItem = new QTableWidgetItem(name);
350
351     auto *regexItem = new QTableWidgetItem("");
352     if (regex)
353         regexItem->setCheckState(Qt::Checked);
354     else
355         regexItem->setCheckState(Qt::Unchecked);
356     regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
357
358     auto *csItem = new QTableWidgetItem("");
359     if (cs)
360         csItem->setCheckState(Qt::Checked);
361     else
362         csItem->setCheckState(Qt::Unchecked);
363     csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
364
365     auto *enableItem = new QTableWidgetItem("");
366     if (enable)
367         enableItem->setCheckState(Qt::Checked);
368     else
369         enableItem->setCheckState(Qt::Unchecked);
370     enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
371
372     auto *senderItem = new QTableWidgetItem(sender);
373
374     auto *chanNameItem = new QTableWidgetItem(chanName);
375
376     setupTableTooltips(enableItem, nameItem, regexItem, csItem, senderItem, chanNameItem);
377
378     int lastRow = ui.highlightTable->rowCount() - 1;
379     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
380     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
381     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
382     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
383     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
384     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
385
386     if (!self)
387         ui.highlightTable->setCurrentItem(nameItem);
388
389     highlightList << HighlightRuleManager::HighlightRule(id, name, regex, cs, enable, false, sender, chanName);
390 }
391
392
393 void CoreHighlightSettingsPage::addNewIgnoredRow(bool enable, int id, const QString &name, bool regex, bool cs,
394                                                  const QString &sender, const QString &chanName, bool self)
395 {
396     ui.ignoredTable->setRowCount(ui.ignoredTable->rowCount() + 1);
397
398     if (id < 0) {
399         id = nextId();
400     }
401
402     auto *nameItem = new QTableWidgetItem(name);
403
404     auto *regexItem = new QTableWidgetItem("");
405     if (regex)
406         regexItem->setCheckState(Qt::Checked);
407     else
408         regexItem->setCheckState(Qt::Unchecked);
409     regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
410
411     auto *csItem = new QTableWidgetItem("");
412     if (cs)
413         csItem->setCheckState(Qt::Checked);
414     else
415         csItem->setCheckState(Qt::Unchecked);
416     csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
417
418     auto *enableItem = new QTableWidgetItem("");
419     if (enable)
420         enableItem->setCheckState(Qt::Checked);
421     else
422         enableItem->setCheckState(Qt::Unchecked);
423     enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
424
425     auto *chanNameItem = new QTableWidgetItem(chanName);
426
427     auto *senderItem = new QTableWidgetItem(sender);
428
429     setupTableTooltips(enableItem, nameItem, regexItem, csItem, senderItem, chanNameItem);
430
431     int lastRow = ui.ignoredTable->rowCount() - 1;
432     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
433     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
434     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
435     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
436     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
437     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
438
439     if (!self)
440         ui.ignoredTable->setCurrentItem(nameItem);
441
442     ignoredList << HighlightRuleManager::HighlightRule(id, name, regex, cs, enable, true, sender, chanName);
443 }
444
445
446 void CoreHighlightSettingsPage::removeSelectedHighlightRows()
447 {
448     QList<int> selectedRows;
449     QList<QTableWidgetItem *> selectedItemList = ui.highlightTable->selectedItems();
450     for (auto selectedItem : selectedItemList) {
451         selectedRows.append(selectedItem->row());
452     }
453     qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
454     int lastRow = -1;
455     for (auto row : selectedRows) {
456         if (row != lastRow) {
457             ui.highlightTable->removeRow(row);
458             highlightList.removeAt(row);
459         }
460         lastRow = row;
461     }
462 }
463
464
465 void CoreHighlightSettingsPage::removeSelectedIgnoredRows()
466 {
467     QList<int> selectedRows;
468     QList<QTableWidgetItem *> selectedItemList = ui.ignoredTable->selectedItems();
469     for (auto selectedItem : selectedItemList) {
470         selectedRows.append(selectedItem->row());
471     }
472     qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
473     int lastRow = -1;
474     for (auto row : selectedRows) {
475         if (row != lastRow) {
476             ui.ignoredTable->removeRow(row);
477             ignoredList.removeAt(row);
478         }
479         lastRow = row;
480     }
481 }
482
483
484 void CoreHighlightSettingsPage::highlightNicksChanged(const int index)
485 {
486     // Only allow toggling "Case sensitive" when a nickname will be highlighted
487     auto highlightNickType = ui.highlightNicksComboBox->itemData(index).value<int>();
488     ui.nicksCaseSensitive->setEnabled(highlightNickType != HighlightRuleManager::NoNick);
489 }
490
491
492 void CoreHighlightSettingsPage::selectHighlightRow(QTableWidgetItem *item)
493 {
494     int row = item->row();
495     bool selected = item->isSelected();
496     ui.highlightTable
497         ->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1),
498                            selected);
499 }
500
501
502 void CoreHighlightSettingsPage::selectIgnoredRow(QTableWidgetItem *item)
503 {
504     int row = item->row();
505     bool selected = item->isSelected();
506     ui.ignoredTable
507         ->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1),
508                            selected);
509 }
510
511
512 void CoreHighlightSettingsPage::emptyHighlightTable()
513 {
514     // ui.highlight and highlightList should have the same size, but just to make sure.
515     if (ui.highlightTable->rowCount() != highlightList.size()) {
516         qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
517     }
518     while (ui.highlightTable->rowCount()) {
519         ui.highlightTable->removeRow(0);
520     }
521     highlightList.clear();
522 }
523
524
525 void CoreHighlightSettingsPage::emptyIgnoredTable()
526 {
527     // ui.highlight and highlightList should have the same size, but just to make sure.
528     if (ui.ignoredTable->rowCount() != ignoredList.size()) {
529         qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
530     }
531     while (ui.ignoredTable->rowCount()) {
532         ui.ignoredTable->removeRow(0);
533     }
534     ignoredList.clear();
535 }
536
537
538 void CoreHighlightSettingsPage::highlightTableChanged(QTableWidgetItem *item)
539 {
540     if (item->row() + 1 > highlightList.size())
541         return;
542
543     auto highlightRule = highlightList.value(item->row());
544
545
546     switch (item->column()) {
547         case CoreHighlightSettingsPage::EnableColumn:
548             highlightRule.isEnabled = (item->checkState() == Qt::Checked);
549             break;
550         case CoreHighlightSettingsPage::NameColumn:
551             if (item->text() == "")
552                 item->setText(tr("this shouldn't be empty"));
553             highlightRule.name = item->text();
554             break;
555         case CoreHighlightSettingsPage::RegExColumn:
556             highlightRule.isRegEx = (item->checkState() == Qt::Checked);
557             break;
558         case CoreHighlightSettingsPage::CsColumn:
559             highlightRule.isCaseSensitive = (item->checkState() == Qt::Checked);
560             break;
561         case CoreHighlightSettingsPage::SenderColumn:
562             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
563                 item->setText("");
564             highlightRule.sender = item->text();
565             break;
566         case CoreHighlightSettingsPage::ChanColumn:
567             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
568                 item->setText("");
569             highlightRule.chanName = item->text();
570             break;
571     }
572     highlightList[item->row()] = highlightRule;
573     emit widgetHasChanged();
574 }
575
576
577 void CoreHighlightSettingsPage::ignoredTableChanged(QTableWidgetItem *item)
578 {
579     if (item->row() + 1 > ignoredList.size())
580         return;
581
582     auto ignoredRule = ignoredList.value(item->row());
583
584
585     switch (item->column()) {
586         case CoreHighlightSettingsPage::EnableColumn:
587             ignoredRule.isEnabled = (item->checkState() == Qt::Checked);
588             break;
589         case CoreHighlightSettingsPage::NameColumn:
590             if (item->text() == "")
591                 item->setText(tr("this shouldn't be empty"));
592             ignoredRule.name = item->text();
593             break;
594         case CoreHighlightSettingsPage::RegExColumn:
595             ignoredRule.isRegEx = (item->checkState() == Qt::Checked);
596             break;
597         case CoreHighlightSettingsPage::CsColumn:
598             ignoredRule.isCaseSensitive = (item->checkState() == Qt::Checked);
599             break;
600         case CoreHighlightSettingsPage::SenderColumn:
601             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
602                 item->setText("");
603             ignoredRule.sender = item->text();
604             break;
605         case CoreHighlightSettingsPage::ChanColumn:
606             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
607                 item->setText("");
608             ignoredRule.chanName = item->text();
609             break;
610     }
611     ignoredList[item->row()] = ignoredRule;
612     emit widgetHasChanged();
613 }
614
615
616 void CoreHighlightSettingsPage::load()
617 {
618     emptyHighlightTable();
619     emptyIgnoredTable();
620
621     auto ruleManager = Client::highlightRuleManager();
622     if (ruleManager) {
623         for (auto &rule : ruleManager->highlightRuleList()) {
624             if (rule.isInverse) {
625                 addNewIgnoredRow(rule.isEnabled,
626                                  rule.id,
627                                  rule.name,
628                                  rule.isRegEx,
629                                  rule.isCaseSensitive,
630                                  rule.sender,
631                                  rule.chanName);
632             }
633             else {
634                 addNewHighlightRow(rule.isEnabled, rule.id, rule.name, rule.isRegEx, rule.isCaseSensitive, rule.sender,
635                                    rule.chanName);
636             }
637         }
638
639         int highlightNickType = ruleManager->highlightNick();
640         ui.highlightNicksComboBox->setCurrentIndex(ui.highlightNicksComboBox->findData(QVariant(highlightNickType)));
641         // Trigger the initial update of nicksCaseSensitive being enabled or not
642         highlightNicksChanged(ui.highlightNicksComboBox->currentIndex());
643         ui.nicksCaseSensitive->setChecked(ruleManager->nicksCaseSensitive());
644
645         setChangedState(false);
646         _initialized = true;
647     } else {
648         defaults();
649     }
650 }
651
652
653 void CoreHighlightSettingsPage::save()
654 {
655     if (!hasChanged())
656         return;
657
658     if (!_initialized)
659         return;
660
661     auto ruleManager = Client::highlightRuleManager();
662     if (ruleManager == nullptr)
663         return;
664
665     auto clonedManager = HighlightRuleManager();
666     clonedManager.fromVariantMap(ruleManager->toVariantMap());
667     clonedManager.clear();
668
669     for (auto &rule : highlightList) {
670         clonedManager.addHighlightRule(rule.id, rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, false,
671                                        rule.sender, rule.chanName);
672     }
673
674     for (auto &rule : ignoredList) {
675         clonedManager.addHighlightRule(rule.id, rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, true,
676                                        rule.sender, rule.chanName);
677     }
678
679     auto highlightNickType = ui.highlightNicksComboBox->itemData(ui.highlightNicksComboBox->currentIndex()).value<int>();
680
681     clonedManager.setHighlightNick(HighlightRuleManager::HighlightNickType(highlightNickType));
682     clonedManager.setNicksCaseSensitive(ui.nicksCaseSensitive->isChecked());
683
684     ruleManager->requestUpdate(clonedManager.toVariantMap());
685     setChangedState(false);
686     load();
687 }
688
689
690 int CoreHighlightSettingsPage::nextId()
691 {
692     int max = 0;
693     for (int i = 0; i < highlightList.count(); i++) {
694         int id = highlightList[i].id;
695         if (id > max) {
696             max = id;
697         }
698     }
699     for (int i = 0; i < ignoredList.count(); i++) {
700         int id = ignoredList[i].id;
701         if (id > max) {
702             max = id;
703         }
704     }
705     return max + 1;
706 }
707
708
709 void CoreHighlightSettingsPage::widgetHasChanged()
710 {
711     setChangedState(true);
712 }
713
714
715 void CoreHighlightSettingsPage::on_coreUnsupportedDetails_clicked()
716 {
717     // Re-use translations of "Local Highlights" as this is a word-for-word reference, forcing all
718     // spaces to non-breaking
719     const QString localHighlightsName = tr("Local Highlights").replace(" ", "&nbsp;");
720
721     const QString remoteHighlightsMsgText =
722             QString("<p><b>%1</b></p></br><p>%2</p></br><p>%3</p>"
723                     ).arg(tr("Your Quassel core is too old to support remote highlights"),
724                           tr("You need a Quassel core v0.13.0 or newer to configure remote "
725                              "highlights."),
726                           tr("You can still configure highlights for this device only in "
727                              "<i>%1</i>.").arg(localHighlightsName));
728
729     QMessageBox::warning(this,
730                          tr("Remote Highlights unsupported"),
731                          remoteHighlightsMsgText);
732 }
733
734
735 void CoreHighlightSettingsPage::importRules()
736 {
737     NotificationSettings notificationSettings;
738
739     const auto localHighlightList = notificationSettings.highlightList();
740
741     // Re-use translations of "Legacy/Local Highlights" as this is a word-for-word reference,
742     // forcing all spaces to non-breaking
743     QString localHighlightsName;
744     if (Quassel::runMode() == Quassel::Monolithic) {
745         localHighlightsName = tr("Legacy Highlights").replace(" ", "&nbsp;");
746     } else {
747         localHighlightsName = tr("Local Highlights").replace(" ", "&nbsp;");
748     }
749
750     if (localHighlightList.count() == 0) {
751         // No highlight rules exist to import, do nothing
752         QMessageBox::information(this,
753                                  tr("No highlights to import"),
754                                  tr("No highlight rules in <i>%1</i>."
755                                     ).arg(localHighlightsName));
756         return;
757     }
758
759     int ret = QMessageBox::question(this,
760                                     tr("Import highlights?"),
761                                     tr("Import all highlight rules from <i>%1</i>?"
762                                        ).arg(localHighlightsName),
763                                     QMessageBox::Yes|QMessageBox::No,
764                                     QMessageBox::No);
765
766     if (ret != QMessageBox::Yes) {
767         // Only two options, Yes or No, return if not Yes
768         return;
769     }
770
771     if (hasChanged()) {
772         // Save existing changes first to avoid overwriting them
773         save();
774     }
775
776     auto clonedManager = HighlightRuleManager();
777     clonedManager.fromVariantMap(Client::highlightRuleManager()->toVariantMap());
778
779     for (const auto &variant : notificationSettings.highlightList()) {
780         auto highlightRule = variant.toMap();
781
782         clonedManager.addHighlightRule(
783                 clonedManager.nextId(),
784                 highlightRule["Name"].toString(),
785                 highlightRule["RegEx"].toBool(),
786                 highlightRule["CS"].toBool(),
787                 highlightRule["Enable"].toBool(),
788                 false,
789                 "",
790                 highlightRule["Channel"].toString()
791         );
792     }
793
794     Client::highlightRuleManager()->requestUpdate(clonedManager.toVariantMap());
795     setChangedState(false);
796     load();
797
798     // Give a heads-up that all succeeded
799     QMessageBox::information(this,
800                              tr("Imported highlights"),
801                              tr("%1 highlight rules successfully imported."
802                                 ).arg(QString::number(localHighlightList.count())));
803 }
804
805
806 bool CoreHighlightSettingsPage::isSelectable() const
807 {
808     return Client::isConnected();
809     // We check for Quassel::Feature::CoreSideHighlights when loading this page, allowing us to show
810     // a friendly error message.
811 }