From 0a1d592c7bc85e537b8aa8425d8cbce7b48f915a Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Fri, 2 Nov 2012 21:30:40 +0000
Subject: Save recent status messages and allow easy setting.

Change-Id: I5baaa2cf28cbc344bf442c4a74e0c9ff3ba31ea1

diff --git a/.project.vimrc b/.project.vimrc
index 771522b..cf0bd43 100644
--- a/.project.vimrc
+++ b/.project.vimrc
@@ -15,3 +15,5 @@ set errorformat+=%E%.%#\ test:\ %.%#line:\ %l\ %f,%Z%m
 set makeprg=python\ 3rdParty/SCons/scons.py\ check=1
 
 set noexpandtab
+
+let g:syntastic_c_include_dirs = [ '.', 'Swift/QtUI', '3rdParty/Boost/src' ]
diff --git a/Swift/ChangeLog.md b/Swift/ChangeLog.md
index 6046ba1..2fd9bf3 100644
--- a/Swift/ChangeLog.md
+++ b/Swift/ChangeLog.md
@@ -1,6 +1,7 @@
 3.0-beta1
 ---------
 - Allow toggling of a more compact roster display.
+- Remember status settings and provide quick access to them with searching of recent selections in the status setter.
 
 2.0-beta2
 ---------
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index 7cd017b..a54c6a2 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -74,7 +74,8 @@ if env["SCONS_STAGE"] == "build" :
 			"XMPPURIController.cpp",
 			"ChatMessageSummarizer.cpp",
 			"SettingConstants.cpp",
-			"WhiteboardManager.cpp"
+			"WhiteboardManager.cpp",
+			"StatusCache.cpp"
 		])
 
 	env.Append(UNITTEST_SOURCES = [
diff --git a/Swift/Controllers/StatusCache.cpp b/Swift/Controllers/StatusCache.cpp
new file mode 100644
index 0000000..0e8c34d
--- /dev/null
+++ b/Swift/Controllers/StatusCache.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright (c) 2012 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swift/Controllers/StatusCache.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/lexical_cast.hpp>
+#include <boost/filesystem/fstream.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Base/ByteArray.h>
+#include <SwifTools/Application/ApplicationPathProvider.h>
+
+namespace Swift {
+
+static const size_t MAX_ENTRIES = 200;
+
+StatusCache::StatusCache(ApplicationPathProvider* paths) {
+	paths_ = paths;
+	path_ = paths_->getDataDir() / "StatusCache";
+	loadRecents();
+}
+
+StatusCache::~StatusCache() {
+
+}
+
+std::vector<StatusCache::PreviousStatus> StatusCache::getMatches(const std::string& substring, size_t maxCount) const {
+	std::vector<PreviousStatus> matches;
+	foreach (const PreviousStatus& status, previousStatuses_) {
+		if (substring.empty() || boost::algorithm::ifind_first(status.first, substring)) {
+			matches.push_back(status);
+			if (matches.size() == maxCount) {
+				break;
+			}
+		}
+	}
+	return matches;
+}
+
+void StatusCache::addRecent(const std::string& text, StatusShow::Type type) {
+	previousStatuses_.push_back(PreviousStatus(text, type));
+	for (size_t i = previousStatuses_.size(); i > MAX_ENTRIES; i--) {
+		previousStatuses_.pop_front();
+	}
+	saveRecents();
+}
+
+void StatusCache::loadRecents() {
+	try {
+		if (boost::filesystem::exists(path_)) {
+			ByteArray data;
+			readByteArrayFromFile(data, path_.string());
+			std::string stringData = byteArrayToString(data);
+			std::vector<std::string> lines;
+			boost::split(lines, stringData, boost::is_any_of("\n"));
+			foreach (const std::string& line, lines) {
+				std::vector<std::string> bits;
+				boost::split(bits, line, boost::is_any_of("\t"));
+				if (bits.size() < 2) {
+					continue;
+				}
+				StatusShow::Type type;
+				type = static_cast<StatusShow::Type>(boost::lexical_cast<size_t>(bits[0]));
+				previousStatuses_.push_back(PreviousStatus(bits[1], type));
+			}
+		}
+	}
+	catch (const boost::filesystem::filesystem_error& e) {
+		std::cerr << "ERROR: " << e.what() << std::endl;
+	}
+}
+
+void StatusCache::saveRecents() {
+	try {
+		if (!boost::filesystem::exists(path_.parent_path())) {
+			boost::filesystem::create_directories(path_.parent_path());
+		}
+		boost::filesystem::ofstream file(path_);
+		foreach (const PreviousStatus& recent, previousStatuses_) {
+			std::string message = recent.first;
+			boost::replace_all(message, "\t", " ");
+			file << recent.second << "\t" << message << std::endl;
+		}
+		file.close();
+	}
+	catch (const boost::filesystem::filesystem_error& e) {
+		std::cerr << "ERROR: " << e.what() << std::endl;
+	}
+}
+
+}
+
+
+
+
diff --git a/Swift/Controllers/StatusCache.h b/Swift/Controllers/StatusCache.h
new file mode 100644
index 0000000..35b3674
--- /dev/null
+++ b/Swift/Controllers/StatusCache.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2012 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <string>
+#include <utility>
+#include <vector>
+#include <list>
+#include <iostream>
+
+#include <boost/filesystem/path.hpp>
+
+#include <Swiften/Elements/StatusShow.h>
+
+namespace Swift {
+	class ApplicationPathProvider;
+	class StatusCache {
+		public:
+			typedef std::pair<std::string, StatusShow::Type> PreviousStatus;
+		public:
+			StatusCache(ApplicationPathProvider* paths);
+			~StatusCache();
+
+			std::vector<PreviousStatus> getMatches(const std::string& substring, size_t maxCount) const;
+			void addRecent(const std::string& text, StatusShow::Type type);
+		private:
+			void saveRecents();
+			void loadRecents();
+		private:
+			boost::filesystem::path path_;
+			std::list<PreviousStatus> previousStatuses_;
+			ApplicationPathProvider* paths_;
+	};
+}
+
+
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 8f74a8d..9749397 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -47,14 +47,14 @@
 
 namespace Swift {
 
-QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus, bool emoticonsExist) : QWidget(), MainWindow(false), loginMenus_(loginMenus) {
+QtMainWindow::QtMainWindow(SettingsProvider* settings, UIEventStream* uiEventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist) : QWidget(), MainWindow(false), loginMenus_(loginMenus) {
 	uiEventStream_ = uiEventStream;
 	settings_ = settings;
 	setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding));
 	QBoxLayout *mainLayout = new QBoxLayout(QBoxLayout::TopToBottom, this);
 	mainLayout->setContentsMargins(0,0,0,0);
 	mainLayout->setSpacing(0);
-	meView_ = new QtRosterHeader(settings, this);
+	meView_ = new QtRosterHeader(settings, statusCache, this);
 	mainLayout->addWidget(meView_);
 	connect(meView_, SIGNAL(onChangeStatusRequest(StatusShow::Type, const QString&)), this, SLOT(handleStatusChanged(StatusShow::Type, const QString&)));
 	connect(meView_, SIGNAL(onEditProfileRequest()), this, SLOT(handleEditProfileRequest()));
diff --git a/Swift/QtUI/QtMainWindow.h b/Swift/QtUI/QtMainWindow.h
index f91610a..99bc675 100644
--- a/Swift/QtUI/QtMainWindow.h
+++ b/Swift/QtUI/QtMainWindow.h
@@ -32,11 +32,12 @@ namespace Swift {
 	class QtTabWidget;
 	class SettingsProvider;
 	class QtUIPreferences;
+	class StatusCache;
 
 	class QtMainWindow : public QWidget, public MainWindow {
 		Q_OBJECT
 		public:
-			QtMainWindow(SettingsProvider*, UIEventStream* eventStream, QtLoginWindow::QtMenus loginMenus, bool emoticonsExist);
+			QtMainWindow(SettingsProvider*, UIEventStream* eventStream, QtLoginWindow::QtMenus loginMenus, StatusCache* statusCache, bool emoticonsExist);
 			virtual ~QtMainWindow();
 			std::vector<QMenu*> getMenus() {return menus_;}
 			void setMyNick(const std::string& name);
diff --git a/Swift/QtUI/QtRosterHeader.cpp b/Swift/QtUI/QtRosterHeader.cpp
index d32a12e..44459d5 100644
--- a/Swift/QtUI/QtRosterHeader.cpp
+++ b/Swift/QtUI/QtRosterHeader.cpp
@@ -23,7 +23,7 @@
 #include "QtScaledAvatarCache.h"
 
 namespace Swift {
-QtRosterHeader::QtRosterHeader(SettingsProvider* settings, QWidget* parent) : QWidget(parent) {
+QtRosterHeader::QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent) : QWidget(parent) {
 	QHBoxLayout* topLayout = new QHBoxLayout();
 	topLayout->setSpacing(3);
 	topLayout->setContentsMargins(4,4,4,4);
@@ -62,7 +62,7 @@ QtRosterHeader::QtRosterHeader(SettingsProvider* settings, QWidget* parent) : QW
 
 	rightLayout->addLayout(nameAndSecurityLayout);
 
-	statusWidget_ = new QtStatusWidget(this);
+	statusWidget_ = new QtStatusWidget(statusCache, this);
 	connect(statusWidget_, SIGNAL(onChangeStatusRequest(StatusShow::Type, const QString&)), this, SLOT(handleChangeStatusRequest(StatusShow::Type, const QString&)));
 	rightLayout->addWidget(statusWidget_);
 
diff --git a/Swift/QtUI/QtRosterHeader.h b/Swift/QtUI/QtRosterHeader.h
index 9527cf4..ad19178 100644
--- a/Swift/QtUI/QtRosterHeader.h
+++ b/Swift/QtUI/QtRosterHeader.h
@@ -24,11 +24,12 @@ namespace Swift {
 	class QtStatusWidget;
 	class QtNameWidget;
 	class SettingsProvider;
+	class StatusCache;
 	
 	class QtRosterHeader : public QWidget {
 		Q_OBJECT
 	public:
-		QtRosterHeader(SettingsProvider* settings, QWidget* parent = NULL);
+		QtRosterHeader(SettingsProvider* settings, StatusCache* statusCache, QWidget* parent = NULL);
 		void setAvatar(const QString& path);
 
 		void setJID(const QString& jid);
diff --git a/Swift/QtUI/QtStatusWidget.cpp b/Swift/QtUI/QtStatusWidget.cpp
index 0e2731a..fc1e764 100644
--- a/Swift/QtUI/QtStatusWidget.cpp
+++ b/Swift/QtUI/QtStatusWidget.cpp
@@ -23,10 +23,11 @@
 #include "Swift/QtUI/QtLineEdit.h"
 #include "Swift/QtUI/QtSwiftUtil.h"
 #include <Swift/Controllers/StatusUtil.h>
+#include <Swift/Controllers/StatusCache.h>
 
 namespace Swift {
 
-QtStatusWidget::QtStatusWidget(QWidget *parent) : QWidget(parent), editCursor_(Qt::IBeamCursor), viewCursor_(Qt::PointingHandCursor) {
+QtStatusWidget::QtStatusWidget(StatusCache* statusCache, QWidget *parent) : QWidget(parent), statusCache_(statusCache), editCursor_(Qt::IBeamCursor), viewCursor_(Qt::PointingHandCursor) {
 	isClicking_ = false;
 	connecting_ = false;
 	setMaximumHeight(24);
@@ -134,6 +135,14 @@ void QtStatusWidget::generateList() {
 		item->setStatusTip(item->toolTip());
 		item->setData(Qt::UserRole, QVariant(type));
 	}
+	std::vector<StatusCache::PreviousStatus> previousStatuses = statusCache_->getMatches(Q2PSTRING(text), 8);
+	foreach (StatusCache::PreviousStatus savedStatus, previousStatuses) {
+		QListWidgetItem* item = new QListWidgetItem(P2QSTRING(savedStatus.first), menu_);
+		item->setIcon(icons_[savedStatus.second]);
+		item->setToolTip(item->text());
+		item->setStatusTip(item->toolTip());
+		item->setData(Qt::UserRole, QVariant(savedStatus.second));
+	}
 	foreach (StatusShow::Type type, icons_.keys()) {
 		QListWidgetItem* item = new QListWidgetItem(P2QSTRING(statusShowTypeToFriendlyName(type)), menu_);
 		item->setIcon(icons_[type]);
@@ -141,8 +150,20 @@ void QtStatusWidget::generateList() {
 		item->setStatusTip(item->toolTip());
 		item->setData(Qt::UserRole, QVariant(type));
 	}
+	resizeMenu();
 }
 
+void QtStatusWidget::resizeMenu() {
+	int height = menu_->sizeHintForRow(0) * menu_->count();
+	int marginLeft;
+	int marginTop;
+	int marginRight;
+	int marginBottom;
+	menu_->getContentsMargins(&marginLeft, &marginTop, &marginRight, &marginBottom);
+	height += marginTop + marginBottom;
+
+	menu_->setGeometry(menu_->x(), menu_->y(), menu_->width(), height);
+}
 
 void QtStatusWidget::handleClicked() {
 	editing_ = true;
@@ -197,7 +218,7 @@ void QtStatusWidget::viewMode() {
 	disconnect(qApp, SIGNAL(focusChanged(QWidget*, QWidget*)), this, SLOT(handleApplicationFocusChanged(QWidget*, QWidget*)));
 	editing_ = false;
 	menu_->hide();
-	stack_->setCurrentIndex(0);	
+	stack_->setCurrentIndex(0);
 }
 
 void QtStatusWidget::handleEditComplete() {
@@ -205,6 +226,7 @@ void QtStatusWidget::handleEditComplete() {
 	statusText_ = newStatusText_;
 	viewMode();
 	emit onChangeStatusRequest(selectedStatusType_, statusText_);
+	statusCache_->addRecent(Q2PSTRING(statusText_), selectedStatusType_);
 }
 
 void QtStatusWidget::handleEditCancelled() {
diff --git a/Swift/QtUI/QtStatusWidget.h b/Swift/QtUI/QtStatusWidget.h
index 75bcf52..57f52c6 100644
--- a/Swift/QtUI/QtStatusWidget.h
+++ b/Swift/QtUI/QtStatusWidget.h
@@ -22,10 +22,12 @@ class QMovie;
 namespace Swift {
 	class QtLineEdit;
 	class QtElidingLabel;
+	class StatusCache;
+
 	class QtStatusWidget : public QWidget {
 		Q_OBJECT
 		public:
-			QtStatusWidget(QWidget *parent);
+			QtStatusWidget(StatusCache* statusCache, QWidget *parent);
 			~QtStatusWidget();
 			StatusShow::Type getSelectedStatusShow();
 			void setStatusType(StatusShow::Type type);
@@ -45,9 +47,11 @@ namespace Swift {
 			void handleItemClicked(QListWidgetItem* item);
 			static QString getNoMessage();
 		private:
+			void resizeMenu();
 			void viewMode();
 			void setNewToolTip();
 			//QComboBox *types_;
+			StatusCache* statusCache_;
 			QStackedWidget* stack_;
 			QLabel* statusIcon_;
 			QtElidingLabel* statusTextLabel_;
diff --git a/Swift/QtUI/QtSwift.cpp b/Swift/QtUI/QtSwift.cpp
index 223f3ae..e2f2f8d 100644
--- a/Swift/QtUI/QtSwift.cpp
+++ b/Swift/QtUI/QtSwift.cpp
@@ -38,6 +38,7 @@
 #include <SwifTools/AutoUpdater/AutoUpdater.h>
 #include <SwifTools/AutoUpdater/PlatformAutoUpdaterFactory.h>
 #include "Swiften/Base/Paths.h"
+#include <Swift/Controllers/StatusCache.h>
 
 #if defined(SWIFTEN_PLATFORM_WINDOWS)
 #include "WindowsNotifier.h"
@@ -190,6 +191,8 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
 	uriHandler_ = new QtDBUSURIHandler();
 #endif
 
+	statusCache_ = new StatusCache(applicationPathProvider_);
+
 	if (splitter_) {
 		splitter_->show();
 	}
@@ -199,7 +202,7 @@ QtSwift::QtSwift(const po::variables_map& options) : networkFactories_(&clientMa
 			// Don't add the first tray (see note above)
 			systemTrays_.push_back(new QtSystemTray());
 		}
-		QtUIFactory* uiFactory = new QtUIFactory(settingsHierachy_, qtSettings_, tabs_, splitter_, systemTrays_[i], chatWindowFactory_, networkFactories_.getTimerFactory(), startMinimized, !emoticons.empty());
+		QtUIFactory* uiFactory = new QtUIFactory(settingsHierachy_, qtSettings_, tabs_, splitter_, systemTrays_[i], chatWindowFactory_, networkFactories_.getTimerFactory(), statusCache_, startMinimized, !emoticons.empty());
 		uiFactories_.push_back(uiFactory);
 		MainController* mainController = new MainController(
 				&clientMainThreadCaller_,
@@ -243,6 +246,7 @@ QtSwift::~QtSwift() {
 	}
 	delete tabs_;
 	delete splitter_;
+	delete statusCache_;
 	delete uriHandler_;
 	delete dock_;
 	delete soundPlayer_;
diff --git a/Swift/QtUI/QtSwift.h b/Swift/QtUI/QtSwift.h
index 42fb50f..99393d4 100644
--- a/Swift/QtUI/QtSwift.h
+++ b/Swift/QtUI/QtSwift.h
@@ -50,6 +50,7 @@ namespace Swift {
 	class URIHandler;
 	class SettingsProviderHierachy;
 	class XMLSettingsProvider;
+	class StatusCache;
 		
 	class QtSwift : public QObject {
 		Q_OBJECT
@@ -81,6 +82,7 @@ namespace Swift {
 			CertificateStorageFactory* certificateStorageFactory_;
 			AutoUpdater* autoUpdater_;
 			Notifier* notifier_;
+			StatusCache* statusCache_;
 			PlatformIdleQuerier idleQuerier_;
 			ActualIdleDetector idleDetector_;
 #if defined(SWIFTEN_PLATFORM_MACOSX)
diff --git a/Swift/QtUI/QtUIFactory.cpp b/Swift/QtUI/QtUIFactory.cpp
index a154fb0..4c6b328 100644
--- a/Swift/QtUI/QtUIFactory.cpp
+++ b/Swift/QtUI/QtUIFactory.cpp
@@ -33,7 +33,7 @@
 
 namespace Swift {
 
-QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, bool startMinimized, bool emoticonsExist) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), startMinimized(startMinimized), emoticonsExist_(emoticonsExist) {
+QtUIFactory::QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist) : settings(settings), qtOnlySettings(qtOnlySettings), tabs(tabs), netbookSplitter(netbookSplitter), systemTray(systemTray), chatWindowFactory(chatWindowFactory), timerFactory_(timerFactory), lastMainWindow(NULL), loginWindow(NULL), statusCache(statusCache), startMinimized(startMinimized), emoticonsExist_(emoticonsExist) {
 	chatFontSize = settings->getSetting(QtUISettingConstants::CHATWINDOW_FONT_SIZE);
 	historyFontSize_ = settings->getSetting(QtUISettingConstants::HISTORYWINDOW_FONT_SIZE);
 }
@@ -78,7 +78,7 @@ FileTransferListWidget* QtUIFactory::createFileTransferListWidget() {
 }
 
 MainWindow* QtUIFactory::createMainWindow(UIEventStream* eventStream) {
-	lastMainWindow  = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), emoticonsExist_);
+	lastMainWindow  = new QtMainWindow(settings, eventStream, loginWindow->getMenus(), statusCache, emoticonsExist_);
 	return lastMainWindow;
 }
 
diff --git a/Swift/QtUI/QtUIFactory.h b/Swift/QtUI/QtUIFactory.h
index 30f0101..eb39cc6 100644
--- a/Swift/QtUI/QtUIFactory.h
+++ b/Swift/QtUI/QtUIFactory.h
@@ -26,11 +26,12 @@ namespace Swift {
 	class TimerFactory;
 	class historyWindow_;
 	class WhiteboardSession;
+	class StatusCache;
 
 	class QtUIFactory : public QObject, public UIFactory {
 			Q_OBJECT
 		public:
-			QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, bool startMinimized, bool emoticonsExist);
+			QtUIFactory(SettingsProviderHierachy* settings, QtSettingsProvider* qtOnlySettings, QtChatTabs* tabs, QSplitter* netbookSplitter, QtSystemTray* systemTray, QtChatWindowFactory* chatWindowFactory, TimerFactory* timerFactory, StatusCache* statusCache, bool startMinimized, bool emoticonsExist);
 
 			virtual XMLConsoleWidget* createXMLConsoleWidget();
 			virtual HistoryWindow* createHistoryWindow(UIEventStream*);
@@ -63,6 +64,7 @@ namespace Swift {
 			TimerFactory* timerFactory_;
 			QtMainWindow* lastMainWindow;
 			QtLoginWindow* loginWindow;
+			StatusCache* statusCache;
 			std::vector<QPointer<QtChatWindow> > chatWindows;
 			bool startMinimized;
 			int chatFontSize;
-- 
cgit v0.10.2-6-g49f6