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