From 168538129a69fa37a19e768149b32ca262bb85a3 Mon Sep 17 00:00:00 2001
From: Tobias Markmann <tm@ayena.de>
Date: Sun, 8 Dec 2013 17:48:18 +0100
Subject: Add QtFilterWidget, a general live-filtering composite widget for
 QtTreeWidgets.

Change-Id: I39d1ae718890d15ffacde6d482b5435cc05330ec
License: This patch is BSD-licensed, see Documentation/Licenses/BSD-simplified.txt for details.

diff --git a/Swift/Controllers/Roster/FuzzyRosterFilter.h b/Swift/Controllers/Roster/FuzzyRosterFilter.h
new file mode 100644
index 0000000..6710084
--- /dev/null
+++ b/Swift/Controllers/Roster/FuzzyRosterFilter.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+
+#include <Swift/Controllers/ContactSuggester.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+#include <Swift/Controllers/Roster/RosterItem.h>
+#include <Swift/Controllers/Roster/RosterFilter.h>
+
+namespace Swift {
+
+class FuzzyRosterFilter : public RosterFilter {
+	public:
+		FuzzyRosterFilter(const std::string& query) : query_(query) { }
+		virtual ~FuzzyRosterFilter() {}
+		virtual bool operator() (RosterItem* item) const {
+			ContactRosterItem *contactItem = dynamic_cast<ContactRosterItem*>(item);
+			if (contactItem) {
+				const bool itemMatched = ContactSuggester::fuzzyMatch(contactItem->getDisplayName(), query_) || ContactSuggester::fuzzyMatch(contactItem->getDisplayJID(), query_);
+				return !itemMatched;
+			} else {
+				return false;
+			}
+		}
+
+	private:
+		std::string query_;
+};
+
+}
+
+
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 4716780..7af9728 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -33,6 +33,7 @@
 #include <Swift/Controllers/UIEvents/RequestBlockListDialogUIEvent.h>
 #include <Swift/Controllers/SettingConstants.h>
 
+#include <Swift/QtUI/Roster/QtFilterWidget.h>
 #include <Swift/QtUI/QtSwiftUtil.h>
 #include <Swift/QtUI/QtTabWidget.h>
 #include <Swift/QtUI/QtSettingsProvider.h>
@@ -76,7 +77,9 @@ QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStr
 	contactTabLayout->setContentsMargins(0, 0, 0, 0);
 
 	treeWidget_ = new QtRosterWidget(uiEventStream_, settings_, this);
+
 	contactTabLayout->addWidget(treeWidget_);
+	new QtFilterWidget(this, treeWidget_, contactTabLayout);
 
 	tabs_->addTab(contactsTabWidget_, tr("&Contacts"));
 
diff --git a/Swift/QtUI/Roster/QtFilterWidget.cpp b/Swift/QtUI/Roster/QtFilterWidget.cpp
new file mode 100644
index 0000000..5bd4669
--- /dev/null
+++ b/Swift/QtUI/Roster/QtFilterWidget.cpp
@@ -0,0 +1,132 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#include <Swift/QtUI/Roster/QtFilterWidget.h>
+
+#include <QLayout>
+#include <QVBoxLayout>
+#include <QKeyEvent>
+#include <QEvent>
+#include <QString>
+#include <QEvent>
+
+#include <Swift/QtUI/QtSwiftUtil.h>
+
+namespace Swift {
+
+QtFilterWidget::QtFilterWidget(QWidget* parent, QtTreeWidget* treeView, QBoxLayout* layout) : QWidget(parent), treeView_(treeView), fuzzyRosterFilter_(0), isModifierSinglePressed_(false) {
+	int targetIndex = layout->indexOf(treeView);
+
+	QVBoxLayout* vboxLayout = new QVBoxLayout(this);
+	vboxLayout->setSpacing(0);
+	vboxLayout->setContentsMargins(0,0,0,0);
+
+	filterLineEdit_ = new QLineEdit(this);
+	filterLineEdit_->hide();
+	vboxLayout->addWidget(filterLineEdit_);
+
+	vboxLayout->addWidget(treeView);
+	setLayout(vboxLayout);
+	layout->insertWidget(targetIndex, this);
+
+	filterLineEdit_->installEventFilter(this);
+	treeView->installEventFilter(this);
+
+	sourceModel_ = treeView_->model();
+}
+
+QtFilterWidget::~QtFilterWidget() {
+
+}
+
+bool QtFilterWidget::eventFilter(QObject*, QEvent* event) {
+	if (event->type() == QEvent::KeyPress ||
+		event->type() == QEvent::KeyRelease ||
+		// InputMethodQuery got introduced in Qt 5.
+#if QT_VERSION >= 0x050000
+		event->type() == QEvent::InputMethodQuery ||
+#endif
+		event->type() == QEvent::InputMethod) {
+		event->ignore();
+		QKeyEvent* keyEvent = dynamic_cast<QKeyEvent*>(event);
+		if (keyEvent) {
+			if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Down) {
+				return false;
+			} else if ((keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Right) && filterLineEdit_->text().isEmpty()) {
+				return false;
+			} else if (keyEvent->key() == Qt::Key_Alt && event->type() == QEvent::KeyPress) {
+				isModifierSinglePressed_ = true;
+			} else if (keyEvent->key() == Qt::Key_Alt && event->type() == QEvent::KeyRelease && isModifierSinglePressed_) {
+				QPoint itemOffset(2,2);
+				QPoint contextMenuPosition = treeView_->visualRect(treeView_->currentIndex()).topLeft() + itemOffset;;
+				QApplication::postEvent(treeView_, new QContextMenuEvent(QContextMenuEvent::Keyboard, contextMenuPosition, treeView_->mapToGlobal(contextMenuPosition)));
+				return true;
+			} else if (keyEvent->key() == Qt::Key_Return) {
+				filterLineEdit_->setText("");
+				return false;
+			} else if (keyEvent->key() == Qt::Key_Escape) {
+				filterLineEdit_->setText("");
+			} else {
+				isModifierSinglePressed_ = false;
+			}
+		}
+
+		filterLineEdit_->event(event);
+		filterLineEdit_->setVisible(!filterLineEdit_->text().isEmpty());
+
+		// update roster filters
+		if (event->type() == QEvent::KeyRelease) {
+			if (fuzzyRosterFilter_) {
+				if (filterLineEdit_->text().isEmpty()) {
+					// remove currently installed search filter and put old filters back
+					treeView_->getRoster()->removeFilter(fuzzyRosterFilter_);
+					delete fuzzyRosterFilter_;
+					fuzzyRosterFilter_ = NULL;
+					pushAllFilters();
+				} else {
+					// remove currently intsalled search filter and put new search filter in place
+					updateSearchFilter();
+				}
+			} else {
+				if (!filterLineEdit_->text().isEmpty()) {
+					// remove currently installed filters and put a search filter in place
+					popAllFilters();
+					updateSearchFilter();
+				}
+			}
+		}
+		return true;
+	}
+	return false;
+}
+
+void QtFilterWidget::popAllFilters() {
+	std::vector<RosterFilter*> filters = treeView_->getRoster()->getFilters();
+	foreach(RosterFilter* filter, filters) {
+		filters_.push_back(filter);
+		treeView_->getRoster()->removeFilter(filter);
+	}
+}
+
+void QtFilterWidget::pushAllFilters() {
+	foreach(RosterFilter* filter, filters_) {
+		treeView_->getRoster()->addFilter(filter);
+	}
+	filters_.clear();
+}
+
+void QtFilterWidget::updateSearchFilter() {
+	if (fuzzyRosterFilter_) {
+		treeView_->getRoster()->removeFilter(fuzzyRosterFilter_);
+		delete fuzzyRosterFilter_;
+		fuzzyRosterFilter_ = NULL;
+	}
+	fuzzyRosterFilter_ = new FuzzyRosterFilter(Q2PSTRING(filterLineEdit_->text()));
+	treeView_->getRoster()->addFilter(fuzzyRosterFilter_);
+	treeView_->setCurrentIndex(sourceModel_->index(0, 0, sourceModel_->index(0,0)));
+}
+
+}
diff --git a/Swift/QtUI/Roster/QtFilterWidget.h b/Swift/QtUI/Roster/QtFilterWidget.h
new file mode 100644
index 0000000..e33616b
--- /dev/null
+++ b/Swift/QtUI/Roster/QtFilterWidget.h
@@ -0,0 +1,46 @@
+/*
+ * Copyright (c) 2013 Tobias Markmann
+ * Licensed under the simplified BSD license.
+ * See Documentation/Licenses/BSD-simplified.txt for more information.
+ */
+
+#pragma once
+
+#include <vector>
+
+#include <QBoxLayout>
+#include <QLineEdit>
+#include <QWidget>
+
+#include <Swift/Controllers/Roster/RosterFilter.h>
+#include <Swift/Controllers/Roster/FuzzyRosterFilter.h>
+
+#include <Swift/QtUI/Roster/QtTreeWidget.h>
+
+namespace Swift {
+
+class QtFilterWidget : public QWidget {
+	Q_OBJECT
+	public:
+		QtFilterWidget(QWidget* parent, QtTreeWidget* treeView, QBoxLayout* layout = 0);
+		virtual ~QtFilterWidget();
+
+	protected:
+		bool eventFilter(QObject*, QEvent* event);
+
+	private:
+		void popAllFilters();
+		void pushAllFilters();
+
+		void updateSearchFilter();
+
+	private:
+		QLineEdit* filterLineEdit_;
+		QtTreeWidget* treeView_;
+		std::vector<RosterFilter*> filters_;
+		QAbstractItemModel* sourceModel_;
+		FuzzyRosterFilter* fuzzyRosterFilter_;
+		bool isModifierSinglePressed_;
+};
+
+}
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 56d2d1b..06b04fa 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -145,6 +145,7 @@ sources = [
     "Roster/RosterDelegate.cpp",
     "Roster/GroupItemDelegate.cpp",
     "Roster/DelegateCommons.cpp",
+    "Roster/QtFilterWidget.cpp",
     "Roster/QtRosterWidget.cpp",
     "Roster/QtOccupantListWidget.cpp",
     "Roster/RosterTooltip.cpp",
-- 
cgit v0.10.2-6-g49f6