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