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