settings: Add Features tab to Network for skipCaps
authorShane Synan <digitalcircuit36939@gmail.com>
Mon, 20 Jul 2020 05:33:35 +0000 (01:33 -0400)
committerManuel Nickschas <sputnick@quassel-irc.org>
Sat, 28 Nov 2020 12:42:31 +0000 (13:42 +0100)
Add new "Features" tab to the Network settings page, allowing
configuring which IRCv3 capabilities to skip during negotiation.

This initial list is empty; individual IRCv3 capabilities will be
added in future commits.

When the core doesn't support skipping capabilities, show an upgrade
warning banner at the top.

When skipping capabilities is supported, show an info banner noting
that the network must support these features.  The details button
tries to explain IRCv3 capabilities and shows the currently enabled
features.

Add an "Advanced..." button which directly configures the "skipCaps"
list as a space-separated list of IRCv3 capabilities.  This provides
an escape hatch in case misbehaving servers are found or if Quassel's
implementation of an IRCv3 feature is faulty.  It also allows older
clients (with this change) to clear the capability skip list set by
newer clients.  A "Defaults" button makes it clear how to restore the
list to Quassel's normal behavior.

Clean up the settings page UI, including fixing the Commands tab
"perform" list of commands to fill the available space.

src/qtui/settingspages/capseditdlg.ui [new file with mode: 0644]
src/qtui/settingspages/networkssettingspage.cpp
src/qtui/settingspages/networkssettingspage.h
src/qtui/settingspages/networkssettingspage.ui
src/qtui/settingspages/settingspages.cmake

diff --git a/src/qtui/settingspages/capseditdlg.ui b/src/qtui/settingspages/capseditdlg.ui
new file mode 100644 (file)
index 0000000..23284eb
--- /dev/null
@@ -0,0 +1,112 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>CapsEditDlg</class>
+ <widget class="QDialog" name="CapsEditDlg">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>475</width>
+    <height>154</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Edit Network Features</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="QLabel" name="label">
+     <property name="text">
+      <string>&lt;p&gt;Enter a space-separated list of IRCv3 capabilities to ignore:&lt;/p&gt;</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <widget class="QLineEdit" name="skipCapsEdit"/>
+   </item>
+   <item>
+    <widget class="QLabel" name="label_2">
+     <property name="text">
+      <string>&lt;p&gt;A list of capabilities may be found at &lt;a href=&quot;https://ircv3.net/irc/&quot;&gt;https://ircv3.net/irc/&lt;/a&gt;.&lt;/p&gt;</string>
+     </property>
+     <property name="alignment">
+      <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
+     </property>
+     <property name="wordWrap">
+      <bool>true</bool>
+     </property>
+     <property name="openExternalLinks">
+      <bool>true</bool>
+     </property>
+     <property name="textInteractionFlags">
+      <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
+     </property>
+    </widget>
+   </item>
+   <item>
+    <spacer>
+     <property name="orientation">
+      <enum>Qt::Vertical</enum>
+     </property>
+     <property name="sizeHint" stdset="0">
+      <size>
+       <width>270</width>
+       <height>0</height>
+      </size>
+     </property>
+    </spacer>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok|QDialogButtonBox::RestoreDefaults</set>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>CapsEditDlg</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>248</x>
+     <y>254</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>CapsEditDlg</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>316</x>
+     <y>260</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
index 468f2b5..c9de1ac 100644 (file)
@@ -67,6 +67,7 @@ NetworksSettingsPage::NetworksSettingsPage(QWidget* parent)
     disconnectedIcon = icon::get("network-disconnect");
 
     // Status icons
+    infoIcon = icon::get({"emblem-information", "dialog-information"});
     successIcon = icon::get({"emblem-success", "dialog-information"});
     unavailableIcon = icon::get({"emblem-unavailable", "dialog-warning"});
     questionIcon = icon::get({"emblem-question", "dialog-question", "dialog-information"});
@@ -178,6 +179,20 @@ void NetworksSettingsPage::load()
                                                         "modify message rate limits.")));
     }
 
+    if (!Client::isConnected() || Client::isCoreFeatureEnabled(Quassel::Feature::SkipIrcCaps)) {
+        // Either disconnected or IRCv3 capability skippping supported, enable configuration and
+        // hide warning.  Don't show the warning needlessly when disconnected.
+        ui.enableCapsConfigWidget->setEnabled(true);
+        ui.enableCapsStatusLabel->setText(tr("These features require support from the network"));
+        ui.enableCapsStatusIcon->setPixmap(infoIcon.pixmap(16));
+    }
+    else {
+        // Core does not IRCv3 capability skipping, show warning and disable configuration
+        ui.enableCapsConfigWidget->setEnabled(false);
+        ui.enableCapsStatusLabel->setText(tr("Your Quassel core is too old to configure IRCv3 features"));
+        ui.enableCapsStatusIcon->setPixmap(unavailableIcon.pixmap(16));
+    }
+
     // Hide the SASL EXTERNAL notice until a network's shown.  Stops it from showing while loading
     // backlog from the core.
     sslUpdated();
@@ -982,6 +997,73 @@ void NetworksSettingsPage::on_saslStatusDetails_clicked()
     }
 }
 
+void NetworksSettingsPage::on_enableCapsStatusDetails_clicked()
+{
+    if (!Client::isConnected() || Client::isCoreFeatureEnabled(Quassel::Feature::SkipIrcCaps)) {
+        // Either disconnected or IRCv3 capability skippping supported
+
+        // Try to get a list of currently enabled features
+        QStringList sortedCapsEnabled;
+        // Check if a network is selected
+        if (ui.networkList->selectedItems().count()) {
+            // Get the underlying Network from the selected network
+            NetworkId netid = ui.networkList->selectedItems()[0]->data(Qt::UserRole).value<NetworkId>();
+            const Network* net = Client::network(netid);
+            if (net && Client::isCoreFeatureEnabled(Quassel::Feature::CapNegotiation)) {
+                // Capability negotiation is supported, network exists.
+                // If the network is disconnected, the list of enabled capabilities will be empty,
+                // no need to check for that specifically.
+                // Sorting isn't required, but it looks nicer.
+                sortedCapsEnabled = net->capsEnabled();
+                sortedCapsEnabled.sort();
+            }
+        }
+
+        // Try to explain IRCv3 network features in a friendly way, including showing the currently
+        // enabled features if available
+        auto messageText = QString("<p>%1</p></br><p>%2</p>")
+                .arg(tr("Quassel makes use of newer IRC features when supported by the IRC network."
+                        "  If desired, you can disable unwanted or problematic features here."),
+                     tr("The <a href=\"https://ircv3.net/irc/\">IRCv3 website</a> provides more "
+                        "technical details on the IRCv3 capabilities powering these features."));
+
+        if (!sortedCapsEnabled.isEmpty()) {
+            // Format the capabilities within <code></code> blocks
+            auto formattedCaps = QString("<code>%1</code>")
+                    .arg(sortedCapsEnabled.join("</code>, <code>"));
+
+            // Add the currently enabled capabilities to the list
+            // This creates a new QString, but this code is not performance-critical.
+            messageText = messageText.append(QString("<p><i>%1</i></p>").arg(
+                                                 tr("Currently enabled IRCv3 capabilities for this "
+                                                    "network: %1").arg(formattedCaps)));
+        }
+
+        QMessageBox::information(this, tr("Configuring network features"), messageText);
+    }
+    else {
+        // Core does not IRCv3 capability skipping, show warning
+        QMessageBox::warning(this, tr("Configuring network features unsupported"),
+                             QString("<p><b>%1</b></p></br><p>%2</p>")
+                             .arg(tr("Your Quassel core is too old to configure IRCv3 network features"),
+                                  tr("You need a Quassel core v0.14.0 or newer to control what network "
+                                     "features Quassel will use.")));
+    }
+}
+
+void NetworksSettingsPage::on_enableCapsAdvanced_clicked()
+{
+    if (currentId == 0)
+        return;
+
+    CapsEditDlg dlg(networkInfos[currentId].skipCapsToString(), this);
+    if (dlg.exec() == QDialog::Accepted) {
+        networkInfos[currentId].skipCapsFromString(dlg.skipCapsString());
+        displayNetwork(currentId);
+        widgetHasChanged();
+    }
+}
+
 IdentityId NetworksSettingsPage::defaultIdentity() const
 {
     IdentityId defaultId = 0;
@@ -1213,6 +1295,47 @@ void ServerEditDlg::updateSslPort(bool isChecked)
     }
 }
 
+/**************************************************************************
+ * CapsEditDlg
+ *************************************************************************/
+
+CapsEditDlg::CapsEditDlg(const QString& oldSkipCapsString, QWidget* parent)
+    : QDialog(parent)
+    , oldSkipCapsString(oldSkipCapsString)
+{
+    ui.setupUi(this);
+
+    // Connect to the reset button to reset the text
+    // This provides an explicit way to "get back to defaults" in case someone changes settings to
+    // experiment
+    QPushButton* defaultsButton = ui.buttonBox->button(QDialogButtonBox::RestoreDefaults);
+    connect(defaultsButton, &QPushButton::clicked, this, &CapsEditDlg::defaultSkipCaps);
+
+    if (oldSkipCapsString.isEmpty()) {
+        // Disable Reset button
+        on_skipCapsEdit_textChanged("");
+    }
+    else {
+        ui.skipCapsEdit->setText(oldSkipCapsString);
+    }
+}
+
+
+QString CapsEditDlg::skipCapsString() const
+{
+    return ui.skipCapsEdit->text();
+}
+
+void CapsEditDlg::defaultSkipCaps()
+{
+    ui.skipCapsEdit->setText("");
+}
+
+void CapsEditDlg::on_skipCapsEdit_textChanged(const QString& text)
+{
+    ui.buttonBox->button(QDialogButtonBox::RestoreDefaults)->setDisabled(text.isEmpty());
+}
+
 /**************************************************************************
  * SaveNetworksDlg
  *************************************************************************/
index 5c9efca..73bee52 100644 (file)
@@ -27,6 +27,7 @@
 #include "network.h"
 #include "settingspage.h"
 
+#include "ui_capseditdlg.h"
 #include "ui_networkadddlg.h"
 #include "ui_networkeditdlg.h"
 #include "ui_networkssettingspage.h"
@@ -114,6 +115,16 @@ private slots:
      */
     void on_saslStatusDetails_clicked();
 
+    /**
+     * Event handler for Features status Details button
+     */
+    void on_enableCapsStatusDetails_clicked();
+
+    /**
+     * Event handler for Features Advanced edit button
+     */
+    void on_enableCapsAdvanced_clicked();
+
 private:
     /**
      * Status of capability support
@@ -137,7 +148,7 @@ private:
     QIcon connectedIcon, connectingIcon, disconnectedIcon;
 
     // Status icons
-    QIcon successIcon, unavailableIcon, questionIcon;
+    QIcon infoIcon, successIcon, unavailableIcon, questionIcon;
 
     CapSupportStatus _saslStatusSelected;  /// Status of SASL support for currently-selected network
 
@@ -229,6 +240,25 @@ private:
     Ui::ServerEditDlg ui;
 };
 
+class CapsEditDlg : public QDialog
+{
+    Q_OBJECT
+
+public:
+    CapsEditDlg(const QString& oldSkipCapsString, QWidget* parent = nullptr);
+
+    QString skipCapsString() const;
+
+private slots:
+    void defaultSkipCaps();
+    void on_skipCapsEdit_textChanged(const QString&);
+
+private:
+    Ui::CapsEditDlg ui;
+
+    QString oldSkipCapsString;
+};
+
 class SaveNetworksDlg : public QDialog
 {
     Q_OBJECT
index 7e6ad13..e56a7fc 100644 (file)
              <bool>true</bool>
             </property>
             <property name="sizePolicy">
-             <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+             <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
               <horstretch>0</horstretch>
               <verstretch>0</verstretch>
              </sizepolicy>
@@ -368,7 +368,7 @@ Note that Quassel IRC automatically rejoins channels, so /join will rarely be ne
           </item>
          </layout>
         </widget>
-        <widget class="QWidget" name="tab">
+        <widget class="QWidget" name="connectionTab">
          <attribute name="title">
           <string>Connection</string>
          </attribute>
@@ -658,7 +658,7 @@ Note that Quassel IRC automatically rejoins channels, so /join will rarely be ne
           </item>
          </layout>
         </widget>
-        <widget class="QWidget" name="tab_2">
+        <widget class="QWidget" name="autoIdentifyTab">
          <attribute name="title">
           <string>Auto Identify</string>
          </attribute>
@@ -881,14 +881,137 @@ Note that Quassel IRC automatically rejoins channels, so /join will rarely be ne
           </item>
          </layout>
         </widget>
-        <widget class="QWidget" name="advancedTab">
+        <widget class="QWidget" name="featuresTab">
+         <attribute name="title">
+          <string>Features</string>
+         </attribute>
+         <attribute name="toolTip">
+          <string>Configure the modern IRC messaging features Quassel supports</string>
+         </attribute>
+         <layout class="QVBoxLayout" name="verticalLayout_14">
+          <item>
+           <widget class="QWidget" name="enableCapsStatusWidget" native="true">
+            <layout class="QHBoxLayout" name="horizontalLayout_8">
+             <property name="leftMargin">
+              <number>0</number>
+             </property>
+             <property name="topMargin">
+              <number>0</number>
+             </property>
+             <property name="rightMargin">
+              <number>0</number>
+             </property>
+             <property name="bottomMargin">
+              <number>0</number>
+             </property>
+             <item>
+              <widget class="QLabel" name="enableCapsStatusIcon">
+               <property name="text">
+                <string notr="true">[icon]</string>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QLabel" name="enableCapsStatusLabel">
+               <property name="sizePolicy">
+                <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+                 <horstretch>0</horstretch>
+                 <verstretch>0</verstretch>
+                </sizepolicy>
+               </property>
+               <property name="text">
+                <string>These features require support from the network</string>
+               </property>
+               <property name="wordWrap">
+                <bool>true</bool>
+               </property>
+              </widget>
+             </item>
+             <item>
+              <widget class="QPushButton" name="enableCapsStatusDetails">
+               <property name="text">
+                <string>Details...</string>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </widget>
+          </item>
+          <item>
+           <widget class="QWidget" name="enableCapsConfigWidget" native="true">
+            <layout class="QVBoxLayout" name="verticalLayout_6">
+             <property name="leftMargin">
+              <number>0</number>
+             </property>
+             <property name="topMargin">
+              <number>0</number>
+             </property>
+             <property name="rightMargin">
+              <number>0</number>
+             </property>
+             <property name="bottomMargin">
+              <number>0</number>
+             </property>
+             <item>
+              <layout class="QHBoxLayout" name="horizontalLayout_9">
+               <item>
+                <widget class="QPushButton" name="enableCapsAdvanced">
+                 <property name="toolTip">
+                  <string>Configure which IRC capabilities Quassel will ignore during negotiation</string>
+                 </property>
+                 <property name="text">
+                  <string>Advanced...</string>
+                 </property>
+                </widget>
+               </item>
+               <item>
+                <spacer name="horizontalSpacer_6">
+                 <property name="orientation">
+                  <enum>Qt::Horizontal</enum>
+                 </property>
+                 <property name="sizeHint" stdset="0">
+                  <size>
+                   <width>13</width>
+                   <height>35</height>
+                  </size>
+                 </property>
+                </spacer>
+               </item>
+              </layout>
+             </item>
+             <item>
+              <widget class="QLabel" name="enableCapsReconnectNotice">
+               <property name="text">
+                <string>&lt;i&gt;Changes apply after reconnecting to the network&lt;/i&gt;</string>
+               </property>
+              </widget>
+             </item>
+            </layout>
+           </widget>
+          </item>
+          <item>
+           <spacer name="verticalSpacer_5">
+            <property name="orientation">
+             <enum>Qt::Vertical</enum>
+            </property>
+            <property name="sizeHint" stdset="0">
+             <size>
+              <width>20</width>
+              <height>178</height>
+             </size>
+            </property>
+           </spacer>
+          </item>
+         </layout>
+        </widget>
+        <widget class="QWidget" name="encodingsTab">
          <attribute name="title">
           <string>Encodings</string>
          </attribute>
          <attribute name="toolTip">
-          <string>Configure advanced settings such as message encodings and automatic reconnect</string>
+          <string>Configure advanced settings such as message encodings</string>
          </attribute>
-         <layout class="QVBoxLayout" name="verticalLayout_6">
+         <layout class="QVBoxLayout" name="verticalLayout_13">
           <item>
            <widget class="QGroupBox" name="useCustomEncodings">
             <property name="enabled">
@@ -1046,6 +1169,8 @@ This setting defines the encoding for messages that are not UTF-8.</string>
   <tabstop>autoIdentify</tabstop>
   <tabstop>autoIdentifyService</tabstop>
   <tabstop>autoIdentifyPassword</tabstop>
+  <tabstop>enableCapsStatusDetails</tabstop>
+  <tabstop>enableCapsAdvanced</tabstop>
   <tabstop>useCustomEncodings</tabstop>
   <tabstop>sendEncoding</tabstop>
   <tabstop>recvEncoding</tabstop>
index 3a72fc5..e04bea4 100644 (file)
@@ -36,6 +36,7 @@ set(SP_SOURCES
 
 set(SP_FORMS
     buffervieweditdlg.ui
+    capseditdlg.ui
     coreaccounteditdlg.ui
     createidentitydlg.ui
     identityeditwidget.ui