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