From 087bc243ffb53e3273580bce5ce66305841a3bff Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Sat, 2 Oct 2010 09:53:43 +0100
Subject: Persist roster group expandiness.

Release-Notes: Whether roster groups are expanded or collapsed is now persisted between sessions.

Resolves: #399

diff --git a/Swift/Controllers/MainController.cpp b/Swift/Controllers/MainController.cpp
index 7ff3ed0..9915da9 100644
--- a/Swift/Controllers/MainController.cpp
+++ b/Swift/Controllers/MainController.cpp
@@ -255,7 +255,7 @@ void MainController::handleConnected() {
 	if (freshLogin) {
 		serverDiscoInfo_ = boost::shared_ptr<DiscoInfo>(new DiscoInfo());
 
-		rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, presenceSender_, eventController_, uiEventStream_, client_->getIQRouter());
+		rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, presenceSender_, eventController_, uiEventStream_, client_->getIQRouter(), settings_);
 		rosterController_->onChangeStatusRequest.connect(boost::bind(&MainController::handleChangeStatusRequest, this, _1, _2));
 		rosterController_->onSignOutRequest.connect(boost::bind(&MainController::signOut, this));
 
diff --git a/Swift/Controllers/RosterController.cpp b/Swift/Controllers/RosterController.cpp
index 79cf3b8..5288df1 100644
--- a/Swift/Controllers/RosterController.cpp
+++ b/Swift/Controllers/RosterController.cpp
@@ -37,12 +37,13 @@ namespace Swift {
 /**
  * The controller does not gain ownership of these parameters.
  */
-RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter)
+RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter, SettingsProvider* settings)
  : myJID_(jid), xmppRoster_(xmppRoster), mainWindowFactory_(mainWindowFactory), mainWindow_(mainWindowFactory_->createMainWindow(uiEventStream)), roster_(new Roster()), offlineFilter_(new OfflineRosterFilter()) {
 	iqRouter_ = iqRouter;
 	presenceOracle_ = presenceOracle;
 	presenceSender_ = presenceSender;
 	eventController_ = eventController;
+	expandiness_ = new RosterGroupExpandinessPersister(roster_, settings);
 	roster_->addFilter(offlineFilter_);
 	mainWindow_->setRosterModel(roster_);
 	
@@ -65,7 +66,7 @@ RosterController::RosterController(const JID& jid, XMPPRoster* xmppRoster, Avata
 
 RosterController::~RosterController() {
 	delete offlineFilter_;
-
+	delete expandiness_;
 }
 
 void RosterController::setNickResolver(NickResolver* nickResolver) {
diff --git a/Swift/Controllers/RosterController.h b/Swift/Controllers/RosterController.h
index 80e7e3e..3cb3812 100644
--- a/Swift/Controllers/RosterController.h
+++ b/Swift/Controllers/RosterController.h
@@ -14,6 +14,7 @@
 #include "Swiften/Elements/RosterPayload.h"
 #include "Swiften/Avatars/AvatarManager.h"
 #include "Swift/Controllers/UIEvents/UIEvent.h"
+#include "Swift/Controllers/RosterGroupExpandinessPersister.h"
 
 #include "Swiften/Base/boost_bsignals.h"
 #include <boost/shared_ptr.hpp>
@@ -32,10 +33,11 @@ namespace Swift {
 	class SubscriptionRequestEvent;
 	class UIEventStream;
 	class IQRouter;
+	class SettingsProvider;
 
 	class RosterController {
 		public:
-			RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_);
+			RosterController(const JID& jid, XMPPRoster* xmppRoster, AvatarManager* avatarManager, MainWindowFactory* mainWindowFactory, NickResolver* nickResolver, PresenceOracle* presenceOracle, PresenceSender* presenceSender, EventController* eventController, UIEventStream* uiEventStream, IQRouter* iqRouter_, SettingsProvider* settings);
 			~RosterController();
 			void showRosterWindow();
 			MainWindow* getWindow() {return mainWindow_;};
@@ -70,6 +72,7 @@ namespace Swift {
 			PresenceOracle* presenceOracle_;
 			PresenceSender* presenceSender_;
 			EventController* eventController_;
+			RosterGroupExpandinessPersister* expandiness_;
 			IQRouter* iqRouter_;
 			boost::bsignals::scoped_connection changeStatusConnection_;
 			boost::bsignals::scoped_connection showOfflineConnection_;
diff --git a/Swift/Controllers/RosterGroupExpandinessPersister.cpp b/Swift/Controllers/RosterGroupExpandinessPersister.cpp
new file mode 100644
index 0000000..a6a998a
--- /dev/null
+++ b/Swift/Controllers/RosterGroupExpandinessPersister.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "Swift/Controllers/RosterGroupExpandinessPersister.h"
+
+#include <boost/bind.hpp>
+#include <vector>
+
+#include "Swiften/Roster/GroupRosterItem.h"
+
+namespace Swift {
+
+RosterGroupExpandinessPersister::RosterGroupExpandinessPersister(Roster* roster, SettingsProvider* settings) : roster_(roster), settings_(settings) {
+	load();
+	roster_->onGroupAdded.connect(boost::bind(&RosterGroupExpandinessPersister::handleGroupAdded, this, _1));
+}
+
+void RosterGroupExpandinessPersister::handleGroupAdded(GroupRosterItem* group) {
+	if (collapsed_.find(group->getDisplayName()) != collapsed_.end()) {
+		group->setExpanded(false);
+	} else {
+		group->setExpanded(true);
+	}
+	group->onExpandedChanged.connect(boost::bind(&RosterGroupExpandinessPersister::handleExpandedChanged, this, group, _1));
+}
+
+void RosterGroupExpandinessPersister::handleExpandedChanged(GroupRosterItem* group, bool expanded) {
+	if (expanded) {
+		collapsed_.erase(collapsed_.find(group->getDisplayName()));
+	} else {
+		collapsed_.insert(group->getDisplayName());
+	}
+	save();
+}
+
+void RosterGroupExpandinessPersister::save() {
+	String setting;
+	foreach (const String& group, collapsed_) {
+		if (setting.isEmpty()) {
+			setting += "\n";
+		}
+		setting += group;
+	}
+	settings_->storeString(SettingPath, setting);
+}
+
+void RosterGroupExpandinessPersister::load() {
+	String saved = settings_->getStringSetting(SettingPath);
+	std::vector<String> collapsed = saved.split('\n');
+	foreach (const String& group, collapsed) {
+		collapsed_.insert(group);
+	}
+}
+
+const String RosterGroupExpandinessPersister::SettingPath = "GroupExpandiness";
+
+}
diff --git a/Swift/Controllers/RosterGroupExpandinessPersister.h b/Swift/Controllers/RosterGroupExpandinessPersister.h
new file mode 100644
index 0000000..545c9d0
--- /dev/null
+++ b/Swift/Controllers/RosterGroupExpandinessPersister.h
@@ -0,0 +1,27 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <set>
+#include "Swiften/Roster/Roster.h"
+#include "Swiften/Settings/SettingsProvider.h"
+
+namespace Swift {
+	class RosterGroupExpandinessPersister {
+		public:
+			RosterGroupExpandinessPersister(Roster* roster, SettingsProvider* settings);
+		private:
+			void handleExpandedChanged(GroupRosterItem* group, bool expanded);
+			void handleGroupAdded(GroupRosterItem* group);
+			void load();
+			void save();
+			std::set<String> collapsed_;
+			Roster* roster_;
+			SettingsProvider* settings_;
+			static const String SettingPath;
+	};
+}
diff --git a/Swift/Controllers/SConscript b/Swift/Controllers/SConscript
index d37b370..dead16a 100644
--- a/Swift/Controllers/SConscript
+++ b/Swift/Controllers/SConscript
@@ -29,6 +29,7 @@ if env["SCONS_STAGE"] == "build" :
 			"MainController.cpp",
 			"NickResolver.cpp",
 			"RosterController.cpp",
+			"RosterGroupExpandinessPersister.cpp",
 			"EventWindowController.cpp",
 			"SoundEventController.cpp",
 			"SystemTrayController.cpp",
diff --git a/Swift/Controllers/UnitTest/RosterControllerTest.cpp b/Swift/Controllers/UnitTest/RosterControllerTest.cpp
index 4ecc4c8..dde48ed 100644
--- a/Swift/Controllers/UnitTest/RosterControllerTest.cpp
+++ b/Swift/Controllers/UnitTest/RosterControllerTest.cpp
@@ -20,6 +20,7 @@
 #include "Swiften/Roster/Roster.h"
 #include "Swiften/Roster/GroupRosterItem.h"
 #include "Swiften/Roster/ContactRosterItem.h"
+#include "Swiften/Settings/DummySettingsProvider.h"
 #include "Swiften/Avatars/NullAvatarManager.h"
 #include "Swift/Controllers/XMPPEvents/EventController.h"
 #include "Swiften/Presence/PresenceOracle.h"
@@ -61,7 +62,8 @@ class RosterControllerTest : public CppUnit::TestFixture
 			presenceSender_ = new PresenceSender(stanzaChannel_);
 			eventController_ = new EventController();
 			uiEventStream_ = new UIEventStream();
-			rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, presenceSender_, eventController_, uiEventStream_, router_);
+			settings_ = new DummySettingsProvider();
+			rosterController_ = new RosterController(jid_, xmppRoster_, avatarManager_, mainWindowFactory_, nickResolver_, presenceOracle_, presenceSender_, eventController_, uiEventStream_, router_, settings_);
 			mainWindow_ = mainWindowFactory_->last;
 		};
 
@@ -78,6 +80,7 @@ class RosterControllerTest : public CppUnit::TestFixture
 			delete presenceOracle_;
 			delete stanzaChannel_;
 			delete uiEventStream_;
+			delete settings_;
 		};
 
 	GroupRosterItem* groupChild(size_t i) {
@@ -206,6 +209,7 @@ class RosterControllerTest : public CppUnit::TestFixture
 		EventController* eventController_;
 		UIEventStream* uiEventStream_;
 		MockMainWindow* mainWindow_;
+		DummySettingsProvider* settings_;
 };
 
 CPPUNIT_TEST_SUITE_REGISTRATION(RosterControllerTest);
diff --git a/Swiften/Roster/GroupRosterItem.cpp b/Swiften/Roster/GroupRosterItem.cpp
index aa9fdd4..325c890 100644
--- a/Swiften/Roster/GroupRosterItem.cpp
+++ b/Swiften/Roster/GroupRosterItem.cpp
@@ -31,7 +31,11 @@ bool GroupRosterItem::isExpanded() const {
 	to avoid a loop in this case.
  */
 void GroupRosterItem::setExpanded(bool expanded) {
+	bool old = expanded_;
 	expanded_ = expanded;
+	if (expanded != old) {
+		onExpandedChanged(expanded);
+	}
 }
 
 const std::vector<RosterItem*>& GroupRosterItem::getChildren() const {
diff --git a/Swiften/Roster/GroupRosterItem.h b/Swiften/Roster/GroupRosterItem.h
index 67ced97..b306b59 100644
--- a/Swiften/Roster/GroupRosterItem.h
+++ b/Swiften/Roster/GroupRosterItem.h
@@ -29,6 +29,7 @@ class GroupRosterItem : public RosterItem {
 		static bool itemLessThanWithoutStatus(const RosterItem* left, const RosterItem* right);
 		void setExpanded(bool expanded);
 		bool isExpanded() const;
+		boost::signal<void (bool)> onExpandedChanged;
 	private:
 		void handleChildrenChanged(GroupRosterItem* group);
 		void handleDataChanged(RosterItem* item);
diff --git a/Swiften/Settings/DummySettingsProvider.h b/Swiften/Settings/DummySettingsProvider.h
new file mode 100644
index 0000000..33f02b7
--- /dev/null
+++ b/Swiften/Settings/DummySettingsProvider.h
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include "Swiften/Settings/SettingsProvider.h"
+
+namespace Swift {
+
+class DummySettingsProvider : public SettingsProvider {
+	public:
+		virtual ~DummySettingsProvider() {}
+		virtual String getStringSetting(const String&) {return "";}
+		virtual void storeString(const String &, const String &) {}
+		virtual bool getBoolSetting(const String &, bool ) {return true;}
+		virtual void storeBool(const String &, bool ) {}
+		virtual int getIntSetting(const String &, int ) {return 0;}
+		virtual void storeInt(const String &, int ) {}
+		virtual std::vector<String> getAvailableProfiles() {return std::vector<String>();}
+		virtual void createProfile(const String& ) {}
+};
+
+}
+
+
+
-- 
cgit v0.10.2-6-g49f6