From 615b87497cd8d4eedc386f002931d6d353f53ccd Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Mon, 5 Sep 2016 17:44:24 +0200
Subject: Add ability to filter results in "Search Room" dialog

This is implemented with the help of an implementation of
QSortFilterProxyModel which filters room names based on a
user search string.

Test-Information:

Tested on OS X 10.11.6 with Qt 5.5.1. Tested UX with different
MUC services and search strings.

Change-Id: I88085d089493008b2197a4aeb45d8c4d75724b9c

diff --git a/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.cpp b/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.cpp
new file mode 100644
index 0000000..b8cf15a
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.cpp
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2016 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#include <Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h>
+
+namespace Swift {
+
+QtLeafSortFilterProxyModel::QtLeafSortFilterProxyModel(QObject* parent) : QSortFilterProxyModel(parent) {
+
+}
+
+QtLeafSortFilterProxyModel::~QtLeafSortFilterProxyModel() {
+
+}
+
+bool QtLeafSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex& source_parent) const {
+    if (!sourceModel()->hasChildren(sourceModel()->index(source_row, 0, source_parent))) {
+        return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
+    }
+    else {
+        return true;
+    }
+}
+
+}
diff --git a/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h b/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h
new file mode 100644
index 0000000..b4be622
--- /dev/null
+++ b/Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2016 Isode Limited.
+ * All rights reserved.
+ * See the COPYING file for more information.
+ */
+
+#pragma once
+
+#include <QSortFilterProxyModel>
+
+namespace Swift {
+
+/**
+ * @brief The QtLeafSortFilterProxyModel class is similar to the QSortFilterProxyModel
+ * class. While the basic QSortFilterProxyModel class filters all hierarchical items,
+ * the QtLeafSortFilterProxyModel class will only filter on items without children.
+ */
+class QtLeafSortFilterProxyModel : public QSortFilterProxyModel {
+    Q_OBJECT
+
+public:
+    QtLeafSortFilterProxyModel(QObject* parent = nullptr);
+    virtual ~QtLeafSortFilterProxyModel();
+
+protected:
+    virtual bool filterAcceptsRow(int source_row, const QModelIndex& source_parent) const;
+};
+
+}
diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp
index 114ec1d..f69da41 100644
--- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp
+++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.cpp
@@ -12,6 +12,7 @@
 #include <QMovie>
 #include <QPushButton>
 #include <QScrollBar>
+#include <QSortFilterProxyModel>
 #include <QTimer>
 
 #include <Swift/Controllers/UIEvents/AddMUCBookmarkUIEvent.h>
@@ -20,6 +21,7 @@
 #include <Swift/QtUI/MUCSearch/MUCSearchDelegate.h>
 #include <Swift/QtUI/MUCSearch/MUCSearchEmptyItem.h>
 #include <Swift/QtUI/MUCSearch/MUCSearchModel.h>
+#include <Swift/QtUI/MUCSearch/QtLeafSortFilterProxyModel.h>
 #include <Swift/QtUI/QtSwiftUtil.h>
 
 namespace Swift {
@@ -30,10 +32,12 @@ QtMUCSearchWindow::QtMUCSearchWindow() {
     setWindowIcon(QIcon(":/logo-icon-16.png"));
 #endif
     setModal(true);
-    ui_.filter_->hide();
     model_ = new MUCSearchModel();
+    sortFilterProxyModel_ = new QtLeafSortFilterProxyModel(this);
+    sortFilterProxyModel_->setSourceModel(model_);
+    sortFilterProxyModel_->setDynamicSortFilter(true);
     delegate_ = new MUCSearchDelegate();
-    ui_.results_->setModel(model_);
+    ui_.results_->setModel(sortFilterProxyModel_);
     ui_.results_->setItemDelegate(delegate_);
     ui_.results_->setHeaderHidden(true);
     ui_.results_->setRootIsDecorated(true);
@@ -41,7 +45,7 @@ QtMUCSearchWindow::QtMUCSearchWindow() {
     ui_.results_->setAlternatingRowColors(true);
     ui_.results_->setSortingEnabled(true);
     ui_.results_->sortByColumn(0, Qt::AscendingOrder);
-    connect(ui_.searchButton, SIGNAL(clicked()), this, SLOT(handleSearch()));
+    connect(ui_.searchButton_, SIGNAL(clicked()), this, SLOT(handleSearch()));
     connect(ui_.service_, SIGNAL(activated(const QString&)), this, SLOT(handleSearch(const QString&)));
     connect(ui_.results_->selectionModel(), SIGNAL(selectionChanged (const QItemSelection&, const QItemSelection&)), this, SLOT(handleSelectionChanged (const QItemSelection&, const QItemSelection&)));
     connect(ui_.results_, SIGNAL(activated(const QModelIndex&)), this, SLOT(handleActivated(const QModelIndex&)));
@@ -50,6 +54,7 @@ QtMUCSearchWindow::QtMUCSearchWindow() {
     connect(ui_.okButton, SIGNAL(clicked()), this, SLOT(accept()));
     ui_.okButton->setEnabled(false);
     connect(ui_.cancelButton, SIGNAL(clicked()), this, SLOT(reject()));
+    connect(ui_.filter_, SIGNAL(textChanged(const QString&)), this, SLOT(handleFilterStringChanged(const QString&)));
 
     throbber_ = new QLabel(tr("Searching"), ui_.results_);
     throbber_->setMovie(new QMovie(":/icons/throbber.gif", QByteArray(), throbber_));
@@ -104,7 +109,7 @@ void QtMUCSearchWindow::handleActivated(const QModelIndex& index) {
     if (!index.isValid()) {
         return;
     }
-    if (dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(index.internalPointer()))) {
+    if (dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(sortFilterProxyModel_->mapToSource(index).internalPointer()))) {
         accept();
     }
 }
@@ -119,7 +124,12 @@ void QtMUCSearchWindow::handleSearch(const QString& service) {
     }
 }
 
+void QtMUCSearchWindow::handleFilterStringChanged(const QString& filterString) {
+    sortFilterProxyModel_->setFilterRegExp(filterString);
+}
+
 void QtMUCSearchWindow::show() {
+    ui_.filter_->clear();
     QWidget::show();
     QWidget::activateWindow();
 }
@@ -197,7 +207,7 @@ MUCSearchRoomItem* QtMUCSearchWindow::getSelectedRoom() const {
         return nullptr;
     }
     else {
-        return dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(lstIndex.first().internalPointer()));
+        return dynamic_cast<MUCSearchRoomItem*>(static_cast<MUCSearchItem*>(sortFilterProxyModel_->mapToSource(lstIndex.first()).internalPointer()));
     }
 }
 
diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h
index b115e6f..6f38533 100644
--- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h
+++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.h
@@ -10,6 +10,8 @@
 
 #include <Swift/QtUI/MUCSearch/ui_QtMUCSearchWindow.h>
 
+class QSortFilterProxyModel;
+
 namespace Swift {
     class MUCSearchModel;
     class MUCSearchDelegate;
@@ -36,6 +38,7 @@ namespace Swift {
         private slots:
             void handleSearch();
             void handleSearch(const QString&);
+            void handleFilterStringChanged(const QString&);
             void handleActivated(const QModelIndex& index);
             void updateThrobberPosition();
             void handleSelectionChanged (const QItemSelection&, const QItemSelection&);
@@ -47,5 +50,6 @@ namespace Swift {
             MUCSearchDelegate* delegate_;
             QLabel* throbber_;
             bool hasHadScrollBars_;
+            QSortFilterProxyModel* sortFilterProxyModel_ = nullptr;
     };
 }
diff --git a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui
index 49460ab..52714c4 100644
--- a/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui
+++ b/Swift/QtUI/MUCSearch/QtMUCSearchWindow.ui
@@ -6,21 +6,14 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>523</width>
-    <height>368</height>
+    <width>566</width>
+    <height>264</height>
    </rect>
   </property>
   <property name="windowTitle">
    <string>Search Room</string>
   </property>
   <layout class="QGridLayout" name="gridLayout">
-   <item row="0" column="0">
-    <widget class="QLabel" name="label_2">
-     <property name="text">
-      <string>Service:</string>
-     </property>
-    </widget>
-   </item>
    <item row="0" column="1">
     <widget class="QComboBox" name="service_">
      <property name="sizePolicy">
@@ -34,26 +27,14 @@
      </property>
     </widget>
    </item>
-   <item row="0" column="3">
-    <widget class="QLineEdit" name="filter_">
-     <property name="sizePolicy">
-      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
-       <horstretch>1</horstretch>
-       <verstretch>0</verstretch>
-      </sizepolicy>
-     </property>
+   <item row="0" column="0">
+    <widget class="QLabel" name="serviceLabel_">
      <property name="text">
-      <string/>
-     </property>
-     <property name="frame">
-      <bool>true</bool>
+      <string>Service:</string>
      </property>
     </widget>
    </item>
-   <item row="1" column="0" colspan="4">
-    <widget class="QTreeView" name="results_"/>
-   </item>
-   <item row="2" column="0" colspan="4">
+   <item row="2" column="0" colspan="5">
     <layout class="QHBoxLayout" name="horizontalLayout">
      <item>
       <spacer name="horizontalSpacer">
@@ -90,13 +71,42 @@
      </item>
     </layout>
    </item>
+   <item row="0" column="4">
+    <widget class="QLineEdit" name="filter_">
+     <property name="sizePolicy">
+      <sizepolicy hsizetype="Expanding" vsizetype="Fixed">
+       <horstretch>1</horstretch>
+       <verstretch>0</verstretch>
+      </sizepolicy>
+     </property>
+     <property name="text">
+      <string/>
+     </property>
+     <property name="frame">
+      <bool>true</bool>
+     </property>
+     <property name="placeholderText">
+      <string/>
+     </property>
+    </widget>
+   </item>
    <item row="0" column="2">
-    <widget class="QToolButton" name="searchButton">
+    <widget class="QToolButton" name="searchButton_">
      <property name="text">
       <string>List rooms</string>
      </property>
     </widget>
    </item>
+   <item row="1" column="0" colspan="5">
+    <widget class="QTreeView" name="results_"/>
+   </item>
+   <item row="0" column="3">
+    <widget class="QLabel" name="filterLabel_">
+     <property name="text">
+      <string>Search for</string>
+     </property>
+    </widget>
+   </item>
   </layout>
  </widget>
  <resources/>
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 1f15c15..2d01672 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -160,12 +160,13 @@ sources = [
     "ChatList/ChatListMUCItem.cpp",
     "ChatList/ChatListRecentItem.cpp",
     "ChatList/ChatListWhiteboardItem.cpp",
-    "MUCSearch/QtMUCSearchWindow.cpp",
+    "MUCSearch/MUCSearchDelegate.cpp",
+    "MUCSearch/MUCSearchEmptyItem.cpp",
     "MUCSearch/MUCSearchModel.cpp",
     "MUCSearch/MUCSearchRoomItem.cpp",
-    "MUCSearch/MUCSearchEmptyItem.cpp",
-    "MUCSearch/MUCSearchDelegate.cpp",
     "MUCSearch/MUCSearchServiceItem.cpp",
+    "MUCSearch/QtLeafSortFilterProxyModel.cpp",
+    "MUCSearch/QtMUCSearchWindow.cpp",
     "UserSearch/ContactListDelegate.cpp",
     "UserSearch/ContactListModel.cpp",
     "UserSearch/QtContactListWidget.cpp",
-- 
cgit v0.10.2-6-g49f6