68d60176c8f766b0d37d0fac733b09f8b32304f4
[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 "qtui.h"
28
29 CoreHighlightSettingsPage::CoreHighlightSettingsPage(QWidget *parent)
30     : SettingsPage(tr("Interface"), tr("Remote Highlights"), parent)
31 {
32     ui.setupUi(this);
33
34     setupRuleTable(ui.highlightTable);
35     setupRuleTable(ui.ignoredTable);
36
37     ui.highlightNicksComboBox->addItem(tr("All Nicks from Identity"), QVariant(HighlightRuleManager::AllNicks));
38     ui.highlightNicksComboBox->addItem(tr("Current Nick"), QVariant(HighlightRuleManager::CurrentNick));
39     ui.highlightNicksComboBox->addItem(tr("None"), QVariant(HighlightRuleManager::NoNick));
40
41     coreConnectionStateChanged(Client::isConnected()); // need a core connection!
42     connect(Client::instance(), SIGNAL(coreConnectionStateChanged(bool)), this, SLOT(coreConnectionStateChanged(bool)));
43
44     connect(ui.highlightAdd, SIGNAL(clicked(bool)), this, SLOT(addNewHighlightRow()));
45     connect(ui.highlightRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedHighlightRows()));
46     connect(ui.highlightImport, SIGNAL(clicked(bool)), this, SLOT(importRules()));
47
48     connect(ui.ignoredAdd, SIGNAL(clicked(bool)), this, SLOT(addNewIgnoredRow()));
49     connect(ui.ignoredRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedIgnoredRows()));
50
51     // TODO: search for a better signal (one that emits everytime a selection has been changed for one item)
52     connect(ui.highlightTable,
53             SIGNAL(itemClicked(QTableWidgetItem * )),
54             this,
55             SLOT(selectHighlightRow(QTableWidgetItem * )));
56     connect(ui.ignoredTable,
57             SIGNAL(itemClicked(QTableWidgetItem * )),
58             this,
59             SLOT(selectIgnoredRow(QTableWidgetItem * )));
60
61     // Update the "Case sensitive" checkbox
62     connect(ui.highlightNicksComboBox,
63             SIGNAL(currentIndexChanged(int)),
64             this,
65             SLOT(highlightNicksChanged(int)));
66
67     connect(ui.highlightNicksComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
68     connect(ui.nicksCaseSensitive, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
69
70     connect(ui.highlightAdd, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
71     connect(ui.highlightRemove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
72
73     connect(ui.ignoredAdd, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
74     connect(ui.ignoredRemove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
75
76     connect(ui.highlightTable,
77             SIGNAL(itemChanged(QTableWidgetItem * )),
78             this,
79             SLOT(highlightTableChanged(QTableWidgetItem * )));
80
81     connect(ui.ignoredTable,
82             SIGNAL(itemChanged(QTableWidgetItem * )),
83             this,
84             SLOT(ignoredTableChanged(QTableWidgetItem * )));
85
86     connect(Client::instance(), SIGNAL(connected()), this, SLOT(clientConnected()));
87
88     // Warning icon
89     ui.coreUnsupportedIcon->setPixmap(QIcon::fromTheme("dialog-warning").pixmap(16));
90 }
91
92 void CoreHighlightSettingsPage::coreConnectionStateChanged(bool state)
93 {
94     updateCoreSupportStatus(state);
95     setEnabled(state);
96     if (state) {
97         load();
98     } else {
99         revert();
100     }
101 }
102
103 void CoreHighlightSettingsPage::setupRuleTable(QTableWidget *table) const
104 {
105     table->verticalHeader()->hide();
106     table->setShowGrid(false);
107
108     table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setToolTip(
109                 tr("<b>RegEx</b>: This option determines if the highlight rule should be "
110                    "interpreted as a <b>regular expression</b> or just as a keyword."));
111     table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setWhatsThis(
112                 table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->toolTip());
113
114     table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setToolTip(
115                 tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
116                    "<b>case sensitive</b>."));
117     table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setWhatsThis(
118                 table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->toolTip());
119
120     table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setToolTip(
121                 tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
122                    "<p><i>Example:</i><br />"
123                    "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
124                    "would match on #foobar and on any channel starting with <i>#quassel</i> except "
125                    "for <i>#quasseldroid</i><br />"
126                    "<p>If only inverted names are specified, it will match anything except for "
127                    "what's specified (implicit wildcard).</p>"
128                    "<p><i>Example:</i><br />"
129                    "<i>!#quassel*; !#foobar</i><br />"
130                    "would match anything except for #foobar or any channel starting with "
131                    "<i>#quassel</i></p>"));
132     table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setWhatsThis(
133                 table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->toolTip());
134
135 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
136     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
137     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
138     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
139     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
140     table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
141 #else
142     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
143     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
144     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
145     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
146     table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
147 #endif
148 }
149
150 void CoreHighlightSettingsPage::updateCoreSupportStatus(bool state)
151 {
152     // Assume connected state as enforced by the settings page UI
153     if (!state || Client::isCoreFeatureEnabled(Quassel::Feature::CoreSideHighlights)) {
154         // Either disconnected or core supports highlights, enable highlight configuration and hide
155         // warning.  Don't show the warning needlessly when disconnected.
156         ui.highlightsConfigWidget->setEnabled(true);
157         ui.coreUnsupportedWidget->setVisible(false);
158     } else {
159         // Core does not support highlights, show warning and disable highlight configuration
160         ui.highlightsConfigWidget->setEnabled(false);
161         ui.coreUnsupportedWidget->setVisible(true);
162     }
163 }
164
165 void CoreHighlightSettingsPage::clientConnected()
166 {
167     connect(Client::highlightRuleManager(), SIGNAL(updated()), SLOT(revert()));
168 }
169
170 void CoreHighlightSettingsPage::revert()
171 {
172     if (!hasChanged())
173         return;
174
175     setChangedState(false);
176     load();
177 }
178
179 bool CoreHighlightSettingsPage::hasDefaults() const
180 {
181     return true;
182 }
183
184 void CoreHighlightSettingsPage::defaults()
185 {
186     int highlightNickType = HighlightRuleManager::HighlightNickType::CurrentNick;
187     int defaultIndex = ui.highlightNicksComboBox->findData(QVariant(highlightNickType));
188     ui.highlightNicksComboBox->setCurrentIndex(defaultIndex);
189     ui.nicksCaseSensitive->setChecked(false);
190     emptyHighlightTable();
191     emptyIgnoredTable();
192
193     widgetHasChanged();
194 }
195
196 void CoreHighlightSettingsPage::addNewHighlightRow(bool enable, const QString &name, bool regex, bool cs,
197                                                    const QString &sender, const QString &chanName, bool self)
198 {
199     ui.highlightTable->setRowCount(ui.highlightTable->rowCount() + 1);
200
201     auto *nameItem = new QTableWidgetItem(name);
202
203     auto *regexItem = new QTableWidgetItem("");
204     if (regex)
205         regexItem->setCheckState(Qt::Checked);
206     else
207         regexItem->setCheckState(Qt::Unchecked);
208     regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
209
210     auto *csItem = new QTableWidgetItem("");
211     if (cs)
212         csItem->setCheckState(Qt::Checked);
213     else
214         csItem->setCheckState(Qt::Unchecked);
215     csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
216
217     auto *enableItem = new QTableWidgetItem("");
218     if (enable)
219         enableItem->setCheckState(Qt::Checked);
220     else
221         enableItem->setCheckState(Qt::Unchecked);
222     enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
223
224     auto *chanNameItem = new QTableWidgetItem(chanName);
225
226     auto *senderItem = new QTableWidgetItem(sender);
227
228     enableItem->setToolTip(tr("Enable/disable this rule"));
229     nameItem->setToolTip(tr("Phrase to match"));
230     regexItem->setToolTip(
231                 tr("<b>RegEx</b>: This option determines if the highlight rule should be "
232                    "interpreted as a <b>regular expression</b> or just as a keyword."));
233     csItem->setToolTip(
234                 tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
235                    "<b>case sensitive</b>."));
236     senderItem->setToolTip(
237                 tr("<b>Sender</b>: This option specifies which sender to match.  Leave blank to "
238                    "match any nickname."));
239     chanNameItem->setToolTip(
240                 tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
241                    "<p><i>Example:</i><br />"
242                    "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
243                    "would match on #foobar and on any channel starting with <i>#quassel</i> except "
244                    "for <i>#quasseldroid</i><br />"
245                    "<p>If only inverted names are specified, it will match anything except for "
246                    "what's specified (implicit wildcard).</p>"
247                    "<p><i>Example:</i><br />"
248                    "<i>!#quassel*; !#foobar</i><br />"
249                    "would match anything except for #foobar or any channel starting with "
250                    "<i>#quassel</i></p>"));
251
252     int lastRow = ui.highlightTable->rowCount() - 1;
253     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
254     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
255     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
256     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
257     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
258     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
259
260     if (!self)
261         ui.highlightTable->setCurrentItem(nameItem);
262
263     highlightList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, false, sender, chanName);
264 }
265
266 void CoreHighlightSettingsPage::addNewIgnoredRow(bool enable, const QString &name, bool regex, bool cs,
267                                                  const QString &sender, const QString &chanName, bool self)
268 {
269     ui.ignoredTable->setRowCount(ui.ignoredTable->rowCount() + 1);
270
271     auto *nameItem = new QTableWidgetItem(name);
272
273     auto *regexItem = new QTableWidgetItem("");
274     if (regex)
275         regexItem->setCheckState(Qt::Checked);
276     else
277         regexItem->setCheckState(Qt::Unchecked);
278     regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
279
280     auto *csItem = new QTableWidgetItem("");
281     if (cs)
282         csItem->setCheckState(Qt::Checked);
283     else
284         csItem->setCheckState(Qt::Unchecked);
285     csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
286
287     auto *enableItem = new QTableWidgetItem("");
288     if (enable)
289         enableItem->setCheckState(Qt::Checked);
290     else
291         enableItem->setCheckState(Qt::Unchecked);
292     enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
293
294     auto *chanNameItem = new QTableWidgetItem(chanName);
295
296     auto *senderItem = new QTableWidgetItem(sender);
297
298     enableItem->setToolTip(tr("Enable/disable this rule"));
299     nameItem->setToolTip(tr("Phrase to match"));
300     regexItem->setToolTip(
301                 tr("<b>RegEx</b>: This option determines if the highlight rule should be "
302                    "interpreted as a <b>regular expression</b> or just as a keyword."));
303     csItem->setToolTip(
304                 tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
305                    "<b>case sensitive</b>."));
306     senderItem->setToolTip(
307                 tr("<b>Sender</b>: This option specifies which sender nicknames match.  Leave "
308                    "blank to match any nickname."));
309     chanNameItem->setToolTip(
310                 tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
311                    "<p><i>Example:</i><br />"
312                    "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
313                    "would match on #foobar and on any channel starting with <i>#quassel</i> except "
314                    "for <i>#quasseldroid</i><br />"
315                    "<p>If only inverted names are specified, it will match anything except for "
316                    "what's specified (implicit wildcard).</p>"
317                    "<p><i>Example:</i><br />"
318                    "<i>!#quassel*; !#foobar</i><br />"
319                    "would match anything except for #foobar or any channel starting with "
320                    "<i>#quassel</i></p>"));
321
322     int lastRow = ui.ignoredTable->rowCount() - 1;
323     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
324     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
325     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
326     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
327     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
328     ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
329
330     if (!self)
331         ui.ignoredTable->setCurrentItem(nameItem);
332
333     ignoredList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, true, sender, chanName);
334 }
335
336 void CoreHighlightSettingsPage::removeSelectedHighlightRows()
337 {
338     QList<int> selectedRows;
339     QList<QTableWidgetItem *> selectedItemList = ui.highlightTable->selectedItems();
340     for (auto selectedItem : selectedItemList) {
341         selectedRows.append(selectedItem->row());
342     }
343     qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
344     int lastRow = -1;
345     for (auto row : selectedRows) {
346         if (row != lastRow) {
347             ui.highlightTable->removeRow(row);
348             highlightList.removeAt(row);
349         }
350         lastRow = row;
351     }
352 }
353
354 void CoreHighlightSettingsPage::removeSelectedIgnoredRows()
355 {
356     QList<int> selectedRows;
357     QList<QTableWidgetItem *> selectedItemList = ui.ignoredTable->selectedItems();
358     for (auto selectedItem : selectedItemList) {
359         selectedRows.append(selectedItem->row());
360     }
361     qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
362     int lastRow = -1;
363     for (auto row : selectedRows) {
364         if (row != lastRow) {
365             ui.ignoredTable->removeRow(row);
366             ignoredList.removeAt(row);
367         }
368         lastRow = row;
369     }
370 }
371
372 void CoreHighlightSettingsPage::highlightNicksChanged(const int index) {
373     // Only allow toggling "Case sensitive" when a nickname will be highlighted
374     auto highlightNickType = ui.highlightNicksComboBox->itemData(index).value<int>();
375     ui.nicksCaseSensitive->setEnabled(highlightNickType != HighlightRuleManager::NoNick);
376 }
377
378 void CoreHighlightSettingsPage::selectHighlightRow(QTableWidgetItem *item)
379 {
380     int row = item->row();
381     bool selected = item->isSelected();
382     ui.highlightTable
383         ->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1),
384                            selected);
385 }
386
387 void CoreHighlightSettingsPage::selectIgnoredRow(QTableWidgetItem *item)
388 {
389     int row = item->row();
390     bool selected = item->isSelected();
391     ui.ignoredTable
392         ->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1),
393                            selected);
394 }
395
396 void CoreHighlightSettingsPage::emptyHighlightTable()
397 {
398     // ui.highlight and highlightList should have the same size, but just to make sure.
399     if (ui.highlightTable->rowCount() != highlightList.size()) {
400         qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
401     }
402     while (ui.highlightTable->rowCount()) {
403         ui.highlightTable->removeRow(0);
404     }
405     highlightList.clear();
406 }
407
408 void CoreHighlightSettingsPage::emptyIgnoredTable()
409 {
410     // ui.highlight and highlightList should have the same size, but just to make sure.
411     if (ui.ignoredTable->rowCount() != ignoredList.size()) {
412         qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
413     }
414     while (ui.ignoredTable->rowCount()) {
415         ui.ignoredTable->removeRow(0);
416     }
417     ignoredList.clear();
418 }
419
420 void CoreHighlightSettingsPage::highlightTableChanged(QTableWidgetItem *item)
421 {
422     if (item->row() + 1 > highlightList.size())
423         return;
424
425     auto highlightRule = highlightList.value(item->row());
426
427
428     switch (item->column()) {
429         case CoreHighlightSettingsPage::EnableColumn:
430             highlightRule.isEnabled = (item->checkState() == Qt::Checked);
431             break;
432         case CoreHighlightSettingsPage::NameColumn:
433             if (item->text() == "")
434                 item->setText(tr("this shouldn't be empty"));
435             highlightRule.name = item->text();
436             break;
437         case CoreHighlightSettingsPage::RegExColumn:
438             highlightRule.isRegEx = (item->checkState() == Qt::Checked);
439             break;
440         case CoreHighlightSettingsPage::CsColumn:
441             highlightRule.isCaseSensitive = (item->checkState() == Qt::Checked);
442             break;
443         case CoreHighlightSettingsPage::SenderColumn:
444             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
445                 item->setText("");
446             highlightRule.sender = item->text();
447             break;
448         case CoreHighlightSettingsPage::ChanColumn:
449             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
450                 item->setText("");
451             highlightRule.chanName = item->text();
452             break;
453     }
454     highlightList[item->row()] = highlightRule;
455     emit widgetHasChanged();
456 }
457
458 void CoreHighlightSettingsPage::ignoredTableChanged(QTableWidgetItem *item)
459 {
460     if (item->row() + 1 > ignoredList.size())
461         return;
462
463     auto ignoredRule = ignoredList.value(item->row());
464
465
466     switch (item->column()) {
467         case CoreHighlightSettingsPage::EnableColumn:
468             ignoredRule.isEnabled = (item->checkState() == Qt::Checked);
469             break;
470         case CoreHighlightSettingsPage::NameColumn:
471             if (item->text() == "")
472                 item->setText(tr("this shouldn't be empty"));
473             ignoredRule.name = item->text();
474             break;
475         case CoreHighlightSettingsPage::RegExColumn:
476             ignoredRule.isRegEx = (item->checkState() == Qt::Checked);
477             break;
478         case CoreHighlightSettingsPage::CsColumn:
479             ignoredRule.isCaseSensitive = (item->checkState() == Qt::Checked);
480             break;
481         case CoreHighlightSettingsPage::SenderColumn:
482             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
483                 item->setText("");
484             ignoredRule.sender = item->text();
485             break;
486         case CoreHighlightSettingsPage::ChanColumn:
487             if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
488                 item->setText("");
489             ignoredRule.chanName = item->text();
490             break;
491     }
492     ignoredList[item->row()] = ignoredRule;
493     emit widgetHasChanged();
494 }
495
496 void CoreHighlightSettingsPage::load()
497 {
498     emptyHighlightTable();
499     emptyIgnoredTable();
500
501     auto ruleManager = Client::highlightRuleManager();
502     if (ruleManager) {
503         for (auto &rule : ruleManager->highlightRuleList()) {
504             if (rule.isInverse) {
505                 addNewIgnoredRow(rule.isEnabled,
506                                  rule.name,
507                                  rule.isRegEx,
508                                  rule.isCaseSensitive,
509                                  rule.sender,
510                                  rule.chanName);
511             }
512             else {
513                 addNewHighlightRow(rule.isEnabled, rule.name, rule.isRegEx, rule.isCaseSensitive, rule.sender,
514                                    rule.chanName);
515             }
516         }
517
518         int highlightNickType = ruleManager->highlightNick();
519         ui.highlightNicksComboBox->setCurrentIndex(ui.highlightNicksComboBox->findData(QVariant(highlightNickType)));
520         // Trigger the initial update of nicksCaseSensitive being enabled or not
521         highlightNicksChanged(ui.highlightNicksComboBox->currentIndex());
522         ui.nicksCaseSensitive->setChecked(ruleManager->nicksCaseSensitive());
523
524         setChangedState(false);
525         _initialized = true;
526     } else {
527         defaults();
528     }
529 }
530
531 void CoreHighlightSettingsPage::save()
532 {
533     if (!hasChanged())
534         return;
535
536     if (!_initialized)
537         return;
538
539     auto ruleManager = Client::highlightRuleManager();
540     if (ruleManager == nullptr)
541         return;
542
543     auto clonedManager = HighlightRuleManager();
544     clonedManager.fromVariantMap(ruleManager->toVariantMap());
545     clonedManager.clear();
546
547     for (auto &rule : highlightList) {
548         clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, false,
549                                        rule.sender, rule.chanName);
550     }
551
552     for (auto &rule : ignoredList) {
553         clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, true,
554                                        rule.sender, rule.chanName);
555     }
556
557     auto highlightNickType = ui.highlightNicksComboBox->itemData(ui.highlightNicksComboBox->currentIndex()).value<int>();
558
559     clonedManager.setHighlightNick(HighlightRuleManager::HighlightNickType(highlightNickType));
560     clonedManager.setNicksCaseSensitive(ui.nicksCaseSensitive->isChecked());
561
562     ruleManager->requestUpdate(clonedManager.toVariantMap());
563     setChangedState(false);
564     load();
565 }
566
567 void CoreHighlightSettingsPage::widgetHasChanged()
568 {
569     setChangedState(true);
570 }
571
572 void CoreHighlightSettingsPage::on_coreUnsupportedDetails_clicked()
573 {
574     // Re-use translations of "Local Highlights" as this is a word-for-word reference, forcing all
575     // spaces to non-breaking
576     const QString localHighlightsName = tr("Local Highlights").replace(" ", "&nbsp;");
577
578     const QString remoteHighlightsMsgText =
579             QString("<p><b>%1</b></p></br><p>%2</p></br><p>%3</p>"
580                     ).arg(tr("Your Quassel core is too old to support remote highlights"),
581                           tr("You need a Quassel core v0.13.0 or newer to configure remote "
582                              "highlights."),
583                           tr("You can still configure highlights for this device only in "
584                              "<i>%1</i>.").arg(localHighlightsName));
585
586     QMessageBox::warning(this,
587                          tr("Remote Highlights unsupported"),
588                          remoteHighlightsMsgText);
589 }
590
591 void CoreHighlightSettingsPage::importRules() {
592     NotificationSettings notificationSettings;
593
594     const auto localHighlightList = notificationSettings.highlightList();
595
596     // Re-use translations of "Local Highlights" as this is a word-for-word reference, forcing all
597     // spaces to non-breaking
598     const QString localHighlightsName = tr("Local Highlights").replace(" ", "&nbsp;");
599
600     if (localHighlightList.count() == 0) {
601         // No highlight rules exist to import, do nothing
602         QMessageBox::information(this,
603                                  tr("No local highlights"),
604                                  tr("No highlight rules in <i>%1</i>."
605                                     ).arg(localHighlightsName));
606         return;
607     }
608
609     int ret = QMessageBox::question(this,
610                                     tr("Import local highlights?"),
611                                     tr("Import all highlight rules from <i>%1</i>?"
612                                        ).arg(localHighlightsName),
613                                     QMessageBox::Yes|QMessageBox::No,
614                                     QMessageBox::No);
615
616     if (ret == QMessageBox::No) {
617         // Only two options, Yes or No, just return if No
618         return;
619     }
620
621     auto clonedManager = HighlightRuleManager();
622     clonedManager.fromVariantMap(Client::highlightRuleManager()->toVariantMap());
623
624     for (const auto &variant : notificationSettings.highlightList()) {
625         auto highlightRule = variant.toMap();
626
627         clonedManager.addHighlightRule(
628                 highlightRule["Name"].toString(),
629                 highlightRule["RegEx"].toBool(),
630                 highlightRule["CS"].toBool(),
631                 highlightRule["Enable"].toBool(),
632                 false,
633                 "",
634                 highlightRule["Channel"].toString()
635         );
636     }
637
638     Client::highlightRuleManager()->requestUpdate(clonedManager.toVariantMap());
639     setChangedState(false);
640     load();
641
642     // Give a heads-up that all succeeded
643     QMessageBox::information(this,
644                              tr("Imported local highlights"),
645                              tr("%1 highlight rules successfully imported."
646                                 ).arg(QString::number(localHighlightList.count())));
647 }
648
649 bool CoreHighlightSettingsPage::isSelectable() const {
650     return Client::isConnected();
651     // We check for Quassel::Feature::CoreSideHighlights when loading this page, allowing us to show
652     // a friendly error message.
653 }