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