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