1 /***************************************************************************
2 * Copyright (C) 2005-2018 by the Quassel Project *
3 * devel@quassel-irc.org *
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. *
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. *
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 ***************************************************************************/
21 #include <QHeaderView>
22 #include <QTableWidget>
25 #include "corehighlightsettingspage.h"
28 CoreHighlightSettingsPage::CoreHighlightSettingsPage(QWidget *parent)
29 : SettingsPage(tr("Interface"), tr("Remote Highlights"), parent)
33 setupRuleTable(ui.highlightTable);
34 setupRuleTable(ui.ignoredTable);
36 ui.highlightNicksComboBox->addItem(tr("All Nicks from Identity"), QVariant(HighlightRuleManager::AllNicks));
37 ui.highlightNicksComboBox->addItem(tr("Current Nick"), QVariant(HighlightRuleManager::CurrentNick));
38 ui.highlightNicksComboBox->addItem(tr("None"), QVariant(HighlightRuleManager::NoNick));
40 coreConnectionStateChanged(Client::isConnected()); // need a core connection!
41 connect(Client::instance(), SIGNAL(coreConnectionStateChanged(bool)), this, SLOT(coreConnectionStateChanged(bool)));
43 connect(ui.highlightAdd, SIGNAL(clicked(bool)), this, SLOT(addNewHighlightRow()));
44 connect(ui.highlightRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedHighlightRows()));
45 connect(ui.highlightImport, SIGNAL(clicked(bool)), this, SLOT(importRules()));
47 connect(ui.ignoredAdd, SIGNAL(clicked(bool)), this, SLOT(addNewIgnoredRow()));
48 connect(ui.ignoredRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedIgnoredRows()));
50 // TODO: search for a better signal (one that emits everytime a selection has been changed for one item)
51 connect(ui.highlightTable,
52 SIGNAL(itemClicked(QTableWidgetItem * )),
54 SLOT(selectHighlightRow(QTableWidgetItem * )));
55 connect(ui.ignoredTable,
56 SIGNAL(itemClicked(QTableWidgetItem * )),
58 SLOT(selectIgnoredRow(QTableWidgetItem * )));
60 // Update the "Case sensitive" checkbox
61 connect(ui.highlightNicksComboBox,
62 SIGNAL(currentIndexChanged(int)),
64 SLOT(highlightNicksChanged(int)));
66 connect(ui.highlightNicksComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
67 connect(ui.nicksCaseSensitive, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
69 connect(ui.highlightAdd, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
70 connect(ui.highlightRemove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
72 connect(ui.ignoredAdd, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
73 connect(ui.ignoredRemove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
75 connect(ui.highlightTable,
76 SIGNAL(itemChanged(QTableWidgetItem * )),
78 SLOT(highlightTableChanged(QTableWidgetItem * )));
80 connect(ui.ignoredTable,
81 SIGNAL(itemChanged(QTableWidgetItem * )),
83 SLOT(ignoredTableChanged(QTableWidgetItem * )));
85 connect(Client::instance(), SIGNAL(connected()), this, SLOT(clientConnected()));
88 void CoreHighlightSettingsPage::coreConnectionStateChanged(bool state)
98 void CoreHighlightSettingsPage::setupRuleTable(QTableWidget *table) const
100 table->verticalHeader()->hide();
101 table->setShowGrid(false);
103 table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setToolTip(
104 tr("<b>RegEx</b>: This option determines if the highlight rule should be "
105 "interpreted as a <b>regular expression</b> or just as a keyword."));
106 table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setWhatsThis(
107 table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->toolTip());
109 table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setToolTip(
110 tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
111 "<b>case sensitive</b>."));
112 table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setWhatsThis(
113 table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->toolTip());
115 table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setToolTip(
116 tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
117 "<p><i>Example:</i><br />"
118 "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
119 "would match on #foobar and on any channel starting with <i>#quassel</i> except "
120 "for <i>#quasseldroid</i><br />"
121 "<p>If only inverted names are specified, it will match anything except for "
122 "what's specified (implicit wildcard).</p>"
123 "<p><i>Example:</i><br />"
124 "<i>!#quassel*; !#foobar</i><br />"
125 "would match anything except for #foobar or any channel starting with "
126 "<i>#quassel</i></p>"));
127 table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setWhatsThis(
128 table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->toolTip());
130 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
131 table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
132 table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
133 table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
134 table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
135 table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
137 table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
138 table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
139 table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
140 table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
141 table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
145 void CoreHighlightSettingsPage::clientConnected()
147 connect(Client::highlightRuleManager(), SIGNAL(updated()), SLOT(revert()));
150 void CoreHighlightSettingsPage::revert()
155 setChangedState(false);
159 bool CoreHighlightSettingsPage::hasDefaults() const
164 void CoreHighlightSettingsPage::defaults()
166 int highlightNickType = HighlightRuleManager::HighlightNickType::CurrentNick;
167 int defaultIndex = ui.highlightNicksComboBox->findData(QVariant(highlightNickType));
168 ui.highlightNicksComboBox->setCurrentIndex(defaultIndex);
169 ui.nicksCaseSensitive->setChecked(false);
170 emptyHighlightTable();
176 void CoreHighlightSettingsPage::addNewHighlightRow(bool enable, const QString &name, bool regex, bool cs,
177 const QString &sender, const QString &chanName, bool self)
179 ui.highlightTable->setRowCount(ui.highlightTable->rowCount() + 1);
181 auto *nameItem = new QTableWidgetItem(name);
183 auto *regexItem = new QTableWidgetItem("");
185 regexItem->setCheckState(Qt::Checked);
187 regexItem->setCheckState(Qt::Unchecked);
188 regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
190 auto *csItem = new QTableWidgetItem("");
192 csItem->setCheckState(Qt::Checked);
194 csItem->setCheckState(Qt::Unchecked);
195 csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
197 auto *enableItem = new QTableWidgetItem("");
199 enableItem->setCheckState(Qt::Checked);
201 enableItem->setCheckState(Qt::Unchecked);
202 enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
204 auto *chanNameItem = new QTableWidgetItem(chanName);
206 auto *senderItem = new QTableWidgetItem(sender);
208 enableItem->setToolTip(tr("Enable/disable this rule"));
209 nameItem->setToolTip(tr("Phrase to match"));
210 regexItem->setToolTip(
211 tr("<b>RegEx</b>: This option determines if the highlight rule should be "
212 "interpreted as a <b>regular expression</b> or just as a keyword."));
214 tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
215 "<b>case sensitive</b>."));
216 senderItem->setToolTip(
217 tr("<b>Sender</b>: This option specifies which sender to match. Leave blank to "
218 "match any nickname."));
219 chanNameItem->setToolTip(
220 tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
221 "<p><i>Example:</i><br />"
222 "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
223 "would match on #foobar and on any channel starting with <i>#quassel</i> except "
224 "for <i>#quasseldroid</i><br />"
225 "<p>If only inverted names are specified, it will match anything except for "
226 "what's specified (implicit wildcard).</p>"
227 "<p><i>Example:</i><br />"
228 "<i>!#quassel*; !#foobar</i><br />"
229 "would match anything except for #foobar or any channel starting with "
230 "<i>#quassel</i></p>"));
232 int lastRow = ui.highlightTable->rowCount() - 1;
233 ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
234 ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
235 ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
236 ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
237 ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
238 ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
241 ui.highlightTable->setCurrentItem(nameItem);
243 highlightList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, false, sender, chanName);
246 void CoreHighlightSettingsPage::addNewIgnoredRow(bool enable, const QString &name, bool regex, bool cs,
247 const QString &sender, const QString &chanName, bool self)
249 ui.ignoredTable->setRowCount(ui.ignoredTable->rowCount() + 1);
251 auto *nameItem = new QTableWidgetItem(name);
253 auto *regexItem = new QTableWidgetItem("");
255 regexItem->setCheckState(Qt::Checked);
257 regexItem->setCheckState(Qt::Unchecked);
258 regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
260 auto *csItem = new QTableWidgetItem("");
262 csItem->setCheckState(Qt::Checked);
264 csItem->setCheckState(Qt::Unchecked);
265 csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
267 auto *enableItem = new QTableWidgetItem("");
269 enableItem->setCheckState(Qt::Checked);
271 enableItem->setCheckState(Qt::Unchecked);
272 enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
274 auto *chanNameItem = new QTableWidgetItem(chanName);
276 auto *senderItem = new QTableWidgetItem(sender);
278 enableItem->setToolTip(tr("Enable/disable this rule"));
279 nameItem->setToolTip(tr("Phrase to match"));
280 regexItem->setToolTip(
281 tr("<b>RegEx</b>: This option determines if the highlight rule should be "
282 "interpreted as a <b>regular expression</b> or just as a keyword."));
284 tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
285 "<b>case sensitive</b>."));
286 senderItem->setToolTip(
287 tr("<b>Sender</b>: This option specifies which sender nicknames match. Leave "
288 "blank to match any nickname."));
289 chanNameItem->setToolTip(
290 tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
291 "<p><i>Example:</i><br />"
292 "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
293 "would match on #foobar and on any channel starting with <i>#quassel</i> except "
294 "for <i>#quasseldroid</i><br />"
295 "<p>If only inverted names are specified, it will match anything except for "
296 "what's specified (implicit wildcard).</p>"
297 "<p><i>Example:</i><br />"
298 "<i>!#quassel*; !#foobar</i><br />"
299 "would match anything except for #foobar or any channel starting with "
300 "<i>#quassel</i></p>"));
302 int lastRow = ui.ignoredTable->rowCount() - 1;
303 ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
304 ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
305 ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
306 ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
307 ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
308 ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
311 ui.ignoredTable->setCurrentItem(nameItem);
313 ignoredList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, true, sender, chanName);
316 void CoreHighlightSettingsPage::removeSelectedHighlightRows()
318 QList<int> selectedRows;
319 QList<QTableWidgetItem *> selectedItemList = ui.highlightTable->selectedItems();
320 for (auto selectedItem : selectedItemList) {
321 selectedRows.append(selectedItem->row());
323 qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
325 for (auto row : selectedRows) {
326 if (row != lastRow) {
327 ui.highlightTable->removeRow(row);
328 highlightList.removeAt(row);
334 void CoreHighlightSettingsPage::removeSelectedIgnoredRows()
336 QList<int> selectedRows;
337 QList<QTableWidgetItem *> selectedItemList = ui.ignoredTable->selectedItems();
338 for (auto selectedItem : selectedItemList) {
339 selectedRows.append(selectedItem->row());
341 qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
343 for (auto row : selectedRows) {
344 if (row != lastRow) {
345 ui.ignoredTable->removeRow(row);
346 ignoredList.removeAt(row);
352 void CoreHighlightSettingsPage::highlightNicksChanged(const int index) {
353 // Only allow toggling "Case sensitive" when a nickname will be highlighted
354 auto highlightNickType = ui.highlightNicksComboBox->itemData(index).value<int>();
355 ui.nicksCaseSensitive->setEnabled(highlightNickType != HighlightRuleManager::NoNick);
358 void CoreHighlightSettingsPage::selectHighlightRow(QTableWidgetItem *item)
360 int row = item->row();
361 bool selected = item->isSelected();
363 ->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1),
367 void CoreHighlightSettingsPage::selectIgnoredRow(QTableWidgetItem *item)
369 int row = item->row();
370 bool selected = item->isSelected();
372 ->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1),
376 void CoreHighlightSettingsPage::emptyHighlightTable()
378 // ui.highlight and highlightList should have the same size, but just to make sure.
379 if (ui.highlightTable->rowCount() != highlightList.size()) {
380 qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
382 while (ui.highlightTable->rowCount()) {
383 ui.highlightTable->removeRow(0);
385 highlightList.clear();
388 void CoreHighlightSettingsPage::emptyIgnoredTable()
390 // ui.highlight and highlightList should have the same size, but just to make sure.
391 if (ui.ignoredTable->rowCount() != ignoredList.size()) {
392 qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
394 while (ui.ignoredTable->rowCount()) {
395 ui.ignoredTable->removeRow(0);
400 void CoreHighlightSettingsPage::highlightTableChanged(QTableWidgetItem *item)
402 if (item->row() + 1 > highlightList.size())
405 auto highlightRule = highlightList.value(item->row());
408 switch (item->column()) {
409 case CoreHighlightSettingsPage::EnableColumn:
410 highlightRule.isEnabled = (item->checkState() == Qt::Checked);
412 case CoreHighlightSettingsPage::NameColumn:
413 if (item->text() == "")
414 item->setText(tr("this shouldn't be empty"));
415 highlightRule.name = item->text();
417 case CoreHighlightSettingsPage::RegExColumn:
418 highlightRule.isRegEx = (item->checkState() == Qt::Checked);
420 case CoreHighlightSettingsPage::CsColumn:
421 highlightRule.isCaseSensitive = (item->checkState() == Qt::Checked);
423 case CoreHighlightSettingsPage::SenderColumn:
424 if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
426 highlightRule.sender = item->text();
428 case CoreHighlightSettingsPage::ChanColumn:
429 if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
431 highlightRule.chanName = item->text();
434 highlightList[item->row()] = highlightRule;
435 emit widgetHasChanged();
438 void CoreHighlightSettingsPage::ignoredTableChanged(QTableWidgetItem *item)
440 if (item->row() + 1 > ignoredList.size())
443 auto ignoredRule = ignoredList.value(item->row());
446 switch (item->column()) {
447 case CoreHighlightSettingsPage::EnableColumn:
448 ignoredRule.isEnabled = (item->checkState() == Qt::Checked);
450 case CoreHighlightSettingsPage::NameColumn:
451 if (item->text() == "")
452 item->setText(tr("this shouldn't be empty"));
453 ignoredRule.name = item->text();
455 case CoreHighlightSettingsPage::RegExColumn:
456 ignoredRule.isRegEx = (item->checkState() == Qt::Checked);
458 case CoreHighlightSettingsPage::CsColumn:
459 ignoredRule.isCaseSensitive = (item->checkState() == Qt::Checked);
461 case CoreHighlightSettingsPage::SenderColumn:
462 if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
464 ignoredRule.sender = item->text();
466 case CoreHighlightSettingsPage::ChanColumn:
467 if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
469 ignoredRule.chanName = item->text();
472 ignoredList[item->row()] = ignoredRule;
473 emit widgetHasChanged();
476 void CoreHighlightSettingsPage::load()
478 emptyHighlightTable();
481 auto ruleManager = Client::highlightRuleManager();
483 for (auto &rule : ruleManager->highlightRuleList()) {
484 if (rule.isInverse) {
485 addNewIgnoredRow(rule.isEnabled,
488 rule.isCaseSensitive,
493 addNewHighlightRow(rule.isEnabled, rule.name, rule.isRegEx, rule.isCaseSensitive, rule.sender,
498 int highlightNickType = ruleManager->highlightNick();
499 ui.highlightNicksComboBox->setCurrentIndex(ui.highlightNicksComboBox->findData(QVariant(highlightNickType)));
500 // Trigger the initial update of nicksCaseSensitive being enabled or not
501 highlightNicksChanged(ui.highlightNicksComboBox->currentIndex());
502 ui.nicksCaseSensitive->setChecked(ruleManager->nicksCaseSensitive());
504 setChangedState(false);
511 void CoreHighlightSettingsPage::save()
519 auto ruleManager = Client::highlightRuleManager();
520 if (ruleManager == nullptr)
523 auto clonedManager = HighlightRuleManager();
524 clonedManager.fromVariantMap(ruleManager->toVariantMap());
525 clonedManager.clear();
527 for (auto &rule : highlightList) {
528 clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, false,
529 rule.sender, rule.chanName);
532 for (auto &rule : ignoredList) {
533 clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, true,
534 rule.sender, rule.chanName);
537 auto highlightNickType = ui.highlightNicksComboBox->itemData(ui.highlightNicksComboBox->currentIndex()).value<int>();
539 clonedManager.setHighlightNick(HighlightRuleManager::HighlightNickType(highlightNickType));
540 clonedManager.setNicksCaseSensitive(ui.nicksCaseSensitive->isChecked());
542 ruleManager->requestUpdate(clonedManager.toVariantMap());
543 setChangedState(false);
547 void CoreHighlightSettingsPage::widgetHasChanged()
549 setChangedState(true);
552 void CoreHighlightSettingsPage::importRules() {
553 NotificationSettings notificationSettings;
555 auto clonedManager = HighlightRuleManager();
556 clonedManager.fromVariantMap(Client::highlightRuleManager()->toVariantMap());
558 for (const auto &variant : notificationSettings.highlightList()) {
559 auto highlightRule = variant.toMap();
561 clonedManager.addHighlightRule(
562 highlightRule["Name"].toString(),
563 highlightRule["RegEx"].toBool(),
564 highlightRule["CS"].toBool(),
565 highlightRule["Enable"].toBool(),
568 highlightRule["Channel"].toString()
572 Client::highlightRuleManager()->requestUpdate(clonedManager.toVariantMap());
573 setChangedState(false);
577 bool CoreHighlightSettingsPage::isSelectable() const {
578 return Client::isConnected() && Client::isCoreFeatureEnabled(Quassel::Feature::CoreSideHighlights);