From 8ca471e7304e813fa55ebf512a8f30f146fe6b41 Mon Sep 17 00:00:00 2001
From: Kevin Smith <git@kismith.co.uk>
Date: Mon, 4 Oct 2010 17:08:10 +0100
Subject: Assign contacts to groups.

Another patch will follow shortly to stop them appearing offline
after a roster change like this.

Resolves: #272

Release-Notes: It's now possible to assign your contacts to groups.

diff --git a/Swift/QtUI/ContextMenus/QtRosterContextMenu.cpp b/Swift/QtUI/ContextMenus/QtRosterContextMenu.cpp
index 59c3b78..1641266 100644
--- a/Swift/QtUI/ContextMenus/QtRosterContextMenu.cpp
+++ b/Swift/QtUI/ContextMenus/QtRosterContextMenu.cpp
@@ -10,20 +10,26 @@
 #include <QLineEdit>
 #include <QMenu>
 #include <QDebug>
+#include <QDialog>
 
 #include <boost/shared_ptr.hpp>
 
 #include "Swiften/Roster/ContactRosterItem.h"
+#include "Swiften/Roster/GroupRosterItem.h"
 #include "Swiften/Base/String.h"
+#include "Swiften/Roster/Roster.h"
 #include "Swift/Controllers/UIEvents/UIEvent.h"
 #include "Swift/Controllers/UIEvents/RemoveRosterItemUIEvent.h"
 #include "Swift/Controllers/UIEvents/RenameRosterItemUIEvent.h"
 #include "Swift/QtUI/QtSwiftUtil.h"
+#include "Swift/QtUI/QtSetGroupsDialog.h"
+
 
 namespace Swift {
 
-QtRosterContextMenu::QtRosterContextMenu(UIEventStream* eventStream) {
+QtRosterContextMenu::QtRosterContextMenu(UIEventStream* eventStream, QtTreeWidget* treeWidget) {
 	eventStream_ = eventStream;
+	treeWidget_ = treeWidget;
 }
 
 void QtRosterContextMenu::show(RosterItem* item) {
@@ -32,10 +38,36 @@ void QtRosterContextMenu::show(RosterItem* item) {
 		return;
 	}
 	item_ = item;
-	QMenu* contextMenu = new QMenu();
-	contextMenu->addAction("Remove", this, SLOT(handleRemoveContact()));
-	contextMenu->addAction("Rename", this, SLOT(handleRenameContact()));
-	contextMenu->exec(QCursor::pos());
+	QMenu contextMenu;
+	contextMenu.addAction("Remove", this, SLOT(handleRemoveContact()));
+	contextMenu.addAction("Rename", this, SLOT(handleRenameContact()));
+	contextMenu.addAction("Groups", this, SLOT(handleRegroupContact()));
+	/*QMenu* groupsMenu = contextMenu.addMenu("Groups");
+	std::map<QAction, String> groupActions;
+	for (int i = 0; i < 0; i++) {
+		String groupName;
+		groupActions[groupsMenu->addAction(P2QSTRING(groupName))] = groupName;
+	}
+	groupsMenu->addSeparator();
+	groupsMenu->addAction("New Group", this SLOT(handleNewGroup()));*/
+	contextMenu.exec(QCursor::pos());
+}
+
+void QtRosterContextMenu::handleRegroupContact() {
+	QList<QString> allGroups;
+	foreach (RosterItem* item, treeWidget_->getRoster()->getRoot()->getChildren()) {
+		GroupRosterItem* group = dynamic_cast<GroupRosterItem*>(item);
+		if (group) {
+			allGroups.push_back(P2QSTRING(group->getDisplayName()));
+		}
+	}
+	ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item_);
+	assert(contact);
+	QtSetGroupsDialog groupDialog(contact, allGroups);
+
+	if (groupDialog.exec() == QDialog::Accepted) {
+		eventStream_->send(groupDialog.getRegroupEvent());
+	}
 }
 
 void QtRosterContextMenu::handleRemoveContact() {
diff --git a/Swift/QtUI/ContextMenus/QtRosterContextMenu.h b/Swift/QtUI/ContextMenus/QtRosterContextMenu.h
index 44f3314..f2c7e1f 100644
--- a/Swift/QtUI/ContextMenus/QtRosterContextMenu.h
+++ b/Swift/QtUI/ContextMenus/QtRosterContextMenu.h
@@ -9,6 +9,7 @@
 #include <QObject>
 
 #include "Swift/QtUI/ContextMenus/QtContextMenu.h"
+#include "Swift/QtUI/Roster/QtTreeWidget.h"
 #include "Swift/Controllers/UIEvents/UIEventStream.h"
 
 namespace Swift {
@@ -16,15 +17,17 @@ namespace Swift {
 	class QtRosterContextMenu : public QObject, public QtContextMenu {
 		Q_OBJECT
 		public:
-			QtRosterContextMenu(UIEventStream* eventStream);
+			QtRosterContextMenu(UIEventStream* eventStream, QtTreeWidget* treeWidget);
 			void show(RosterItem* item);
 
 		private slots:
 			void handleRemoveContact();
 			void handleRenameContact();
+			void handleRegroupContact();
 
 		private:
 			RosterItem* item_;
 			UIEventStream* eventStream_;
+			QtTreeWidget* treeWidget_;
 	};
 }
diff --git a/Swift/QtUI/QtMainWindow.cpp b/Swift/QtUI/QtMainWindow.cpp
index 6f7783f..f669e95 100644
--- a/Swift/QtUI/QtMainWindow.cpp
+++ b/Swift/QtUI/QtMainWindow.cpp
@@ -53,7 +53,7 @@ QtMainWindow::QtMainWindow(UIEventStream* uiEventStream) : QWidget() {
 	contactTabLayout->setContentsMargins(0, 0, 0, 0);
 	
 	treeWidget_ = new QtTreeWidget(uiEventStream_);
-	contextMenu_ = new QtRosterContextMenu(uiEventStream_);
+	contextMenu_ = new QtRosterContextMenu(uiEventStream_, treeWidget_);
 	treeWidget_->setContextMenu(contextMenu_);
 	treeWidget_->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
 	contactTabLayout->addWidget(treeWidget_);
diff --git a/Swift/QtUI/QtSetGroupsDialog.cpp b/Swift/QtUI/QtSetGroupsDialog.cpp
new file mode 100644
index 0000000..ad24122
--- /dev/null
+++ b/Swift/QtUI/QtSetGroupsDialog.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include "QtSetGroupsDialog.h"
+
+#include <algorithm>
+
+#include <QScrollArea>
+#include <QBoxLayout>
+#include <QLabel>
+#include <QDialogButtonBox>
+
+#include "Swift/QtUI/QtSwiftUtil.h"
+
+namespace Swift {
+
+QtSetGroupsDialog::QtSetGroupsDialog(ContactRosterItem* contact, const QList<QString>& allGroups) : contact_(contact) {
+	QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom, this);
+	QScrollArea* scroll = new QScrollArea(this);
+	layout->addWidget(scroll);
+	QBoxLayout* scrollLayout = new QBoxLayout(QBoxLayout::TopToBottom, scroll);
+	QLabel* label = new QLabel(scroll);
+	label->setText("Choose new groups for " + P2QSTRING(contact->getDisplayName()));
+	scrollLayout->addWidget(label);
+	foreach (QString group, allGroups) {
+			QCheckBox* check = new QCheckBox(scroll);
+			check->setText(group);
+			check->setCheckState(Qt::Unchecked);
+			checkBoxes_[Q2PSTRING(group)] = check;
+			scrollLayout->addWidget(check);
+		}
+	foreach (String group, contact->getGroups()) {
+		checkBoxes_[group]->setCheckState(Qt::Checked);
+	}
+	QWidget* newGroupWidget = new QWidget(scroll);
+	QBoxLayout* newGroupLayout = new QBoxLayout(QBoxLayout::LeftToRight, newGroupWidget);
+	scrollLayout->addWidget(newGroupWidget);
+	newGroup_ = new QCheckBox(newGroupWidget);
+	newGroup_->setText("New Group:");
+	newGroup_->setCheckState(Qt::Unchecked);
+	newGroupLayout->addWidget(newGroup_);
+	newGroupName_ = new QLineEdit(newGroupWidget);
+	newGroupLayout->addWidget(newGroupName_);
+	QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
+	layout->addWidget(buttons);
+	connect(buttons, SIGNAL(accepted()), this, SLOT(accept()));
+	connect(buttons, SIGNAL(rejected()), this, SLOT(reject()));
+}
+
+QtSetGroupsDialog::~QtSetGroupsDialog() {
+	// TODO Auto-generated destructor stub
+}
+
+typedef std::pair<String, QCheckBox*> CheckStringPair;
+
+boost::shared_ptr<RegroupRosterItemUIEvent> QtSetGroupsDialog::getRegroupEvent() {
+	std::vector<String> addedGroups;
+	std::vector<String> removedGroups;
+	std::vector<String> existingGroups = contact_->getGroups();
+	int tickedCount = 0;
+	bool wantsContacts = false;
+	foreach (CheckStringPair pair, checkBoxes_) {
+		bool existing = std::find(existingGroups.begin(), existingGroups.end(), pair.first) != existingGroups.end();
+		if (pair.second->checkState() == Qt::Checked) {
+			tickedCount++;
+			if (pair.first == "Contacts") {
+				wantsContacts = true;
+			}
+			if (!existing) {
+				addedGroups.push_back(pair.first);
+			}
+		} else {
+			if (existing) {
+				removedGroups.push_back(pair.first);
+			}
+		}
+	}
+	if (newGroup_->checkState() == Qt::Checked) {
+		tickedCount++;
+		String name = Q2PSTRING(newGroupName_->text());
+		if (std::find(existingGroups.begin(), existingGroups.end(), name) == existingGroups.end()) {
+			addedGroups.push_back(name);
+		}
+	}
+	if (tickedCount > 1 && wantsContacts) {
+		addedGroups.push_back("Contacts");
+	}
+	boost::shared_ptr<RegroupRosterItemUIEvent> result(new RegroupRosterItemUIEvent(contact_->getJID(), addedGroups, removedGroups));
+	return result;
+}
+
+}
diff --git a/Swift/QtUI/QtSetGroupsDialog.h b/Swift/QtUI/QtSetGroupsDialog.h
new file mode 100644
index 0000000..e8300f5
--- /dev/null
+++ b/Swift/QtUI/QtSetGroupsDialog.h
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2010 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <map>
+
+#include <boost/shared_ptr.hpp>
+
+#include <QDialog>
+#include <QCheckBox>
+#include <QLineEdit>
+#include <QList>
+
+#include "Swiften/Roster/ContactRosterItem.h"
+#include "Swift/Controllers/UIEvents/RegroupRosterItemUIEvent.h"
+
+namespace Swift {
+
+class QtSetGroupsDialog : public QDialog {
+	Q_OBJECT
+	public:
+		QtSetGroupsDialog(ContactRosterItem* contact, const QList<QString>& allGroups);
+		virtual ~QtSetGroupsDialog();
+		boost::shared_ptr<RegroupRosterItemUIEvent> getRegroupEvent();
+	private:
+		ContactRosterItem* contact_;
+		std::map<String, QCheckBox*> checkBoxes_;
+		QCheckBox* newGroup_;
+		QLineEdit* newGroupName_;
+};
+
+}
+
diff --git a/Swift/QtUI/Roster/QtTreeWidget.cpp b/Swift/QtUI/Roster/QtTreeWidget.cpp
index 6ace3df..885cba6 100644
--- a/Swift/QtUI/Roster/QtTreeWidget.cpp
+++ b/Swift/QtUI/Roster/QtTreeWidget.cpp
@@ -46,6 +46,7 @@ QtTreeWidget::~QtTreeWidget() {
 }
 
 void QtTreeWidget::setRosterModel(Roster* roster) {
+	roster_ = roster;
 	model_->setRoster(roster);
 	expandAll();
 }
diff --git a/Swift/QtUI/Roster/QtTreeWidget.h b/Swift/QtUI/Roster/QtTreeWidget.h
index 796afed..838c453 100644
--- a/Swift/QtUI/Roster/QtTreeWidget.h
+++ b/Swift/QtUI/Roster/QtTreeWidget.h
@@ -26,6 +26,7 @@ class QtTreeWidget : public QTreeView{
 		QtTreeWidgetItem* getRoot();
 		void setContextMenu(QtContextMenu* contextMenu);
 		void setRosterModel(Roster* roster);
+		Roster* getRoster() {return roster_;}
 	private slots:
 		void handleItemActivated(const QModelIndex&);
 		void handleModelItemExpanded(const QModelIndex&, bool expanded);
@@ -38,6 +39,7 @@ class QtTreeWidget : public QTreeView{
 	private:
 		void drawBranches(QPainter*, const QRect&, const QModelIndex&) const;
 		RosterModel* model_;
+		Roster* roster_;
 		RosterDelegate* delegate_;
 		QtTreeWidgetItem* treeRoot_;
 		QtContextMenu* contextMenu_;
diff --git a/Swift/QtUI/SConscript b/Swift/QtUI/SConscript
index 609eb5d..a3631e5 100644
--- a/Swift/QtUI/SConscript
+++ b/Swift/QtUI/SConscript
@@ -83,6 +83,7 @@ sources = [
     "QtBookmarkDetailWindow.cpp",
     "QtAddBookmarkWindow.cpp",
     "QtEditBookmarkWindow.cpp",
+    "QtSetGroupsDialog.cpp",
     "ChatSnippet.cpp",
     "MessageSnippet.cpp",
     "SystemMessageSnippet.cpp",
diff --git a/Swiften/Roster/ContactRosterItem.cpp b/Swiften/Roster/ContactRosterItem.cpp
index 9251fad..bbf3928 100644
--- a/Swiften/Roster/ContactRosterItem.cpp
+++ b/Swiften/Roster/ContactRosterItem.cpp
@@ -98,6 +98,18 @@ void ContactRosterItem::applyPresence(const String& resource, boost::shared_ptr<
 	onDataChanged();
 }
 
+const std::vector<String> ContactRosterItem::getGroups() const {
+	return groups_;
+}
+
+/** Only used so a contact can know about the groups it's in*/
+void ContactRosterItem::addGroup(const String& group) {
+	groups_.push_back(group);
+}
+void ContactRosterItem::removeGroup(const String& group) {
+	groups_.erase(std::find(groups_.begin(), groups_.end(), group));
+}
+
 }
 
 
diff --git a/Swiften/Roster/ContactRosterItem.h b/Swiften/Roster/ContactRosterItem.h
index 21f6024..707dd42 100644
--- a/Swiften/Roster/ContactRosterItem.h
+++ b/Swiften/Roster/ContactRosterItem.h
@@ -36,6 +36,10 @@ class ContactRosterItem : public RosterItem {
 		void applyPresence(const String& resource, boost::shared_ptr<Presence> presence);
 		void clearPresence();
 		void calculateShownPresence();
+		const std::vector<String> getGroups() const;
+		/** Only used so a contact can know about the groups it's in*/
+		void addGroup(const String& group);
+		void removeGroup(const String& group);
 	private:
 		JID jid_;
 		JID displayJID_;
@@ -44,6 +48,7 @@ class ContactRosterItem : public RosterItem {
 		std::map<String, boost::shared_ptr<Presence> > presences_;
 		boost::shared_ptr<Presence> offlinePresence_;
 		boost::shared_ptr<Presence> shownPresence_;
+		std::vector<String> groups_;
 };
 
 }
diff --git a/Swiften/Roster/Roster.cpp b/Swiften/Roster/Roster.cpp
index f9f0dbb..68bac53 100644
--- a/Swiften/Roster/Roster.cpp
+++ b/Swiften/Roster/Roster.cpp
@@ -73,9 +73,18 @@ void Roster::addContact(const JID& jid, const JID& displayJID, const String& nam
 	ContactRosterItem *item = new ContactRosterItem(jid, displayJID, name, group);
 	item->setAvatarPath(avatarPath);
 	group->addChild(item);
+	if (itemMap_[fullJIDMapping_ ? jid : jid.toBare()].size() > 0) {
+		foreach (String existingGroup, itemMap_[fullJIDMapping_ ? jid : jid.toBare()][0]->getGroups()) {
+			item->addGroup(existingGroup);
+		}
+	}
 	itemMap_[fullJIDMapping_ ? jid : jid.toBare()].push_back(item);
 	item->onDataChanged.connect(boost::bind(&Roster::handleDataChanged, this, item));
 	filterContact(item, group);
+
+	foreach (ContactRosterItem* item, itemMap_[fullJIDMapping_ ? jid : jid.toBare()]) {
+		item->addGroup(groupName);
+	}
 }
 
 struct JIDEqualsTo {
@@ -113,6 +122,9 @@ void Roster::removeContactFromGroup(const JID& jid, const String& groupName) {
 		}
 		it++;
 	}
+	foreach (ContactRosterItem* item, itemMap_[fullJIDMapping_ ? jid : jid.toBare()]) {
+		item->removeGroup(groupName);
+	}
 }
 
 
-- 
cgit v0.10.2-6-g49f6