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