diff -urN -X psidiff.ignore sources/iris/src/xmpp/jid/jid.cpp work/iris/src/xmpp/jid/jid.cpp
--- sources/iris/src/xmpp/jid/jid.cpp	2009-06-29 22:25:39.000000000 +0600
+++ work/iris/src/xmpp/jid/jid.cpp	2009-07-12 15:54:22.000000000 +0600
@@ -370,6 +370,13 @@
 	return j;
 }
 
+Jid Jid::withDomain(const QString &s) const
+{
+	Jid j = *this;
+	j.setDomain(s);
+	return j;
+}
+
 Jid Jid::withResource(const QString &s) const
 {
 	Jid j = *this;
diff -urN -X psidiff.ignore sources/iris/src/xmpp/jid/jid.h work/iris/src/xmpp/jid/jid.h
--- sources/iris/src/xmpp/jid/jid.h	2009-06-29 22:25:39.000000000 +0600
+++ work/iris/src/xmpp/jid/jid.h	2009-07-12 15:54:22.000000000 +0600
@@ -51,6 +51,7 @@
 		const QString & full() const { return f; }
 
 		Jid withNode(const QString &s) const;
+		Jid withDomain(const QString &s) const;
 		Jid withResource(const QString &s) const;
 
 		bool isValid() const;
diff -urN -X psidiff.ignore sources/src/contactmanager/contactmanagerdlg.cpp work/src/contactmanager/contactmanagerdlg.cpp
--- sources/src/contactmanager/contactmanagerdlg.cpp	1970-01-01 05:00:00.000000000 +0500
+++ work/src/contactmanager/contactmanagerdlg.cpp	2009-07-22 00:45:29.000000000 +0600
@@ -0,0 +1,303 @@
+#include <QMessageBox>
+#include <QItemSelectionModel>
+#include <QFileDialog>
+#include <QFile>
+#include <QScrollArea>
+#include <QCheckBox>
+
+#include "contactmanagerdlg.h"
+
+#include "psiaccount.h"
+#include "userlist.h"
+#include "xmpp_tasks.h"
+#include "psiiconset.h"
+#include "vcardfactory.h"
+#include "contactview.h"
+
+
+ContactManagerDlg::ContactManagerDlg(PsiAccount *pa) :
+	QDialog(0),
+	pa_(pa),
+	um(0)
+{
+	setAttribute(Qt::WA_DeleteOnClose, true);
+	ui_.setupUi(this);
+	pa_->dialogRegister(this);
+	setWindowFlags(Qt::Window);
+
+	um = new ContactManagerModel(this, pa_);
+	um->reloadUsers();
+	ui_.usersView->setModel(um);
+	ui_.usersView->init();
+
+	ui_.cbAction->addItem(IconsetFactory::icon("psi/sendMessage").icon(), tr("Message"), 1);
+	ui_.cbAction->addItem(IconsetFactory::icon("psi/remove").icon(), tr("Remove"), 2);
+	ui_.cbAction->addItem(IconsetFactory::icon("status/ask").icon(), tr("Auth request"), 3);
+	ui_.cbAction->addItem(IconsetFactory::icon("status/ask").icon(), tr("Auth grant"), 4);
+	ui_.cbAction->addItem(IconsetFactory::icon("psi/command").icon(), tr("Change domain"), 5);
+	ui_.cbAction->addItem(IconsetFactory::icon("psi/info").icon(), tr("Resolve nicks"), 6);
+	ui_.cbAction->addItem(IconsetFactory::icon("psi/browse").icon(), tr("Move to group"), 7);
+	ui_.cbAction->addItem(IconsetFactory::icon("psiplus/cm_export").icon(), tr("Export"), 8);
+	ui_.cbAction->addItem(IconsetFactory::icon("psiplus/cm_import").icon(), tr("Import"), 9);
+	connect(ui_.cbAction, SIGNAL(currentIndexChanged(int)), this, SLOT(showParamField(int)));
+	showParamField(0); // 0 - default index of combobox
+
+	ui_.cmbField->insertItems(0, um->manageableFields());
+	ui_.cmbMatchType->addItem(tr("Simple"), ContactManagerModel::SimpleMatch);
+	ui_.cmbMatchType->addItem(tr("RegExp"), ContactManagerModel::RegexpMatch);
+	connect(ui_.btnExecute, SIGNAL(clicked()), this, SLOT(executeCurrent()));
+	connect(ui_.btnSelect, SIGNAL(clicked()), this, SLOT(doSelect()));
+
+	connect(pa_->client(), SIGNAL(rosterRequestFinished(bool,int,QString)), this, SLOT(client_rosterUpdated(bool,int,QString)));
+}
+
+
+
+ContactManagerDlg::~ContactManagerDlg()
+{
+	pa_->dialogUnregister(this);
+}
+
+void ContactManagerDlg::changeEvent(QEvent *e)
+{
+    switch (e->type()) {
+    case QEvent::LanguageChange:
+		ui_.retranslateUi(this);
+        break;
+    default:
+        break;
+    }
+}
+
+void ContactManagerDlg::doSelect()
+{
+	int type = ui_.cmbMatchType->itemData(ui_.cmbMatchType->currentIndex()).toInt();
+	int coumnIndex = ui_.cmbField->currentIndex();
+	um->invertByMatch(coumnIndex + 1, type,  ui_.edtMatch->text());
+}
+
+void ContactManagerDlg::executeCurrent()
+{
+	int action = ui_.cbAction->itemData(ui_.cbAction->currentIndex()).toInt();
+	QList<UserListItem *> users = um->checkedUsers();
+	if (!users.count() && action != 9) {
+		return;
+	}
+	switch (action) {
+		case 1: //message
+			{
+				QList<XMPP::Jid> list;
+				foreach (UserListItem *u, users) {
+					list.append(u->jid().full());
+				}
+				pa_->actionSendMessage(list);
+			}
+			break;
+		case 2: //remove
+			{
+				if (QMessageBox::question(this, "Removal confirmation",
+										  "Are you sure want to delete selected contacts?",
+										  QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) {
+					return;
+				}
+				um->startBatch();
+				JT_Roster *r = new JT_Roster(pa_->client()->rootTask());
+				JT_UnRegister *ju = 0;
+				foreach (UserListItem *u, users) {
+					r->remove(u->jid());
+					if(u->isTransport() && !Jid(pa_->client()->host()).compare(u->jid())) {
+						if (!ju) {
+							ju = new JT_UnRegister(pa_->client()->rootTask());
+						}
+						ju->unreg(u->jid());
+					}
+				}
+				um->clear();
+				r->go(true);
+				if (ju) {
+					ju->go(true);
+				}
+				pa_->client()->rosterRequest();
+			}
+			break;
+		case 3: //Auth request
+			foreach (UserListItem *u, users) {
+				pa_->dj_authReq(u->jid());
+			}
+			break;
+		case 4: //Auth grant
+			foreach (UserListItem *u, users) {
+				pa_->dj_auth(u->jid());
+			}
+			break;
+		case 5: //change domain
+			changeDomain(users);
+			break;
+		case 6: //resolve nicks
+			foreach (UserListItem *u, users) {
+				VCardFactory::instance()->getVCard(u->jid(), pa_->client()->rootTask(), pa_, SLOT(resolveContactName()));
+			}
+			break;
+		case 7:
+			changeGroup(users);
+			break;
+		case 8: //export
+			exportRoster(users);
+			break;
+		case 9: //import
+			importRoster();
+			break;
+		default:
+			QMessageBox::warning(this, "Invalid", "This action is not supported atm");
+	}
+}
+
+void ContactManagerDlg::showParamField(int index)
+{
+	int action = ui_.cbAction->itemData(index).toInt();
+	ui_.edtActionParam->hide();
+	ui_.cmbActionParam->hide();
+	switch (action) {
+		case 5:
+			ui_.edtActionParam->show();
+			break;
+		case 7:
+			ui_.cmbActionParam->clear();
+			ui_.cmbActionParam->addItems(pa_->contactProfile()->groupList());
+			ui_.cmbActionParam->show();
+			break;
+	}
+}
+
+void ContactManagerDlg::changeDomain(QList<UserListItem *>& users)
+{
+	QString domain = ui_.edtActionParam->text();
+	if (domain.size()) {
+		um->startBatch();
+		um->clear();
+		JT_Roster *r = new JT_Roster(pa_->client()->rootTask());
+		foreach (UserListItem *u, users) {
+			if (!u->jid().node().isEmpty()) {
+				r->set(u->jid().withDomain(domain), u->name(), u->groups());
+				r->remove(u->jid());
+			}
+		}
+		r->go(true);
+		pa_->client()->rosterRequest();
+	} else {
+		QMessageBox::warning(this, "Invalid", "Please fill parameter field with new domain name");
+	}
+}
+
+void ContactManagerDlg::changeGroup(QList<UserListItem *>& users)
+{
+	JT_Roster *r = new JT_Roster(pa_->client()->rootTask());
+	QStringList groups(ui_.cmbActionParam->currentText());
+
+	foreach (UserListItem *u, users) {
+		r->set(u->jid(), u->name(), groups);
+	}
+	r->go(true);
+}
+
+void ContactManagerDlg::client_rosterUpdated(bool success, int statusCode,QString statusString)
+{
+	if (success) {
+		um->reloadUsers();
+	}
+	ui_.usersView->viewport()->update();
+	Q_UNUSED(statusCode);
+	Q_UNUSED(statusString);
+	um->stopBatch();
+}
+
+void ContactManagerDlg::exportRoster(QList<UserListItem *>& users)
+{
+	QString fileName = QFileDialog::getSaveFileName(this, tr("Roster file"), QDir::homePath());
+	if (!fileName.isEmpty()) {
+		QDomDocument doc;
+		QString nick;
+		QDomElement root = doc.createElement("roster");
+		doc.appendChild(root);
+		foreach (UserListItem *u, users) {
+			QDomElement contact = root.appendChild(doc.createElement("contact")).toElement();
+			contact.setAttribute("jid", u->jid().bare());
+			foreach (QString group, u->groups()) {
+				contact.appendChild(doc.createElement("group")).appendChild(doc.createTextNode(group));
+			}
+			nick = u->name();
+			if (!nick.isEmpty()) {
+				contact.appendChild(doc.createElement("nick")).appendChild(doc.createTextNode(nick));
+			}
+		}
+		QFile file(fileName);
+		if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
+			QMessageBox::critical(this, "Save error!", QString("Can't open file %1 for writing").arg(fileName));
+			return;
+		}
+		file.write(doc.toString().toLocal8Bit());
+		file.close();
+	}
+}
+
+void ContactManagerDlg::importRoster()
+{
+	QString fileName = QFileDialog::getOpenFileName(this, tr("Roster file"), QDir::homePath());
+	if (!fileName.isEmpty()) {
+		QFile file(fileName);
+		if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
+			QMessageBox::critical(this, "Open error!", QString("Can't open file %1 for reading").arg(fileName));
+			return;
+		}
+		QDomDocument doc;
+		if (!doc.setContent(&file)) {
+			QMessageBox::critical(this, "Open error!", QString("File %1 is not xml file").arg(fileName));
+			return;
+		}
+		QDomNodeList domContacts = doc.documentElement().elementsByTagName("contact");
+		if (domContacts.isEmpty()) {
+			QMessageBox::warning(this, "Nothing to do..", QString("No contacts found in file %1").arg(fileName));
+		}
+
+		QHash<QString, QString>nicks;
+		QHash<QString, QStringList>groups;
+		QString jid, nick;
+		QStringList jids;
+		QStringList labelContent;
+		for (unsigned i=0; i<domContacts.length(); i++) {
+			QDomElement contact = domContacts.item(i).toElement();
+			jid = contact.attribute("jid");
+			jids.append(jid);
+			QDomNodeList props = contact.childNodes();
+			for (unsigned j=0; j<props.length(); j++) {
+				if (!props.item(j).isElement()) {
+					continue;
+				}
+				QDomElement el = props.item(j).toElement();
+				if (el.tagName() == "nick") {
+					nicks[jid] = el.text();
+				}
+				if (el.tagName() == "group") {
+					groups[jid].append(el.text());
+				}
+			}
+			labelContent.append(QString("%1 (%2)").arg(jid).arg(nicks[jid]));
+		}
+		
+		QMessageBox confirmDlg(
+				QMessageBox::Question,tr("Confirm contacts importing"),
+				tr("Do you really want to import these contacts?"), QMessageBox::Cancel | QMessageBox::Yes);
+		confirmDlg.setDetailedText(labelContent.join("\n"));
+		if (confirmDlg.exec() == QMessageBox::Yes) {
+			um->startBatch();
+			um->clear();
+			JT_Roster *r = new JT_Roster(pa_->client()->rootTask());
+			foreach (QString jid, jids) {
+				r->set(Jid(jid), nicks[jid], groups[jid]);
+			}
+			r->go(true);
+			pa_->client()->rosterRequest(); // через ж.., но пускай пока так.
+		}
+		file.close();
+	}
+}
diff -urN -X psidiff.ignore sources/src/contactmanager/contactmanagerdlg.h work/src/contactmanager/contactmanagerdlg.h
--- sources/src/contactmanager/contactmanagerdlg.h	1970-01-01 05:00:00.000000000 +0500
+++ work/src/contactmanager/contactmanagerdlg.h	2009-07-18 11:13:39.000000000 +0600
@@ -0,0 +1,40 @@
+#ifndef CONTACTMANAGERDLG_H
+#define CONTACTMANAGERDLG_H
+#include "ui_contactmanagerdlg.h"
+
+#include <QtGui/QDialog>
+#include "contactmanagermodel.h"
+class PsiAccount;
+
+namespace Ui {
+    class ContactManagerDlg;
+}
+
+class ContactManagerDlg : public QDialog {
+    Q_OBJECT
+    Q_DISABLE_COPY(ContactManagerDlg)
+public:
+	explicit ContactManagerDlg(PsiAccount *pa);
+    virtual ~ContactManagerDlg();
+
+protected:
+    virtual void changeEvent(QEvent *e);
+
+private:
+	void changeDomain(QList<UserListItem *>& users);
+	void changeGroup(QList<UserListItem *>& users);
+	void exportRoster(QList<UserListItem *>& users);
+	void importRoster();
+
+	Ui::ContactManagerDlg ui_;
+	PsiAccount *pa_;
+	ContactManagerModel *um;
+
+private slots:
+	void doSelect();
+	void executeCurrent();
+	void showParamField(int index);
+	void client_rosterUpdated(bool,int,QString);
+};
+
+#endif // CONTACTMANAGERDLG_H
diff -urN -X psidiff.ignore sources/src/contactmanager/contactmanagerdlg.ui work/src/contactmanager/contactmanagerdlg.ui
--- sources/src/contactmanager/contactmanagerdlg.ui	1970-01-01 05:00:00.000000000 +0500
+++ work/src/contactmanager/contactmanagerdlg.ui	2009-07-12 15:54:22.000000000 +0600
@@ -0,0 +1,203 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>ContactManagerDlg</class>
+ <widget class="QDialog" name="ContactManagerDlg">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>600</width>
+    <height>390</height>
+   </rect>
+  </property>
+  <property name="sizePolicy">
+   <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+    <horstretch>0</horstretch>
+    <verstretch>0</verstretch>
+   </sizepolicy>
+  </property>
+  <property name="windowTitle">
+   <string>Contacts Manager</string>
+  </property>
+  <layout class="QVBoxLayout" name="verticalLayout">
+   <item>
+    <widget class="ContactManagerView" name="usersView">
+     <property name="selectionBehavior">
+      <enum>QAbstractItemView::SelectRows</enum>
+     </property>
+     <property name="sortingEnabled">
+      <bool>true</bool>
+     </property>
+     <attribute name="horizontalHeaderCascadingSectionResizes">
+      <bool>true</bool>
+     </attribute>
+    </widget>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout">
+     <item>
+      <widget class="QLabel" name="lblField">
+       <property name="text">
+        <string>Field</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="cmbField"/>
+     </item>
+     <item>
+      <widget class="QComboBox" name="cmbMatchType"/>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="edtMatch">
+       <property name="maximumSize">
+        <size>
+         <width>32000</width>
+         <height>16777215</height>
+        </size>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="btnSelect">
+       <property name="sizePolicy">
+        <sizepolicy hsizetype="Minimum" vsizetype="Minimum">
+         <horstretch>0</horstretch>
+         <verstretch>0</verstretch>
+        </sizepolicy>
+       </property>
+       <property name="maximumSize">
+        <size>
+         <width>32</width>
+         <height>16777215</height>
+        </size>
+       </property>
+       <property name="text">
+        <string>+/-</string>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <layout class="QHBoxLayout" name="horizontalLayout_2">
+     <item>
+      <widget class="QLabel" name="lblAction">
+       <property name="text">
+        <string>Action</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="cbAction"/>
+     </item>
+     <item>
+      <widget class="QLineEdit" name="edtActionParam">
+       <property name="whatsThis">
+        <string extracomment="Action's parameter"/>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QComboBox" name="cmbActionParam">
+       <property name="editable">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <widget class="QPushButton" name="btnExecute">
+       <property name="text">
+        <string>Execute</string>
+       </property>
+      </widget>
+     </item>
+     <item>
+      <spacer name="horizontalSpacer">
+       <property name="orientation">
+        <enum>Qt::Horizontal</enum>
+       </property>
+       <property name="sizeHint" stdset="0">
+        <size>
+         <width>40</width>
+         <height>20</height>
+        </size>
+       </property>
+      </spacer>
+     </item>
+    </layout>
+   </item>
+   <item>
+    <widget class="QDialogButtonBox" name="buttonBox">
+     <property name="orientation">
+      <enum>Qt::Horizontal</enum>
+     </property>
+     <property name="standardButtons">
+      <set>QDialogButtonBox::Close</set>
+     </property>
+     <property name="centerButtons">
+      <bool>false</bool>
+     </property>
+    </widget>
+   </item>
+  </layout>
+ </widget>
+ <customwidgets>
+  <customwidget>
+   <class>ContactManagerView</class>
+   <extends>QTableView</extends>
+   <header>contactmanager/contactmanagerview.h</header>
+  </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>accepted()</signal>
+   <receiver>ContactManagerDlg</receiver>
+   <slot>accept()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>257</x>
+     <y>380</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>157</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>buttonBox</sender>
+   <signal>rejected()</signal>
+   <receiver>ContactManagerDlg</receiver>
+   <slot>reject()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>325</x>
+     <y>380</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>286</x>
+     <y>274</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>edtMatch</sender>
+   <signal>returnPressed()</signal>
+   <receiver>btnSelect</receiver>
+   <slot>click()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>343</x>
+     <y>292</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>386</x>
+     <y>302</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff -urN -X psidiff.ignore sources/src/contactmanager/contactmanagermodel.cpp work/src/contactmanager/contactmanagermodel.cpp
--- sources/src/contactmanager/contactmanagermodel.cpp	1970-01-01 05:00:00.000000000 +0500
+++ work/src/contactmanager/contactmanagermodel.cpp	2009-07-12 23:35:35.000000000 +0600
@@ -0,0 +1,279 @@
+#include "contactmanagermodel.h"
+#include "psiaccount.h"
+#include "userlist.h"
+#include "xmpp_tasks.h"
+#include "QDebug"
+
+
+ContactManagerModel::ContactManagerModel(QObject * parent, PsiAccount *pa) :
+		QAbstractTableModel(parent),
+		pa_(pa)
+{
+	columnNames
+			<<""
+			<<tr("Nick")
+			<<tr("Group")
+			<<tr("Node")
+			<<tr("Domain")
+			<<tr("Subscription");
+	roles	<<CheckRole
+			<<NickRole
+			<<GroupRole
+			<<NodeRole
+			<<DomainRole
+			<<SubscriptionRole;
+	connect(pa_, SIGNAL(updateContact(UserListItem)), this, SLOT(view_contactUpdated(UserListItem)));
+	connect(pa_->client(), SIGNAL(rosterItemUpdated(const RosterItem &)), this, SLOT(client_rosterItemUpdated(const RosterItem &)));
+}
+
+void ContactManagerModel::reloadUsers()
+{
+	clear();
+	UserList *ul = pa_->userList();
+	foreach (UserListItem *u, *ul) {
+		if (u->inList()) {
+			addContact(u);
+		}
+	}
+	reset();
+}
+
+void ContactManagerModel::clear()
+{
+	_userList.clear();
+	checks.clear();
+}
+
+int ContactManagerModel::rowCount(const QModelIndex &parent) const
+{
+	if (parent.isValid()) return 0;
+	return _userList.count();
+}
+
+int ContactManagerModel::columnCount ( const QModelIndex & parent) const
+{
+	Q_UNUSED(parent);
+	return columnNames.count();
+}
+
+QVariant ContactManagerModel::data(const QModelIndex &index, int role) const
+{
+	Role columnRole = roles[index.column()];
+	UserListItem *u = _userList.at(index.row());
+	if (u) {
+		switch (columnRole) {
+			case CheckRole: //checkbox
+				if (role == Qt::CheckStateRole) {
+					return checks.contains(u->jid().full())?2:0;
+				} else if (role == Qt::TextAlignmentRole) {
+					return (int)(Qt::AlignRight | Qt::AlignVCenter);
+				}
+				break;
+			case NodeRole:
+				if (role == Qt::TextAlignmentRole) {
+					return (int)(Qt::AlignRight | Qt::AlignVCenter);
+				}
+			default:
+				if (role == Qt::DisplayRole) {
+					return userFieldString(u, columnRole);
+				}
+		}
+	}
+	return QVariant();
+}
+
+QString ContactManagerModel::userFieldString(UserListItem *u, ContactManagerModel::Role columnRole) const
+{
+	QString data;
+	switch (columnRole) {
+		case NodeRole: //node
+			data = u->jid().node();
+			break;
+		case DomainRole: //domain
+			data = u->jid().domain();
+			break;
+		case NickRole: //nick
+			data = u->name();
+			break;
+		case GroupRole: //group
+			if (u->groups().isEmpty()) {
+				data = "";
+			} else {
+				data = u->groups().first();
+			}
+			break;
+		case SubscriptionRole: //subscription
+			data = u->subscription().toString();
+			break;
+	}
+	return data;
+}
+
+QVariant ContactManagerModel::headerData(int section, Qt::Orientation orientation,
+					 int role) const
+{
+	if (role == Qt::DisplayRole) {
+		if (orientation == Qt::Horizontal) {
+			return columnNames[section];
+		} else {
+			return section+1;
+		}
+	}
+	return QVariant();
+}
+
+
+QStringList ContactManagerModel::manageableFields()
+{
+	QStringList ret = columnNames;
+	ret.removeFirst();
+	return ret;
+}
+
+void ContactManagerModel::addContact(UserListItem *u)
+{
+	_userList.append(u);
+}
+
+Qt::ItemFlags ContactManagerModel::flags ( const QModelIndex & index ) const
+{
+	Qt::ItemFlags flags = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
+	Role columnRole = roles[index.column()];
+	if ( columnRole == CheckRole ) {
+		flags |= ( Qt::ItemIsUserCheckable );
+	}
+	return flags;
+}
+
+bool ContactManagerModel::setData ( const QModelIndex & index, const QVariant & value, int role )
+{
+	Q_UNUSED(role);
+	if (index.isValid()) {
+		Role columnRole = roles[index.column()];
+		if ( columnRole == CheckRole ) {
+			QString jid = _userList.at(index.row())->jid().full();
+			if (value.toInt() == 3) { //iversion
+				if (checks.contains(jid)) {
+					checks.remove(jid);
+				} else {
+					checks.insert(jid);
+				}
+				emit dataChanged ( index, index );
+			} else {
+				if (checks.contains(jid)) {
+					if (!value.toBool()) {
+						checks.remove(jid);
+						emit dataChanged ( index, index );
+					}
+				} else {
+					if (value.toBool()) {
+						checks.insert(jid);
+						emit dataChanged ( index, index );
+					}
+				}
+			}
+		}
+	}
+	return false;
+}
+
+ContactManagerModel::Role ContactManagerModel::sortRole;
+Qt::SortOrder ContactManagerModel::sortOrder = Qt::AscendingOrder;
+
+void ContactManagerModel::sort ( int column, Qt::SortOrder order = Qt::AscendingOrder )
+{
+	Role columnRole = roles[column];
+	ContactManagerModel::sortRole = columnRole;
+	ContactManagerModel::sortOrder = order;
+	if (columnRole != CheckRole) {
+		emit layoutAboutToBeChanged();
+		qSort(_userList.begin(), _userList.end(), ContactManagerModel::sortLessThan);
+		emit layoutChanged();
+	}
+}
+
+bool ContactManagerModel::sortLessThan(UserListItem *u1, UserListItem *u2)
+{
+	QString g1, g2;
+	bool result = false;
+	switch (ContactManagerModel::sortRole) {
+		case NodeRole: //node
+			result = u1->jid().node() < u2->jid().node();
+			break;
+		case DomainRole: //domain
+			result = u1->jid().domain() < u2->jid().domain();
+			break;
+		case NickRole: //nick
+			result = u1->name() < u2->name();
+			break;
+		case GroupRole: //group
+			if (!u1->groups().isEmpty()) {
+				g1 = u1->groups().first();
+			}
+			if (!u2->groups().isEmpty()) {
+				g2 = u2->groups().first();
+			}
+			result = g1 < g2;
+			break;
+		case SubscriptionRole: //subscription
+			result = u1->subscription().toString() < u2->subscription().toString();
+			break;
+	}
+	return ContactManagerModel::sortOrder == Qt::AscendingOrder? result : !result;
+}
+
+QList<UserListItem *> ContactManagerModel::checkedUsers()
+{
+	QList<UserListItem *> users;
+	foreach (UserListItem *u, _userList) {
+		if (checks.contains(u->jid().full())) {
+			users.append(u);
+		}
+	}
+	return users;
+}
+
+void ContactManagerModel::invertByMatch(int columnIndex, int matchType, const QString &str)
+{
+	emit layoutAboutToBeChanged();
+	Role columnRole = roles[columnIndex];
+	QString data;
+	QRegExp reg;
+	if (matchType == ContactManagerModel::RegexpMatch) {
+		reg = QRegExp(str);
+	}
+	foreach (UserListItem *u, _userList) {
+		data = userFieldString(u, columnRole);
+		if ((matchType == ContactManagerModel::SimpleMatch && str == data) ||
+			(matchType == ContactManagerModel::RegexpMatch && reg.indexIn(data) != -1)) {
+			QString jid = u->jid().full();
+			if (checks.contains(jid)) {
+				checks.remove(jid);
+			} else {
+				checks.insert(jid);
+			}
+		}
+	}
+	emit layoutChanged();
+}
+
+void ContactManagerModel::view_contactUpdated(const UserListItem &u)
+{
+	contactUpdated(u.jid());
+}
+
+void ContactManagerModel::client_rosterItemUpdated(const RosterItem &item)
+{
+	contactUpdated(item.jid());
+}
+
+void  ContactManagerModel::contactUpdated(const Jid &jid)
+{
+	int i = 0;
+	foreach (UserListItem *lu, _userList) {
+		if (lu->jid() == jid) {
+			dataChanged(index(i, 1), index(i, columnNames.count()-1));
+		}
+		i++;
+	}
+}
diff -urN -X psidiff.ignore sources/src/contactmanager/contactmanagermodel.h work/src/contactmanager/contactmanagermodel.h
--- sources/src/contactmanager/contactmanagermodel.h	1970-01-01 05:00:00.000000000 +0500
+++ work/src/contactmanager/contactmanagermodel.h	2009-07-13 22:08:29.000000000 +0600
@@ -0,0 +1,73 @@
+#ifndef CONTACTMANAGERMODEL_H
+#define CONTACTMANAGERMODEL_H
+
+#include <QAbstractTableModel>
+#include <QStringList>
+#include <QSet>
+
+class UserListItem;
+class PsiAccount;
+namespace XMPP
+{
+	class RosterItem;
+	class Jid;
+}
+using namespace XMPP;
+
+class ContactManagerModel : public QAbstractTableModel
+{
+	Q_OBJECT
+public:
+	enum Role {
+		// DisplayRole / EditRole
+		CheckRole,
+		NickRole,
+		GroupRole,
+		NodeRole,
+		DomainRole,
+		SubscriptionRole
+	};
+	const static int SimpleMatch = 1;
+	const static int RegexpMatch = 2;
+
+	ContactManagerModel(QObject * parent, PsiAccount *pa);
+	int rowCount(const QModelIndex &parent = QModelIndex()) const;
+	int columnCount ( const QModelIndex & parent = QModelIndex() ) const;
+	QVariant data(const QModelIndex &index, int role) const;
+	QVariant headerData(int section, Qt::Orientation orientation,
+						 int role = Qt::DisplayRole) const;
+	Qt::ItemFlags flags ( const QModelIndex & index ) const;
+	bool setData ( const QModelIndex & index, const QVariant & value, int role = Qt::EditRole );
+	void sort ( int column, Qt::SortOrder order );
+	static bool sortLessThan(UserListItem *u1, UserListItem *u2);
+	static Role sortRole;
+	static Qt::SortOrder sortOrder;
+
+
+	QStringList manageableFields();
+	void reloadUsers();
+	void clear();
+	void addContact(UserListItem *u);
+	QList<UserListItem *> checkedUsers();
+	void invertByMatch(int columnIndex, int matchType, const QString &str);
+
+	void startBatch() { layoutAboutToBeChanged(); }
+	void stopBatch() { layoutChanged(); }
+
+
+private:
+	PsiAccount *pa_;
+	QList<UserListItem *> _userList;
+	QStringList columnNames;
+	QList<Role> roles;
+	QSet<QString> checks;
+
+	QString userFieldString(UserListItem *u, ContactManagerModel::Role columnRole) const;
+	void contactUpdated(const Jid &);
+
+private slots:
+	void view_contactUpdated(const UserListItem &);
+	void client_rosterItemUpdated(const RosterItem &);
+};
+
+#endif // CONTACTMANAGERMODEL_H
diff -urN -X psidiff.ignore sources/src/contactmanager/contactmanager.pri work/src/contactmanager/contactmanager.pri
--- sources/src/contactmanager/contactmanager.pri	1970-01-01 05:00:00.000000000 +0500
+++ work/src/contactmanager/contactmanager.pri	2009-07-12 15:54:22.000000000 +0600
@@ -0,0 +1,7 @@
+HEADERS += $$PWD/contactmanagerdlg.h \
+    $$PWD/contactmanagermodel.h \
+	$$PWD/contactmanagerview.h
+SOURCES += $$PWD/contactmanagerdlg.cpp \
+    $$PWD/contactmanagermodel.cpp \
+	$$PWD/contactmanagerview.cpp
+FORMS += $$PWD/contactmanagerdlg.ui
diff -urN -X psidiff.ignore sources/src/contactmanager/contactmanagerview.cpp work/src/contactmanager/contactmanagerview.cpp
--- sources/src/contactmanager/contactmanagerview.cpp	1970-01-01 05:00:00.000000000 +0500
+++ work/src/contactmanager/contactmanagerview.cpp	2009-07-12 22:21:02.000000000 +0600
@@ -0,0 +1,72 @@
+#include <QMenu>
+#include <QContextMenuEvent>
+#include <QtGui/QHeaderView>
+
+#include "contactmanagerview.h"
+#include "psiiconset.h"
+
+ContactManagerView::ContactManagerView( QWidget * parent )
+		: QTableView(parent)
+{
+
+}
+
+void ContactManagerView::init()
+{
+	resizeColumnsToContents();
+	horizontalHeader()->setResizeMode(0, QHeaderView::ResizeToContents);
+	horizontalHeader()->setStretchLastSection(true);
+	horizontalHeader()->setSortIndicator(-1, Qt::AscendingOrder);
+	verticalHeader()->setDefaultAlignment( Qt::AlignHCenter );
+
+	connect(horizontalHeader(), SIGNAL(sectionClicked(int)), this, SLOT(sortByColumn(int)));
+}
+
+void ContactManagerView::contextMenuEvent( QContextMenuEvent * e )
+{
+	QMenu *popup = new QMenu(this);
+	QList<QAction *> actions;
+	actions <<new QAction(IconsetFactory::icon("psiplus/cm_check").icon(), tr("Check"), popup)
+			<<new QAction(IconsetFactory::icon("psiplus/cm_uncheck").icon(), tr("Uncheck"), popup)
+			<<new QAction(IconsetFactory::icon("psiplus/cm_invertcheck").icon(), tr("Invert"), popup);
+	popup->addActions(actions);
+	QAction *result = popup->exec(e->globalPos());
+	int iresult;
+	if (result) {
+		iresult = actions.indexOf(result);
+		const QVariant value(2);
+		foreach(const QModelIndex &check, selectionModel()->selectedRows(0)) {
+			switch (iresult) {
+				case 0: //check
+					model()->setData(check, 2);
+					break;
+				case 1: //uncheck
+					model()->setData(check, 0);
+					break;
+				case 2: //invert
+					model()->setData(check, 3);
+					break;
+			}
+		}
+	}
+	delete popup;
+}
+
+void ContactManagerView::keyPressEvent( QKeyEvent * e )
+{
+	if (e->key() == Qt::Key_Space) {
+		int data = 2; //check
+		if (e->modifiers() & Qt::ControlModifier) {
+			data = 3; //invert
+		} else if (e->modifiers() & Qt::ShiftModifier) {
+			data = 0; //uncheck
+		}
+		foreach(const QModelIndex &check, selectionModel()->selectedRows(0)) {
+			model()->setData(check, data);
+		}
+		e->accept();
+	} else {
+		QTableView::keyPressEvent(e);
+		e->ignore();
+	}
+}
diff -urN -X psidiff.ignore sources/src/contactmanager/contactmanagerview.h work/src/contactmanager/contactmanagerview.h
--- sources/src/contactmanager/contactmanagerview.h	1970-01-01 05:00:00.000000000 +0500
+++ work/src/contactmanager/contactmanagerview.h	2009-07-12 22:02:32.000000000 +0600
@@ -0,0 +1,17 @@
+#ifndef CONTACTMANAGERVIEW_H
+#define CONTACTMANAGERVIEW_H
+
+#include <QTableView>
+
+class ContactManagerView : public QTableView
+{
+public:
+	ContactManagerView( QWidget * parent = 0 );
+	void init();
+
+protected:
+	void contextMenuEvent( QContextMenuEvent * e );
+	void keyPressEvent( QKeyEvent * e );
+};
+
+#endif // CONTACTMANAGERVIEW_H
diff -urN -X psidiff.ignore sources/src/contactview.cpp work/src/contactview.cpp
--- sources/src/contactview.cpp	2009-06-29 22:25:39.000000000 +0600
+++ work/src/contactview.cpp	2009-07-12 15:54:22.000000000 +0600
@@ -61,6 +61,7 @@
 #include "textutil.h"
 #include "bookmarkmanagedlg.h"
 #include "bookmarkmanager.h"
+#include "contactmanager/contactmanagerdlg.h"
 #include "groupchatdlg.h"
 #include "tabdlg.h"
 #include "statusdlg.h"
@@ -946,7 +947,7 @@ void ContactProfile::doContextMenu(Conta
 		am->insertItem(/*IconsetFactory::iconPixmap("psi/edit/clear"),*/ tr("Update MOTD"), 3);
 		am->insertItem(IconsetFactory::icon("psi/remove").icon(), tr("Delete MOTD"), 4);
 
-		const int status_start = 15;
+		const int status_start = 17;
 		Q3PopupMenu *sm = new Q3PopupMenu(&pm);
 		sm->insertItem(PsiIconset::instance()->status(STATUS_ONLINE).icon(),	status2txt(STATUS_ONLINE),	STATUS_ONLINE		+ status_start);
 		if (PsiOptions::instance()->getOption("options.ui.menu.status.chat").toBool())
@@ -998,6 +999,8 @@ void ContactProfile::doContextMenu(Conta
 		pm.insertItem(IconsetFactory::icon("psi/disco").icon(), tr("Service &Discovery"), 9);
 		if (PsiOptions::instance()->getOption("options.ui.message.enabled").toBool())
 			pm.insertItem(IconsetFactory::icon("psi/sendMessage").icon(), tr("New &Blank Message"), 6);
+		pm.insertItem(IconsetFactory::icon("psiplus/action_contacts_manager").icon(), tr("&Contacts Manager"), 16);
+		pm.setItemEnabled(16, d->pa->isAvailable());
 		pm.insertSeparator();
 		pm.insertItem(IconsetFactory::icon("psi/xml").icon(), tr("&XML Console"), 10);
 		pm.insertSeparator();
@@ -1063,6 +1066,15 @@ void ContactProfile::doContextMenu(Conta
 		else if(x == 14  && pm.isItemEnabled(15)) {
 			emit actionUnsetAvatar();
 		}
+		else if(x == 16) {
+			ContactManagerDlg *dlg = d->pa->findDialog<ContactManagerDlg*>();
+			if(dlg) {
+				bringToFront(dlg);
+			} else {
+				dlg = new ContactManagerDlg(d->pa);
+				dlg->show();
+			}
+		}
 		else if(x >= status_start && x <= STATUS_CHAT + status_start) { // STATUS_CHAT is the highest value of the states
 			int status = x - status_start;
 			d->pa->changeStatus(status);
diff -urN -X psidiff.ignore sources/src/contactview.h work/src/contactview.h
--- sources/src/contactview.h	2009-06-29 22:25:39.000000000 +0600
+++ work/src/contactview.h	2009-07-12 15:54:22.000000000 +0600
@@ -85,6 +85,7 @@
 
 	QString makeTip(bool trim, bool doLinkify) const;
 
+	QStringList groupList() const;
 	ContactViewItem *checkGroup(int type);
 	ContactViewItem *checkGroup(const QString &name);
 
@@ -170,7 +171,6 @@
 	int contactSizeFromGroup(const QString &groupName) const;
 
 	void updateGroupInfo(ContactViewItem *group);
-	QStringList groupList() const;
 
 	void deferredUpdateGroups();
 
diff -urN -X psidiff.ignore sources/src/src.pri work/src/src.pri
--- sources/src/src.pri	2009-06-29 22:25:39.000000000 +0600
+++ work/src/src.pri	2009-07-12 15:54:22.000000000 +0600
@@ -8,6 +8,7 @@
 include($$PWD/utilities/utilities.pri)
 include($$PWD/tabs/tabs.pri)
 include($$PWD/Certificates/Certificates.pri)
+include($$PWD/contactmanager/contactmanager.pri)
 
 # tools
 include($$PWD/tools/trayicon/trayicon.pri)
