/***************************************************************************
- * Copyright (C) 2005-2016 by the Quassel Project *
+ * Copyright (C) 2005-2018 by the Quassel Project *
* devel@quassel-irc.org *
* *
* This program is free software; you can redistribute it and/or modify *
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
***************************************************************************/
+#include <QHeaderView>
+#include <QMessageBox>
#include <QTableWidget>
-#include <QtWidgets/QHeaderView>
+#include "client.h"
#include "corehighlightsettingspage.h"
+#include "icon.h"
#include "qtui.h"
-#include "client.h"
CoreHighlightSettingsPage::CoreHighlightSettingsPage(QWidget *parent)
- : SettingsPage(tr("Interface"), tr("Core-Side Highlights"), parent)
+ : SettingsPage(tr("Interface"),
+ // In Monolithic mode, local highlights are replaced by remote highlights
+ Quassel::runMode() == Quassel::Monolithic ?
+ tr("Highlights") : tr("Remote Highlights"),
+ parent)
{
ui.setupUi(this);
setupRuleTable(ui.highlightTable);
setupRuleTable(ui.ignoredTable);
- ui.highlightNicksComboBox->addItem(tr("All Nicks from Identity"), HighlightRuleManager::AllNicks);
- ui.highlightNicksComboBox->addItem(tr("Current Nick"), HighlightRuleManager::CurrentNick);
- ui.highlightNicksComboBox->addItem(tr("None"), HighlightRuleManager::NoNick);
+ ui.highlightNicksComboBox->addItem(tr("All Nicks from Identity"), QVariant(HighlightRuleManager::AllNicks));
+ ui.highlightNicksComboBox->addItem(tr("Current Nick"), QVariant(HighlightRuleManager::CurrentNick));
+ ui.highlightNicksComboBox->addItem(tr("None"), QVariant(HighlightRuleManager::NoNick));
+
+ coreConnectionStateChanged(Client::isConnected()); // need a core connection!
+ connect(Client::instance(), SIGNAL(coreConnectionStateChanged(bool)), this, SLOT(coreConnectionStateChanged(bool)));
connect(ui.highlightAdd, SIGNAL(clicked(bool)), this, SLOT(addNewHighlightRow()));
connect(ui.highlightRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedHighlightRows()));
+ connect(ui.highlightImport, SIGNAL(clicked(bool)), this, SLOT(importRules()));
connect(ui.ignoredAdd, SIGNAL(clicked(bool)), this, SLOT(addNewIgnoredRow()));
connect(ui.ignoredRemove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedIgnoredRows()));
this,
SLOT(selectIgnoredRow(QTableWidgetItem * )));
+ // Update the "Case sensitive" checkbox
+ connect(ui.highlightNicksComboBox,
+ SIGNAL(currentIndexChanged(int)),
+ this,
+ SLOT(highlightNicksChanged(int)));
connect(ui.highlightNicksComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(widgetHasChanged()));
connect(ui.nicksCaseSensitive, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
SLOT(ignoredTableChanged(QTableWidgetItem * )));
connect(Client::instance(), SIGNAL(connected()), this, SLOT(clientConnected()));
+
+ // Warning icon
+ ui.coreUnsupportedIcon->setPixmap(icon::get("dialog-warning").pixmap(16));
+
+ // Set up client/monolithic remote highlights information
+ if (Quassel::runMode() == Quassel::Monolithic) {
+ // We're running in Monolithic mode, local highlights are considered legacy
+ ui.highlightImport->setText(tr("Import Legacy"));
+ ui.highlightImport->setToolTip(tr("Import highlight rules configured in <i>%1</i>.")
+ .arg(tr("Legacy Highlights").replace(" ", " ")));
+ // Re-use translations of "Legacy Highlights" as this is a word-for-word reference, forcing
+ // all spaces to be non-breaking
+ } else {
+ // We're running in client/split mode, local highlights are distinguished from remote
+ ui.highlightImport->setText(tr("Import Local"));
+ ui.highlightImport->setToolTip(tr("Import highlight rules configured in <i>%1</i>.")
+ .arg(tr("Local Highlights").replace(" ", " ")));
+ // Re-use translations of "Local Highlights" as this is a word-for-word reference, forcing
+ // all spaces to be non-breaking
+ }
}
+
+void CoreHighlightSettingsPage::coreConnectionStateChanged(bool state)
+{
+ updateCoreSupportStatus(state);
+ setEnabled(state);
+ if (state) {
+ load();
+ } else {
+ revert();
+ }
+}
+
void CoreHighlightSettingsPage::setupRuleTable(QTableWidget *table) const
{
table->verticalHeader()->hide();
table->setShowGrid(false);
- table->horizontalHeaderItem(RegExColumn)->setToolTip(
- tr("<b>RegEx</b>: This option determines if the highlight rule should be interpreted as a <b>regular expression</b> or just as a keyword."));
- table->horizontalHeaderItem(RegExColumn)->setWhatsThis(
- tr("<b>RegEx</b>: This option determines if the highlight rule should be interpreted as a <b>regular expression</b> or just as a keyword."));
-
- table->horizontalHeaderItem(CsColumn)->setToolTip(
- tr("<b>CS</b>: This option determines if the highlight rule should be interpreted <b>case sensitive</b>."));
- table->horizontalHeaderItem(CsColumn)->setWhatsThis(
- tr("<b>CS</b>: This option determines if the highlight rule should be interpreted <b>case sensitive</b>."));
-
- table->horizontalHeaderItem(ChanColumn)->setToolTip(
- tr("<b>Channel</b>: This regular expression determines for which <b>channels</b> the highlight rule works. Leave blank to match any channel. Put <b>!</b> in the beginning to negate. Case insensitive."));
- table->horizontalHeaderItem(ChanColumn)->setWhatsThis(
- tr("<b>Channel</b>: This regular expression determines for which <b>channels</b> the highlight rule works. Leave blank to match any channel. Put <b>!</b> in the beginning to negate. Case insensitive."));
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::EnableColumn)->setToolTip(
+ tr("Enable/disable this rule"));
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::EnableColumn)->setWhatsThis(
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::EnableColumn)->toolTip());
+
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::NameColumn)->setToolTip(
+ tr("Phrase to match"));
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::NameColumn)->setWhatsThis(
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::NameColumn)->toolTip());
+
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setToolTip(
+ tr("<b>RegEx</b>: This option determines if the highlight rule, <i>Sender</i>, and "
+ "<i>Channel</i> should be interpreted as <b>regular expressions</b> or just as "
+ "keywords."));
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setWhatsThis(
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->toolTip());
+
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setToolTip(
+ tr("<b>CS</b>: This option determines if the highlight rule, <i>Sender</i>, and "
+ "<i>Channel</i> should be interpreted <b>case sensitive</b>."));
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setWhatsThis(
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->toolTip());
+
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::SenderColumn)->setToolTip(
+ tr("<p><b>Sender</b>: Semicolon separated list of <i>nick!ident@host</i> names, "
+ "leave blank to match any nickname.</p>"
+ "<p><i>Example:</i><br />"
+ "<i>Alice!*; Bob!*@example.com; Carol*!*; !Caroline!*</i><br />"
+ "would match on <i>Alice</i>, <i>Bob</i> with hostmask <i>example.com</i>, and "
+ "any nickname starting with <i>Carol</i> except for <i>Caroline</i><br />"
+ "<p>If only inverted names are specified, it will match anything except for "
+ "what's specified (implicit wildcard).</p>"
+ "<p><i>Example:</i><br />"
+ "<i>!Announce*!*; !Wheatley!aperture@*</i><br />"
+ "would match anything except for <i>Wheatley</i> with ident <i>aperture</i> or "
+ "any nickname starting with <i>Announce</i></p>"));
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::SenderColumn)->setWhatsThis(
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::SenderColumn)->toolTip());
+
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setToolTip(
+ tr("<p><b>Channel</b>: Semicolon separated list of channel names, leave blank to "
+ "match any name.</p>"
+ "<p><i>Example:</i><br />"
+ "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
+ "would match on <i>#foobar</i> and any channel starting with <i>#quassel</i> "
+ "except for <i>#quasseldroid</i><br />"
+ "<p>If only inverted names are specified, it will match anything except for "
+ "what's specified (implicit wildcard).</p>"
+ "<p><i>Example:</i><br />"
+ "<i>!#quassel*; !#foobar</i><br />"
+ "would match anything except for <i>#foobar</i> or any channel starting with "
+ "<i>#quassel</i></p>"));
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setWhatsThis(
+ table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->toolTip());
#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
table->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
#else
- table->horizontalHeader()->setSectionResizeMode(EnableColumn, QHeaderView::ResizeToContents);
- table->horizontalHeader()->setSectionResizeMode(NameColumn, QHeaderView::Stretch);
- table->horizontalHeader()->setSectionResizeMode(RegExColumn, QHeaderView::ResizeToContents);
- table->horizontalHeader()->setSectionResizeMode(CsColumn, QHeaderView::ResizeToContents);
- table->horizontalHeader()->setSectionResizeMode(ChanColumn, QHeaderView::ResizeToContents);
+ table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
+ table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
+ table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
+ table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
+ table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
#endif
}
+void CoreHighlightSettingsPage::updateCoreSupportStatus(bool state)
+{
+ // Assume connected state as enforced by the settings page UI
+ if (!state || Client::isCoreFeatureEnabled(Quassel::Feature::CoreSideHighlights)) {
+ // Either disconnected or core supports highlights, enable highlight configuration and hide
+ // warning. Don't show the warning needlessly when disconnected.
+ ui.highlightsConfigWidget->setEnabled(true);
+ ui.coreUnsupportedWidget->setVisible(false);
+ } else {
+ // Core does not support highlights, show warning and disable highlight configuration
+ ui.highlightsConfigWidget->setEnabled(false);
+ ui.coreUnsupportedWidget->setVisible(true);
+ }
+}
+
void CoreHighlightSettingsPage::clientConnected()
{
connect(Client::highlightRuleManager(), SIGNAL(updated()), SLOT(revert()));
void CoreHighlightSettingsPage::defaults()
{
- ui.highlightNicksComboBox->setCurrentIndex(ui.highlightNicksComboBox
- ->findData(QVariant(HighlightRuleManager::HighlightNickType::CurrentNick)));
+ int highlightNickType = HighlightRuleManager::HighlightNickType::CurrentNick;
+ int defaultIndex = ui.highlightNicksComboBox->findData(QVariant(highlightNickType));
+ ui.highlightNicksComboBox->setCurrentIndex(defaultIndex);
ui.nicksCaseSensitive->setChecked(false);
emptyHighlightTable();
emptyIgnoredTable();
enableItem->setCheckState(Qt::Unchecked);
enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+ auto *senderItem = new QTableWidgetItem(sender);
+
auto *chanNameItem = new QTableWidgetItem(chanName);
- auto *senderItem = new QTableWidgetItem(sender);
+ enableItem->setToolTip(tr("Enable/disable this rule"));
+ nameItem->setToolTip(tr("Phrase to match"));
+ regexItem->setToolTip(
+ tr("<b>RegEx</b>: This option determines if the highlight rule, <i>Sender</i>, and "
+ "<i>Channel</i> should be interpreted as <b>regular expressions</b> or just as "
+ "keywords."));
+ csItem->setToolTip(
+ tr("<b>CS</b>: This option determines if the highlight rule, <i>Sender</i>, and "
+ "<i>Channel</i> should be interpreted <b>case sensitive</b>."));
+ senderItem->setToolTip(
+ tr("<p><b>Sender</b>: Semicolon separated list of <i>nick!ident@host</i> names, "
+ "leave blank to match any nickname.</p>"
+ "<p><i>Example:</i><br />"
+ "<i>Alice!*; Bob!*@example.com; Carol*!*; !Caroline!*</i><br />"
+ "would match on <i>Alice</i>, <i>Bob</i> with hostmask <i>example.com</i>, and "
+ "any nickname starting with <i>Carol</i> except for <i>Caroline</i><br />"
+ "<p>If only inverted names are specified, it will match anything except for "
+ "what's specified (implicit wildcard).</p>"
+ "<p><i>Example:</i><br />"
+ "<i>!Announce*!*; !Wheatley!aperture@*</i><br />"
+ "would match anything except for <i>Wheatley</i> with ident <i>aperture</i> or "
+ "any nickname starting with <i>Announce</i></p>"));
+ chanNameItem->setToolTip(
+ tr("<p><b>Channel</b>: Semicolon separated list of channel names, leave blank to "
+ "match any name.</p>"
+ "<p><i>Example:</i><br />"
+ "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
+ "would match on <i>#foobar</i> and any channel starting with <i>#quassel</i> "
+ "except for <i>#quasseldroid</i><br />"
+ "<p>If only inverted names are specified, it will match anything except for "
+ "what's specified (implicit wildcard).</p>"
+ "<p><i>Example:</i><br />"
+ "<i>!#quassel*; !#foobar</i><br />"
+ "would match anything except for <i>#foobar</i> or any channel starting with "
+ "<i>#quassel</i></p>"));
int lastRow = ui.highlightTable->rowCount() - 1;
ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
auto *senderItem = new QTableWidgetItem(sender);
+ enableItem->setToolTip(tr("Enable/disable this rule"));
+ nameItem->setToolTip(tr("Phrase to match"));
+ regexItem->setToolTip(
+ tr("<b>RegEx</b>: This option determines if the highlight rule should be "
+ "interpreted as a <b>regular expression</b> or just as a keyword."));
+ csItem->setToolTip(
+ tr("<b>CS</b>: This option determines if the highlight rule should be interpreted "
+ "<b>case sensitive</b>."));
+ senderItem->setToolTip(
+ tr("<b>Sender</b>: This option specifies which sender nicknames match. Leave "
+ "blank to match any nickname."));
+ chanNameItem->setToolTip(
+ tr("<p><b>Channel</b>: Semicolon separated list of channel names.</p>"
+ "<p><i>Example:</i><br />"
+ "<i>#quassel*; #foobar; !#quasseldroid</i><br />"
+ "would match on #foobar and any channel starting with <i>#quassel</i> except "
+ "for <i>#quasseldroid</i><br />"
+ "<p>If only inverted names are specified, it will match anything except for "
+ "what's specified (implicit wildcard).</p>"
+ "<p><i>Example:</i><br />"
+ "<i>!#quassel*; !#foobar</i><br />"
+ "would match anything except for #foobar or any channel starting with "
+ "<i>#quassel</i></p>"));
+
int lastRow = ui.ignoredTable->rowCount() - 1;
ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
}
}
+void CoreHighlightSettingsPage::highlightNicksChanged(const int index) {
+ // Only allow toggling "Case sensitive" when a nickname will be highlighted
+ auto highlightNickType = ui.highlightNicksComboBox->itemData(index).value<int>();
+ ui.nicksCaseSensitive->setEnabled(highlightNickType != HighlightRuleManager::NoNick);
+}
+
void CoreHighlightSettingsPage::selectHighlightRow(QTableWidgetItem *item)
{
int row = item->row();
if (ui.highlightTable->rowCount() != highlightList.size()) {
qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
}
- ui.highlightTable->clear();
+ while (ui.highlightTable->rowCount()) {
+ ui.highlightTable->removeRow(0);
+ }
highlightList.clear();
}
if (ui.ignoredTable->rowCount() != ignoredList.size()) {
qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
}
- ui.ignoredTable->clear();
+ while (ui.ignoredTable->rowCount()) {
+ ui.ignoredTable->removeRow(0);
+ }
ignoredList.clear();
}
switch (item->column()) {
case CoreHighlightSettingsPage::EnableColumn:
highlightRule.isEnabled = (item->checkState() == Qt::Checked);
+ break;
case CoreHighlightSettingsPage::NameColumn:
if (item->text() == "")
item->setText(tr("this shouldn't be empty"));
switch (item->column()) {
case CoreHighlightSettingsPage::EnableColumn:
ignoredRule.isEnabled = (item->checkState() == Qt::Checked);
+ break;
case CoreHighlightSettingsPage::NameColumn:
if (item->text() == "")
item->setText(tr("this shouldn't be empty"));
}
}
- ui.highlightNicksComboBox
- ->setCurrentIndex(ui.highlightNicksComboBox->findData(QVariant(ruleManager->highlightNick())));
+ int highlightNickType = ruleManager->highlightNick();
+ ui.highlightNicksComboBox->setCurrentIndex(ui.highlightNicksComboBox->findData(QVariant(highlightNickType)));
+ // Trigger the initial update of nicksCaseSensitive being enabled or not
+ highlightNicksChanged(ui.highlightNicksComboBox->currentIndex());
ui.nicksCaseSensitive->setChecked(ruleManager->nicksCaseSensitive());
setChangedState(false);
_initialized = true;
+ } else {
+ defaults();
}
}
rule.sender, rule.chanName);
}
- auto highlightNickType = ui.highlightNicksComboBox->currentData().value<HighlightRuleManager::HighlightNickType>();
+ auto highlightNickType = ui.highlightNicksComboBox->itemData(ui.highlightNicksComboBox->currentIndex()).value<int>();
- clonedManager.setHighlightNick(highlightNickType);
+ clonedManager.setHighlightNick(HighlightRuleManager::HighlightNickType(highlightNickType));
clonedManager.setNicksCaseSensitive(ui.nicksCaseSensitive->isChecked());
ruleManager->requestUpdate(clonedManager.toVariantMap());
void CoreHighlightSettingsPage::widgetHasChanged()
{
setChangedState(true);
-}
\ No newline at end of file
+}
+
+void CoreHighlightSettingsPage::on_coreUnsupportedDetails_clicked()
+{
+ // Re-use translations of "Local Highlights" as this is a word-for-word reference, forcing all
+ // spaces to non-breaking
+ const QString localHighlightsName = tr("Local Highlights").replace(" ", " ");
+
+ const QString remoteHighlightsMsgText =
+ QString("<p><b>%1</b></p></br><p>%2</p></br><p>%3</p>"
+ ).arg(tr("Your Quassel core is too old to support remote highlights"),
+ tr("You need a Quassel core v0.13.0 or newer to configure remote "
+ "highlights."),
+ tr("You can still configure highlights for this device only in "
+ "<i>%1</i>.").arg(localHighlightsName));
+
+ QMessageBox::warning(this,
+ tr("Remote Highlights unsupported"),
+ remoteHighlightsMsgText);
+}
+
+void CoreHighlightSettingsPage::importRules() {
+ NotificationSettings notificationSettings;
+
+ const auto localHighlightList = notificationSettings.highlightList();
+
+ // Re-use translations of "Legacy/Local Highlights" as this is a word-for-word reference,
+ // forcing all spaces to non-breaking
+ QString localHighlightsName;
+ if (Quassel::runMode() == Quassel::Monolithic) {
+ localHighlightsName = tr("Legacy Highlights").replace(" ", " ");
+ } else {
+ localHighlightsName = tr("Local Highlights").replace(" ", " ");
+ }
+
+ if (localHighlightList.count() == 0) {
+ // No highlight rules exist to import, do nothing
+ QMessageBox::information(this,
+ tr("No highlights to import"),
+ tr("No highlight rules in <i>%1</i>."
+ ).arg(localHighlightsName));
+ return;
+ }
+
+ int ret = QMessageBox::question(this,
+ tr("Import highlights?"),
+ tr("Import all highlight rules from <i>%1</i>?"
+ ).arg(localHighlightsName),
+ QMessageBox::Yes|QMessageBox::No,
+ QMessageBox::No);
+
+ if (ret != QMessageBox::Yes) {
+ // Only two options, Yes or No, return if not Yes
+ return;
+ }
+
+ auto clonedManager = HighlightRuleManager();
+ clonedManager.fromVariantMap(Client::highlightRuleManager()->toVariantMap());
+
+ for (const auto &variant : notificationSettings.highlightList()) {
+ auto highlightRule = variant.toMap();
+
+ clonedManager.addHighlightRule(
+ highlightRule["Name"].toString(),
+ highlightRule["RegEx"].toBool(),
+ highlightRule["CS"].toBool(),
+ highlightRule["Enable"].toBool(),
+ false,
+ "",
+ highlightRule["Channel"].toString()
+ );
+ }
+
+ Client::highlightRuleManager()->requestUpdate(clonedManager.toVariantMap());
+ setChangedState(false);
+ load();
+
+ // Give a heads-up that all succeeded
+ QMessageBox::information(this,
+ tr("Imported highlights"),
+ tr("%1 highlight rules successfully imported."
+ ).arg(QString::number(localHighlightList.count())));
+}
+
+bool CoreHighlightSettingsPage::isSelectable() const {
+ return Client::isConnected();
+ // We check for Quassel::Feature::CoreSideHighlights when loading this page, allowing us to show
+ // a friendly error message.
+}