cmake: avoid de-duplication of user's CXXFLAGS
[quassel.git] / src / qtui / settingspages / corehighlightsettingspage.cpp
index 467991f..cb13489 100644 (file)
@@ -1,5 +1,5 @@
 /***************************************************************************
- *   Copyright (C) 2005-2016 by the Quassel Project                        *
+ *   Copyright (C) 2005-2022 by the Quassel Project                        *
  *   devel@quassel-irc.org                                                 *
  *                                                                         *
  *   This program is free software; you can redistribute it and/or modify  *
 
 #include "corehighlightsettingspage.h"
 
-#include "qtui.h"
-#include "uisettings.h"
+#include <algorithm>
 
 #include <QHeaderView>
-#include <client.h>
+#include <QMessageBox>
+#include <QTableWidget>
+
+#include "client.h"
+#include "icon.h"
+#include "qtui.h"
+#include "util.h"
 
-CoreHighlightSettingsPage::CoreHighlightSettingsPage(QWidget *parent)
-    : SettingsPage(tr("Interface"), tr("Core-Side Highlights"), parent)
+CoreHighlightSettingsPage::CoreHighlightSettingsPage(QWidget* parent)
+    : SettingsPage(tr("Interface"), tr("Highlights"),
+                   parent)
 {
     ui.setupUi(this);
-    ui.highlightTable->verticalHeader()->hide();
-    ui.highlightTable->setShowGrid(false);
-
-    ui.highlightTable->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setToolTip("<b>RegEx</b>: This option determines if the highlight rule should be interpreted as a <b>regular expression</b> or just as a keyword.");
-    ui.highlightTable->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn)->setWhatsThis("<b>RegEx</b>: This option determines if the highlight rule should be interpreted as a <b>regular expression</b> or just as a keyword.");
-
-    ui.highlightTable->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setToolTip("<b>CS</b>: This option determines if the highlight rule should be interpreted <b>case sensitive</b>.");
-    ui.highlightTable->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn)->setWhatsThis("<b>CS</b>: This option determines if the highlight rule should be interpreted <b>case sensitive</b>.");
-
-    ui.highlightTable->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setToolTip("<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.");
-    ui.highlightTable->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn)->setWhatsThis("<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.");
-
-#if QT_VERSION < 0x050000
-    ui.highlightTable->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
-    ui.highlightTable->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
-    ui.highlightTable->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
-    ui.highlightTable->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
-    ui.highlightTable->horizontalHeader()->setResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
-#else
-    ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::NameColumn, QHeaderView::Stretch);
-    ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::RegExColumn, QHeaderView::ResizeToContents);
-    ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::CsColumn, QHeaderView::ResizeToContents);
-    ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::EnableColumn, QHeaderView::ResizeToContents);
-    ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::InverseColumn, QHeaderView::ResizeToContents);
-    ui.highlightTable->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
-#endif
-
-    connect(ui.add, SIGNAL(clicked(bool)), this, SLOT(addNewRow()));
-    connect(ui.remove, SIGNAL(clicked(bool)), this, SLOT(removeSelectedRows()));
-    //TODO: search for a better signal (one that emits everytime a selection has been changed for one item)
-    connect(ui.highlightTable, SIGNAL(itemClicked(QTableWidgetItem *)), this, SLOT(selectRow(QTableWidgetItem *)));
-
-    connect(ui.highlightAllNicks, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
-    connect(ui.highlightCurrentNick, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
-    connect(ui.highlightNoNick, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
-    connect(ui.nicksCaseSensitive, SIGNAL(clicked(bool)), this, SLOT(widgetHasChanged()));
-    connect(ui.add, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
-    connect(ui.remove, SIGNAL(clicked()), this, SLOT(widgetHasChanged()));
-    connect(ui.highlightTable, SIGNAL(itemChanged(QTableWidgetItem *)), this, SLOT(tableChanged(QTableWidgetItem *)));
-
-    connect(Client::instance(), SIGNAL(connected()), this, SLOT(clientConnected()));
+
+    setupRuleTable(ui.highlightTable);
+    setupRuleTable(ui.ignoredTable);
+
+    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(), &Client::coreConnectionStateChanged, this, &CoreHighlightSettingsPage::coreConnectionStateChanged);
+
+    connect(ui.highlightAdd, &QAbstractButton::clicked, this, [this]() { addNewHighlightRow(); });
+    connect(ui.highlightRemove, &QAbstractButton::clicked, this, &CoreHighlightSettingsPage::removeSelectedHighlightRows);
+    connect(ui.highlightImport, &QAbstractButton::clicked, this, &CoreHighlightSettingsPage::importRules);
+
+    connect(ui.ignoredAdd, &QAbstractButton::clicked, this, [this]() { addNewIgnoredRow(); });
+    connect(ui.ignoredRemove, &QAbstractButton::clicked, this, &CoreHighlightSettingsPage::removeSelectedIgnoredRows);
+
+    // TODO: search for a better signal (one that emits every time a selection has been changed for one item)
+    connect(ui.highlightTable, &QTableWidget::itemClicked, this, &CoreHighlightSettingsPage::selectHighlightRow);
+    connect(ui.ignoredTable, &QTableWidget::itemClicked, this, &CoreHighlightSettingsPage::selectIgnoredRow);
+
+    // Update the "Case sensitive" checkbox
+    connect(ui.highlightNicksComboBox,
+            selectOverload<int>(&QComboBox::currentIndexChanged),
+            this,
+            &CoreHighlightSettingsPage::highlightNicksChanged);
+    connect(ui.highlightNicksComboBox, selectOverload<int>(&QComboBox::currentIndexChanged), this, &CoreHighlightSettingsPage::widgetHasChanged);
+    connect(ui.nicksCaseSensitive, &QAbstractButton::clicked, this, &CoreHighlightSettingsPage::widgetHasChanged);
+
+    connect(ui.highlightAdd, &QAbstractButton::clicked, this, &CoreHighlightSettingsPage::widgetHasChanged);
+    connect(ui.highlightRemove, &QAbstractButton::clicked, this, &CoreHighlightSettingsPage::widgetHasChanged);
+
+    connect(ui.ignoredAdd, &QAbstractButton::clicked, this, &CoreHighlightSettingsPage::widgetHasChanged);
+    connect(ui.ignoredRemove, &QAbstractButton::clicked, this, &CoreHighlightSettingsPage::widgetHasChanged);
+
+    connect(ui.highlightTable, &QTableWidget::itemChanged, this, &CoreHighlightSettingsPage::highlightTableChanged);
+
+    connect(ui.ignoredTable, &QTableWidget::itemChanged, this, &CoreHighlightSettingsPage::ignoredTableChanged);
+
+    connect(Client::instance(), &Client::connected, this, &CoreHighlightSettingsPage::clientConnected);
+
+    // Warning icon
+    ui.coreUnsupportedIcon->setPixmap(icon::get({"emblem-unavailable", "dialog-warning"}).pixmap(16));
+
+    // Set up client/monolithic remote highlights information
+    // 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(" ", "&nbsp;")));
+    // Re-use translations of "Legacy Highlights" as this is a word-for-word reference, forcing
+    // all spaces to be non-breaking
 }
 
-void CoreHighlightSettingsPage::clientConnected()
+void CoreHighlightSettingsPage::coreConnectionStateChanged(bool state)
 {
-    connect(Client::highlightRuleManager(), SIGNAL(updated()), SLOT(revert()));
+    updateCoreSupportStatus(state);
+    setEnabled(state);
+    if (state) {
+        load();
+    }
+    else {
+        revert();
+    }
 }
 
+void CoreHighlightSettingsPage::setupRuleTable(QTableWidget* table) const
+{
+    table->verticalHeader()->hide();
+    table->setShowGrid(false);
+
+    setupTableTooltips(table->horizontalHeaderItem(CoreHighlightSettingsPage::EnableColumn),
+                       table->horizontalHeaderItem(CoreHighlightSettingsPage::NameColumn),
+                       table->horizontalHeaderItem(CoreHighlightSettingsPage::RegExColumn),
+                       table->horizontalHeaderItem(CoreHighlightSettingsPage::CsColumn),
+                       table->horizontalHeaderItem(CoreHighlightSettingsPage::SenderColumn),
+                       table->horizontalHeaderItem(CoreHighlightSettingsPage::ChanColumn));
+
+    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::SenderColumn, QHeaderView::ResizeToContents);
+    table->horizontalHeader()->setSectionResizeMode(CoreHighlightSettingsPage::ChanColumn, QHeaderView::ResizeToContents);
+}
 
-void CoreHighlightSettingsPage::revert()
+QString CoreHighlightSettingsPage::getTableTooltip(column tableColumn) const
+{
+    switch (tableColumn) {
+    case CoreHighlightSettingsPage::EnableColumn:
+        return tr("Enable/disable this rule");
+
+    case CoreHighlightSettingsPage::NameColumn:
+        return tr("Phrase to match, leave blank to match any message");
+
+    case CoreHighlightSettingsPage::RegExColumn:
+        return 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.");
+
+    case CoreHighlightSettingsPage::CsColumn:
+        return 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>.");
+
+    case CoreHighlightSettingsPage::SenderColumn:
+        return 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>");
+
+    case CoreHighlightSettingsPage::ChanColumn:
+        return tr("<p><b>Channel</b>: Semicolon separated list of channel/query 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>");
+
+    default:
+        // This shouldn't happen
+        return "Invalid column type in CoreHighlightSettingsPage::getTableTooltip()";
+    }
+}
+
+void CoreHighlightSettingsPage::setupTableTooltips(
+    QWidget* enableWidget, QWidget* nameWidget, QWidget* regExWidget, QWidget* csWidget, QWidget* senderWidget, QWidget* chanWidget) const
+{
+    // Make sure everything's valid
+    Q_ASSERT(enableWidget);
+    Q_ASSERT(nameWidget);
+    Q_ASSERT(regExWidget);
+    Q_ASSERT(csWidget);
+    Q_ASSERT(senderWidget);
+    Q_ASSERT(chanWidget);
+
+    // Set tooltips and "What's this?" prompts
+    // Enabled
+    enableWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::EnableColumn));
+    enableWidget->setWhatsThis(enableWidget->toolTip());
+
+    // Name
+    nameWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::NameColumn));
+    nameWidget->setWhatsThis(nameWidget->toolTip());
+
+    // RegEx enabled
+    regExWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::RegExColumn));
+    regExWidget->setWhatsThis(regExWidget->toolTip());
+
+    // Case-sensitivity
+    csWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::CsColumn));
+    csWidget->setWhatsThis(csWidget->toolTip());
+
+    // Sender
+    senderWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::SenderColumn));
+    senderWidget->setWhatsThis(senderWidget->toolTip());
+
+    // Channel/buffer name
+    chanWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::ChanColumn));
+    chanWidget->setWhatsThis(chanWidget->toolTip());
+}
+
+void CoreHighlightSettingsPage::setupTableTooltips(QTableWidgetItem* enableWidget,
+                                                   QTableWidgetItem* nameWidget,
+                                                   QTableWidgetItem* regExWidget,
+                                                   QTableWidgetItem* csWidget,
+                                                   QTableWidgetItem* senderWidget,
+                                                   QTableWidgetItem* chanWidget) const
+{
+    // Make sure everything's valid
+    Q_ASSERT(enableWidget);
+    Q_ASSERT(nameWidget);
+    Q_ASSERT(regExWidget);
+    Q_ASSERT(csWidget);
+    Q_ASSERT(senderWidget);
+    Q_ASSERT(chanWidget);
+
+    // Set tooltips and "What's this?" prompts
+    // Enabled
+    enableWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::EnableColumn));
+    enableWidget->setWhatsThis(enableWidget->toolTip());
+
+    // Name
+    nameWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::NameColumn));
+    nameWidget->setWhatsThis(nameWidget->toolTip());
+
+    // RegEx enabled
+    regExWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::RegExColumn));
+    regExWidget->setWhatsThis(regExWidget->toolTip());
+
+    // Case-sensitivity
+    csWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::CsColumn));
+    csWidget->setWhatsThis(csWidget->toolTip());
+
+    // Sender
+    senderWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::SenderColumn));
+    senderWidget->setWhatsThis(senderWidget->toolTip());
+
+    // Channel/buffer name
+    chanWidget->setToolTip(getTableTooltip(CoreHighlightSettingsPage::ChanColumn));
+    chanWidget->setWhatsThis(chanWidget->toolTip());
+}
+
+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()
 {
-    qWarning() << "revert()";
+    connect(Client::highlightRuleManager(), &SyncableObject::updated, this, &CoreHighlightSettingsPage::revert);
+}
 
+void CoreHighlightSettingsPage::revert()
+{
     if (!hasChanged())
         return;
 
@@ -90,84 +282,137 @@ void CoreHighlightSettingsPage::revert()
     load();
 }
 
-
 bool CoreHighlightSettingsPage::hasDefaults() const
 {
     return true;
 }
 
-
 void CoreHighlightSettingsPage::defaults()
 {
-    ui.highlightCurrentNick->setChecked(true);
+    int highlightNickType = HighlightRuleManager::HighlightNickType::CurrentNick;
+    int defaultIndex = ui.highlightNicksComboBox->findData(QVariant(highlightNickType));
+    ui.highlightNicksComboBox->setCurrentIndex(defaultIndex);
     ui.nicksCaseSensitive->setChecked(false);
-    emptyTable();
+    emptyHighlightTable();
+    emptyIgnoredTable();
 
     widgetHasChanged();
 }
 
-
-void CoreHighlightSettingsPage::addNewRow(QString name, bool regex, bool cs, bool enable, bool inverse, QString chanName, bool self)
+void CoreHighlightSettingsPage::addNewHighlightRow(
+    bool enable, int id, const QString& name, bool regex, bool cs, const QString& sender, const QString& chanName, bool self)
 {
-    ui.highlightTable->setRowCount(ui.highlightTable->rowCount()+1);
+    ui.highlightTable->setRowCount(ui.highlightTable->rowCount() + 1);
+
+    if (id < 0) {
+        id = nextId();
+    }
 
-    auto *nameItem = new QTableWidgetItem(name);
+    autonameItem = new QTableWidgetItem(name);
 
-    auto *regexItem = new QTableWidgetItem("");
+    autoregexItem = new QTableWidgetItem("");
     if (regex)
         regexItem->setCheckState(Qt::Checked);
     else
         regexItem->setCheckState(Qt::Unchecked);
-    regexItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+    regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
 
-    auto *csItem = new QTableWidgetItem("");
+    autocsItem = new QTableWidgetItem("");
     if (cs)
         csItem->setCheckState(Qt::Checked);
     else
         csItem->setCheckState(Qt::Unchecked);
-    csItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+    csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
 
-    auto *enableItem = new QTableWidgetItem("");
+    autoenableItem = new QTableWidgetItem("");
     if (enable)
         enableItem->setCheckState(Qt::Checked);
     else
         enableItem->setCheckState(Qt::Unchecked);
-    enableItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+    enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
 
-    auto *inverseItem = new QTableWidgetItem("");
-    if (inverse)
-        inverseItem->setCheckState(Qt::Checked);
-    else
-        inverseItem->setCheckState(Qt::Unchecked);
-    inverseItem->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled|Qt::ItemIsSelectable);
+    auto* senderItem = new QTableWidgetItem(sender);
+
+    auto* chanNameItem = new QTableWidgetItem(chanName);
 
-    auto *chanNameItem = new QTableWidgetItem(chanName);
+    setupTableTooltips(enableItem, nameItem, regexItem, csItem, senderItem, chanNameItem);
 
-    int lastRow = ui.highlightTable->rowCount()-1;
+    int lastRow = ui.highlightTable->rowCount() - 1;
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
-    ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::InverseColumn, inverseItem);
+    ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
     ui.highlightTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
 
     if (!self)
         ui.highlightTable->setCurrentItem(nameItem);
 
-    highlightList << HighlightRuleManager::HighlightRule(name, regex, cs, enable, inverse, chanName);
+    highlightList << HighlightRuleManager::HighlightRule(id, name, regex, cs, enable, false, sender, chanName);
 }
 
+void CoreHighlightSettingsPage::addNewIgnoredRow(
+    bool enable, int id, const QString& name, bool regex, bool cs, const QString& sender, const QString& chanName, bool self)
+{
+    ui.ignoredTable->setRowCount(ui.ignoredTable->rowCount() + 1);
+
+    if (id < 0) {
+        id = nextId();
+    }
+
+    auto* nameItem = new QTableWidgetItem(name);
+
+    auto* regexItem = new QTableWidgetItem("");
+    if (regex)
+        regexItem->setCheckState(Qt::Checked);
+    else
+        regexItem->setCheckState(Qt::Unchecked);
+    regexItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
 
-void CoreHighlightSettingsPage::removeSelectedRows()
+    auto* csItem = new QTableWidgetItem("");
+    if (cs)
+        csItem->setCheckState(Qt::Checked);
+    else
+        csItem->setCheckState(Qt::Unchecked);
+    csItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+
+    auto* enableItem = new QTableWidgetItem("");
+    if (enable)
+        enableItem->setCheckState(Qt::Checked);
+    else
+        enableItem->setCheckState(Qt::Unchecked);
+    enableItem->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable);
+
+    auto* chanNameItem = new QTableWidgetItem(chanName);
+
+    auto* senderItem = new QTableWidgetItem(sender);
+
+    setupTableTooltips(enableItem, nameItem, regexItem, csItem, senderItem, chanNameItem);
+
+    int lastRow = ui.ignoredTable->rowCount() - 1;
+    ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::NameColumn, nameItem);
+    ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::RegExColumn, regexItem);
+    ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::CsColumn, csItem);
+    ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::EnableColumn, enableItem);
+    ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::SenderColumn, senderItem);
+    ui.ignoredTable->setItem(lastRow, CoreHighlightSettingsPage::ChanColumn, chanNameItem);
+
+    if (!self)
+        ui.ignoredTable->setCurrentItem(nameItem);
+
+    ignoredList << HighlightRuleManager::HighlightRule(id, name, regex, cs, enable, true, sender, chanName);
+}
+
+void CoreHighlightSettingsPage::removeSelectedHighlightRows()
 {
     QList<int> selectedRows;
-    QList<QTableWidgetItem *> selectedItemList = ui.highlightTable->selectedItems();
-    foreach(QTableWidgetItem *selectedItem, selectedItemList) {
+    QList<QTableWidgetItem*> selectedItemList = ui.highlightTable->selectedItems();
+    for (auto selectedItem : selectedItemList) {
         selectedRows.append(selectedItem->row());
     }
-    qSort(selectedRows.begin(), selectedRows.end(), qGreater<int>());
+    std::sort(selectedRows.begin(), selectedRows.end(), std::greater<>());
     int lastRow = -1;
-    foreach(int row, selectedRows) {
+    for (auto row : selectedRows) {
         if (row != lastRow) {
             ui.highlightTable->removeRow(row);
             highlightList.removeAt(row);
@@ -176,16 +421,46 @@ void CoreHighlightSettingsPage::removeSelectedRows()
     }
 }
 
+void CoreHighlightSettingsPage::removeSelectedIgnoredRows()
+{
+    QList<int> selectedRows;
+    QList<QTableWidgetItem*> selectedItemList = ui.ignoredTable->selectedItems();
+    for (auto selectedItem : selectedItemList) {
+        selectedRows.append(selectedItem->row());
+    }
+    std::sort(selectedRows.begin(), selectedRows.end(), std::greater<>());
+    int lastRow = -1;
+    for (auto row : selectedRows) {
+        if (row != lastRow) {
+            ui.ignoredTable->removeRow(row);
+            ignoredList.removeAt(row);
+        }
+        lastRow = row;
+    }
+}
+
+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::selectRow(QTableWidgetItem *item)
+void CoreHighlightSettingsPage::selectHighlightRow(QTableWidgetItem* item)
 {
     int row = item->row();
     bool selected = item->isSelected();
-    ui.highlightTable->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount-1), selected);
+    ui.highlightTable->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1), selected);
 }
 
+void CoreHighlightSettingsPage::selectIgnoredRow(QTableWidgetItem* item)
+{
+    int row = item->row();
+    bool selected = item->isSelected();
+    ui.ignoredTable->setRangeSelected(QTableWidgetSelectionRange(row, 0, row, CoreHighlightSettingsPage::ColumnCount - 1), selected);
+}
 
-void CoreHighlightSettingsPage::emptyTable()
+void CoreHighlightSettingsPage::emptyHighlightTable()
 {
     // ui.highlight and highlightList should have the same size, but just to make sure.
     if (ui.highlightTable->rowCount() != highlightList.size()) {
@@ -194,75 +469,131 @@ void CoreHighlightSettingsPage::emptyTable()
     while (ui.highlightTable->rowCount()) {
         ui.highlightTable->removeRow(0);
     }
-    while (highlightList.size()) {
-        highlightList.removeLast();
-    }
+    highlightList.clear();
 }
 
+void CoreHighlightSettingsPage::emptyIgnoredTable()
+{
+    // ui.highlight and highlightList should have the same size, but just to make sure.
+    if (ui.ignoredTable->rowCount() != ignoredList.size()) {
+        qDebug() << "something is wrong: ui.highlight and highlightList don't have the same size!";
+    }
+    while (ui.ignoredTable->rowCount()) {
+        ui.ignoredTable->removeRow(0);
+    }
+    ignoredList.clear();
+}
 
-void CoreHighlightSettingsPage::tableChanged(QTableWidgetItem *item)
+void CoreHighlightSettingsPage::highlightTableChanged(QTableWidgetItem* item)
 {
-    if (item->row()+1 > highlightList.size())
+    if (item->row() + 1 > highlightList.size())
         return;
 
     auto highlightRule = highlightList.value(item->row());
 
-
-    switch (item->column())
-    {
+    switch (item->column()) {
+    case CoreHighlightSettingsPage::EnableColumn:
+        highlightRule.setIsEnabled(item->checkState() == Qt::Checked);
+        break;
     case CoreHighlightSettingsPage::NameColumn:
-        if (item->text() == "")
-            item->setText(tr("this shouldn't be empty"));
-        highlightRule.name = item->text();
+        highlightRule.setContents(item->text());
         break;
     case CoreHighlightSettingsPage::RegExColumn:
-        highlightRule.isRegEx = (item->checkState() == Qt::Checked);
+        highlightRule.setIsRegEx(item->checkState() == Qt::Checked);
         break;
     case CoreHighlightSettingsPage::CsColumn:
-        highlightRule.isCaseSensitive = (item->checkState() == Qt::Checked);
+        highlightRule.setIsCaseSensitive(item->checkState() == Qt::Checked);
         break;
-    case CoreHighlightSettingsPage::EnableColumn:
-        highlightRule.isEnabled = (item->checkState() == Qt::Checked);
-        break;
-    case CoreHighlightSettingsPage::InverseColumn:
-        highlightRule.isInverse = (item->checkState() == Qt::Checked);
+    case CoreHighlightSettingsPage::SenderColumn:
+        if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
+            item->setText("");
+        highlightRule.setSender(item->text());
         break;
     case CoreHighlightSettingsPage::ChanColumn:
         if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
             item->setText("");
-        highlightRule.chanName = item->text();
+        highlightRule.setChanName(item->text());
         break;
     }
     highlightList[item->row()] = highlightRule;
     emit widgetHasChanged();
 }
 
-
-void CoreHighlightSettingsPage::load()
+void CoreHighlightSettingsPage::ignoredTableChanged(QTableWidgetItem* item)
 {
-    emptyTable();
+    if (item->row() + 1 > ignoredList.size())
+        return;
 
-    auto ruleManager = Client::highlightRuleManager();
-    for (HighlightRuleManager::HighlightRule rule : ruleManager->highlightRuleList()) {
-        addNewRow(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, rule.isInverse, rule.chanName);
-    }
+    auto ignoredRule = ignoredList.value(item->row());
 
-    switch (ruleManager->highlightNick())
-    {
-    case HighlightRuleManager::NoNick:
-        ui.highlightNoNick->setChecked(true);
+    switch (item->column()) {
+    case CoreHighlightSettingsPage::EnableColumn:
+        ignoredRule.setIsEnabled(item->checkState() == Qt::Checked);
+        break;
+    case CoreHighlightSettingsPage::NameColumn:
+        ignoredRule.setContents(item->text());
         break;
-    case HighlightRuleManager::CurrentNick:
-        ui.highlightCurrentNick->setChecked(true);
+    case CoreHighlightSettingsPage::RegExColumn:
+        ignoredRule.setIsRegEx(item->checkState() == Qt::Checked);
+        break;
+    case CoreHighlightSettingsPage::CsColumn:
+        ignoredRule.setIsCaseSensitive(item->checkState() == Qt::Checked);
         break;
-    case HighlightRuleManager::AllNicks:
-        ui.highlightAllNicks->setChecked(true);
+    case CoreHighlightSettingsPage::SenderColumn:
+        if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
+            item->setText("");
+        ignoredRule.setSender(item->text());
+        break;
+    case CoreHighlightSettingsPage::ChanColumn:
+        if (!item->text().isEmpty() && item->text().trimmed().isEmpty())
+            item->setText("");
+        ignoredRule.setChanName(item->text());
         break;
     }
-    ui.nicksCaseSensitive->setChecked(ruleManager->nicksCaseSensitive());
+    ignoredList[item->row()] = ignoredRule;
+    emit widgetHasChanged();
+}
 
-    setChangedState(false);
-    _initialized = true;
+void CoreHighlightSettingsPage::load()
+{
+    emptyHighlightTable();
+    emptyIgnoredTable();
+
+    auto ruleManager = Client::highlightRuleManager();
+    if (ruleManager) {
+        for (auto& rule : ruleManager->highlightRuleList()) {
+            if (rule.isInverse()) {
+                addNewIgnoredRow(rule.isEnabled(),
+                                 rule.id(),
+                                 rule.contents(),
+                                 rule.isRegEx(),
+                                 rule.isCaseSensitive(),
+                                 rule.sender(),
+                                 rule.chanName());
+            }
+            else {
+                addNewHighlightRow(rule.isEnabled(),
+                                   rule.id(),
+                                   rule.contents(),
+                                   rule.isRegEx(),
+                                   rule.isCaseSensitive(),
+                                   rule.sender(),
+                                   rule.chanName());
+            }
+        }
+
+        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();
+    }
 }
 
 void CoreHighlightSettingsPage::save()
@@ -277,22 +608,35 @@ void CoreHighlightSettingsPage::save()
     if (ruleManager == nullptr)
         return;
 
-    auto clonedManager = HighlightRuleManager();
+    HighlightRuleManager clonedManager;
     clonedManager.fromVariantMap(ruleManager->toVariantMap());
     clonedManager.clear();
 
-    for (const HighlightRuleManager::HighlightRule &rule : highlightList) {
-        clonedManager.addHighlightRule(rule.name, rule.isRegEx, rule.isCaseSensitive, rule.isEnabled, rule.isInverse, rule.chanName);
+    for (auto& rule : highlightList) {
+        clonedManager.addHighlightRule(rule.id(),
+                                       rule.contents(),
+                                       rule.isRegEx(),
+                                       rule.isCaseSensitive(),
+                                       rule.isEnabled(),
+                                       false,
+                                       rule.sender(),
+                                       rule.chanName());
     }
 
-    HighlightRuleManager::HighlightNickType highlightNickType = HighlightRuleManager::NoNick;
-    if (ui.highlightCurrentNick->isChecked())
-        highlightNickType = HighlightRuleManager::CurrentNick;
-    if (ui.highlightAllNicks->isChecked())
-        highlightNickType = HighlightRuleManager::AllNicks;
+    for (auto& rule : ignoredList) {
+        clonedManager.addHighlightRule(rule.id(),
+                                       rule.contents(),
+                                       rule.isRegEx(),
+                                       rule.isCaseSensitive(),
+                                       rule.isEnabled(),
+                                       true,
+                                       rule.sender(),
+                                       rule.chanName());
+    }
 
+    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());
@@ -300,8 +644,142 @@ void CoreHighlightSettingsPage::save()
     load();
 }
 
+int CoreHighlightSettingsPage::nextId()
+{
+    int max = 0;
+    for (int i = 0; i < highlightList.count(); i++) {
+        int id = highlightList[i].id();
+        if (id > max) {
+            max = id;
+        }
+    }
+    for (int i = 0; i < ignoredList.count(); i++) {
+        int id = ignoredList[i].id();
+        if (id > max) {
+            max = id;
+        }
+    }
+    return max + 1;
+}
 
 void CoreHighlightSettingsPage::widgetHasChanged()
 {
     setChangedState(true);
-}
\ No newline at end of file
+}
+
+void CoreHighlightSettingsPage::on_coreUnsupportedDetails_clicked()
+{
+    // Re-use translations of "Legacy Highlights" as this is a word-for-word reference, forcing all
+    // spaces to non-breaking
+    const QString localHighlightsName = tr("Legacy Highlights").replace(" ", "&nbsp;");
+
+    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
+    // "Local Highlights" has been removed; it's always called "Legacy" now.
+    QString localHighlightsName = tr("Legacy Highlights").replace(" ", "&nbsp;");
+
+    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;
+    }
+
+    if (hasChanged()) {
+        // Save existing changes first to avoid overwriting them
+        save();
+    }
+
+    HighlightRuleManager clonedManager;
+    clonedManager.fromVariantMap(Client::highlightRuleManager()->toVariantMap());
+
+    for (const auto& variant : notificationSettings.highlightList()) {
+        auto highlightRule = variant.toMap();
+
+        clonedManager.addHighlightRule(clonedManager.nextId(),
+                                       highlightRule["Name"].toString(),
+                                       highlightRule["RegEx"].toBool(),
+                                       highlightRule["CS"].toBool(),
+                                       highlightRule["Enable"].toBool(),
+                                       false,
+                                       "",
+                                       highlightRule["Channel"].toString());
+    }
+
+    // Copy nickname highlighting settings
+    clonedManager.setNicksCaseSensitive(notificationSettings.nicksCaseSensitive());
+    if (notificationSettings.highlightNick() == NotificationSettings::HighlightNickType::AllNicks) {
+        clonedManager.setHighlightNick(HighlightRuleManager::HighlightNickType::AllNicks);
+    }
+    else if (notificationSettings.highlightNick() == NotificationSettings::HighlightNickType::CurrentNick) {
+       clonedManager.setHighlightNick(HighlightRuleManager::HighlightNickType::CurrentNick);
+    }
+    // else - Don't copy "NoNick", "NoNick" is now default and should be ignored
+
+    Client::highlightRuleManager()->requestUpdate(clonedManager.toVariantMap());
+    setChangedState(false);
+    load();
+
+    // Give a heads-up that all succeeded, ask about removing old rules
+    //
+    // Hypothetically, someone might use a common set of highlight rules across multiple cores.
+    // This won't matter once client highlights are disabled entirely on newer cores.
+    //
+    // Remove this once client-side highlights are disabled for newer cores.
+    ret = QMessageBox::question(this, tr("Imported highlights"),
+                                QString("<p>%1</p></br><p>%2</p>").arg(
+                                    tr("%1 highlight rules successfully imported.").arg(QString::number(localHighlightList.count())),
+                                    tr("Clean up old, duplicate highlight rules?")),
+                                QMessageBox::Yes | QMessageBox::No,
+                                QMessageBox::Yes);
+
+    if (ret != QMessageBox::Yes) {
+        // Only two options, Yes or No, return if not Yes
+        return;
+    }
+
+    // Remove all local highlight rules
+    notificationSettings.setHighlightList({});
+    // Disable local nickname highlighting
+    notificationSettings.setHighlightNick(NotificationSettings::HighlightNickType::NoNick);
+    // Disable nickname sensitivity
+    // This isn't needed to disable local highlights, but it's part of appearing reset-to-default
+    notificationSettings.setNicksCaseSensitive(false);
+
+    // Refresh HighlightSettingsPage in case it was already loaded
+    emit localHighlightsChanged();
+}
+
+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.
+}