From 379da6cd11e76e71a286c6da44cec3ee2c66a97b Mon Sep 17 00:00:00 2001 From: Michail Pishchagin Date: Tue, 2 Feb 2010 00:59:25 +0700 Subject: [PATCH] * New contact list (roster animations currently are not supported, please disable them until the proper implementation is provided). * Added timeouts for account reconnects. * Minor improvements / refactorings. --- src/contactlistaccountgroup.cpp | 186 ++++ src/contactlistaccountgroup.h | 69 ++ src/contactlistaccountmenu.cpp | 340 +++++++ src/contactlistaccountmenu.h | 40 + src/contactlistdragmodel.cpp | 682 +++++++++++++ src/contactlistdragmodel.h | 125 +++ src/contactlistdragview.cpp | 919 ++++++++++++++++++ src/contactlistdragview.h | 158 ++++ src/contactlistgroup.cpp | 491 ++++++++++ src/contactlistgroup.h | 134 +++ src/contactlistgroupcache.cpp | 100 ++ src/contactlistgroupcache.h | 53 ++ src/contactlistgroupmenu.cpp | 176 ++++ src/contactlistgroupmenu.h | 48 + src/contactlistgroupstate.cpp | 227 +++++ src/contactlistgroupstate.h | 73 ++ src/contactlistitem.cpp | 104 ++ src/contactlistitem.h | 65 ++ src/contactlistitemmenu.cpp | 78 ++ src/contactlistitemmenu.h | 53 ++ src/contactlistitemproxy.cpp | 37 + src/contactlistitemproxy.h | 44 + src/contactlistmodel.cpp | 878 +++++++++++++++++ src/contactlistmodel.h | 216 +++++ src/contactlistmodelselection.cpp | 232 +++++ src/contactlistmodelselection.h | 85 ++ src/contactlistmodelupdater.cpp | 198 ++++ src/contactlistmodelupdater.h | 87 ++ src/contactlistnestedgroup.cpp | 303 ++++++ src/contactlistnestedgroup.h | 53 ++ src/contactlistproxymodel.cpp | 145 +++ src/contactlistproxymodel.h | 50 + src/contactlistspecialgroup.cpp | 112 +++ src/contactlistspecialgroup.h | 52 + src/contactlistutil.cpp | 275 ++++++ src/contactlistutil.h | 48 + src/contactlistview.cpp | 518 ++++++++++ src/contactlistview.h | 106 +++ src/contactlistviewdelegate.cpp | 431 +++++++++ src/contactlistviewdelegate.h | 102 ++ src/contactupdatesmanager.cpp | 167 ++++ src/contactupdatesmanager.h | 74 ++ src/contactview.cpp | 19 +- src/dummystream.cpp | 23 + src/dummystream.h | 48 + src/eventdlg.cpp | 23 +- src/eventdlg.h | 2 + src/filetransdlg.cpp | 2 +- src/globaleventqueue.cpp | 83 ++ src/globaleventqueue.h | 56 ++ src/groupchatdlg.cpp | 4 +- src/hoverabletreeview.cpp | 154 +++ src/hoverabletreeview.h | 79 ++ src/legacypsiaccount.cpp | 138 +++ src/legacypsiaccount.h | 50 + src/mainwin.cpp | 191 +++- src/mainwin.h | 18 +- src/miniclient.cpp | 5 +- src/profiles.h | 1 + src/psi_profiles.cpp | 24 +- src/psiaccount.cpp | 1312 +++++++++++++++++++++----- src/psiaccount.h | 112 ++- src/psiactions.h | 7 +- src/psicon.cpp | 277 ++++-- src/psicon.h | 35 +- src/psicontact.cpp | 1046 ++++++++++++++++++++ src/psicontact.h | 188 ++++ src/psicontactlist.cpp | 270 +++++- src/psicontactlist.h | 68 +- src/psicontactlistmodel.cpp | 73 ++ src/psicontactlistmodel.h | 42 + src/psicontactlistview.cpp | 40 + src/psicontactlistview.h | 40 + src/psicontactlistviewdelegate.cpp | 268 ++++++ src/psicontactlistviewdelegate.h | 61 ++ src/psicontactmenu.cpp | 777 +++++++++++++++ src/psicontactmenu.h | 48 + src/psievent.cpp | 344 +++++-- src/psievent.h | 24 +- src/psiselfcontact.cpp | 73 ++ src/psiselfcontact.h | 43 + src/removeconfirmationmessagebox.cpp | 319 +++++++ src/removeconfirmationmessagebox.h | 125 +++ src/resourcemenu.cpp | 96 +- src/resourcemenu.h | 29 +- src/src.pri | 155 ++- src/statusmenu.cpp | 87 ++ src/statusmenu.h | 51 + src/tabs/tabbablewidget.cpp | 6 +- src/tabs/tabbablewidget.h | 5 +- src/tabs/tabdlg.cpp | 4 +- src/tabs/tabdlg.h | 2 +- src/widgets/iconaction.cpp | 31 + src/widgets/iconaction.h | 3 + 94 files changed, 14771 insertions(+), 544 deletions(-) create mode 100644 src/contactlistaccountgroup.cpp create mode 100644 src/contactlistaccountgroup.h create mode 100644 src/contactlistaccountmenu.cpp create mode 100644 src/contactlistaccountmenu.h create mode 100644 src/contactlistdragmodel.cpp create mode 100644 src/contactlistdragmodel.h create mode 100644 src/contactlistdragview.cpp create mode 100644 src/contactlistdragview.h create mode 100755 src/contactlistgroup.cpp create mode 100755 src/contactlistgroup.h create mode 100644 src/contactlistgroupcache.cpp create mode 100644 src/contactlistgroupcache.h create mode 100755 src/contactlistgroupmenu.cpp create mode 100755 src/contactlistgroupmenu.h create mode 100644 src/contactlistgroupstate.cpp create mode 100644 src/contactlistgroupstate.h create mode 100644 src/contactlistitem.cpp create mode 100644 src/contactlistitem.h create mode 100644 src/contactlistitemmenu.cpp create mode 100644 src/contactlistitemmenu.h create mode 100644 src/contactlistitemproxy.cpp create mode 100644 src/contactlistitemproxy.h create mode 100755 src/contactlistmodel.cpp create mode 100644 src/contactlistmodel.h create mode 100644 src/contactlistmodelselection.cpp create mode 100644 src/contactlistmodelselection.h create mode 100644 src/contactlistmodelupdater.cpp create mode 100644 src/contactlistmodelupdater.h create mode 100644 src/contactlistnestedgroup.cpp create mode 100644 src/contactlistnestedgroup.h create mode 100644 src/contactlistproxymodel.cpp create mode 100644 src/contactlistproxymodel.h create mode 100644 src/contactlistspecialgroup.cpp create mode 100644 src/contactlistspecialgroup.h create mode 100644 src/contactlistutil.cpp create mode 100644 src/contactlistutil.h create mode 100644 src/contactlistview.cpp create mode 100644 src/contactlistview.h create mode 100755 src/contactlistviewdelegate.cpp create mode 100755 src/contactlistviewdelegate.h create mode 100644 src/contactupdatesmanager.cpp create mode 100644 src/contactupdatesmanager.h create mode 100644 src/dummystream.cpp create mode 100644 src/dummystream.h create mode 100755 src/globaleventqueue.cpp create mode 100755 src/globaleventqueue.h create mode 100644 src/hoverabletreeview.cpp create mode 100644 src/hoverabletreeview.h create mode 100644 src/legacypsiaccount.cpp create mode 100644 src/legacypsiaccount.h create mode 100755 src/psicontact.cpp create mode 100755 src/psicontact.h create mode 100755 src/psicontactlistmodel.cpp create mode 100755 src/psicontactlistmodel.h create mode 100755 src/psicontactlistview.cpp create mode 100755 src/psicontactlistview.h create mode 100644 src/psicontactlistviewdelegate.cpp create mode 100644 src/psicontactlistviewdelegate.h create mode 100644 src/psicontactmenu.cpp create mode 100644 src/psicontactmenu.h create mode 100755 src/psiselfcontact.cpp create mode 100755 src/psiselfcontact.h create mode 100644 src/removeconfirmationmessagebox.cpp create mode 100644 src/removeconfirmationmessagebox.h create mode 100755 src/statusmenu.cpp create mode 100755 src/statusmenu.h diff --git a/src/contactlistaccountgroup.cpp b/src/contactlistaccountgroup.cpp new file mode 100644 index 000000000..03534542f --- /dev/null +++ b/src/contactlistaccountgroup.cpp @@ -0,0 +1,186 @@ +/* + * contactlistaccountgroup.h + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistaccountgroup.h" + +#include + +#include "psicontact.h" +#include "psiaccount.h" +#include "contactlistitemproxy.h" +#include "psiselfcontact.h" +#include "contactlistaccountmenu.h" +#include "contactlistgroupcache.h" + +ContactListAccountGroup::ContactListAccountGroup(ContactListModel* model, ContactListGroup* parent, PsiAccount* account) + : ContactListNestedGroup(model, parent, QString()) + , isRoot_(!account) + , account_(account) +{ + if (account_) { + model->groupCache()->addGroup(this); + connect(account_, SIGNAL(destroyed(QObject*)), SLOT(accountUpdated())); + connect(account_, SIGNAL(updatedAccount()), SLOT(accountUpdated())); + } +} + +ContactListAccountGroup::~ContactListAccountGroup() +{ + clearGroup(); +} + +void ContactListAccountGroup::clearGroup() +{ + foreach(ContactListAccountGroup* account, accounts_) { + removeItem(findGroup(account)); + } + qDeleteAll(accounts_); + accounts_.clear(); + + ContactListNestedGroup::clearGroup(); +} + +PsiAccount* ContactListAccountGroup::account() const +{ + return account_; +} + +ContactListModel::Type ContactListAccountGroup::type() const +{ + return ContactListModel::AccountType; +} + +void ContactListAccountGroup::addContact(PsiContact* contact, QStringList contactGroups) +{ + Q_ASSERT(contact); + if (isRoot()) { + ContactListAccountGroup* accountGroup = findAccount(contact->account()); + if (!accountGroup) { + accountGroup = new ContactListAccountGroup(model(), this, contact->account()); + accounts_.append(accountGroup); + addItem(new ContactListItemProxy(this, accountGroup)); + accountGroup->addContact(accountGroup->account()->selfContact(), QStringList() << QString()); + } + + accountGroup->addContact(contact, contactGroups); + } + else { + if (model()->groupsEnabled() && !contact->isSelf()) { + ContactListNestedGroup::addContact(contact, contactGroups); + } + else { + ContactListGroup::addContact(contact, contactGroups); + } + } +} + +void ContactListAccountGroup::contactGroupsChanged(PsiContact* contact, QStringList contactGroups) +{ + if (isRoot()) { + ContactListAccountGroup* accountGroup = findAccount(contact->account()); + if (accountGroup) { + accountGroup->contactGroupsChanged(contact, contactGroups); + } + } + else { + if (model()->groupsEnabled() && !contact->isSelf()) { + ContactListNestedGroup::contactGroupsChanged(contact, contactGroups); + } + else { + ContactListGroup::contactGroupsChanged(contact, contactGroups); + } + } +} + +bool ContactListAccountGroup::isRoot() const +{ + return isRoot_; +} + +ContactListAccountGroup* ContactListAccountGroup::findAccount(PsiAccount* account) const +{ + Q_ASSERT(isRoot()); + foreach(ContactListAccountGroup* accountGroup, accounts_) + if (accountGroup->account() == account) + return accountGroup; + + return 0; +} + +void ContactListAccountGroup::removeAccount(ContactListAccountGroup* accountGroup) +{ + Q_ASSERT(accountGroup); + accounts_.removeAll(accountGroup); + removeItem(ContactListGroup::findGroup(accountGroup)); + accountGroup->deleteLater(); +} + +void ContactListAccountGroup::accountUpdated() +{ + Q_ASSERT(!isRoot()); + ContactListAccountGroup* root = dynamic_cast(parent()); + Q_ASSERT(root); + + model()->updatedItem(root->findGroup(this)); + + if (account_.isNull() || !account_->enabled()) { + clearGroup(); + root->removeAccount(this); + } +} + +const QString& ContactListAccountGroup::displayName() const +{ + if (account_) { + return account_->name(); + } + + static QString emptyName; + return emptyName; +} + +QString ContactListAccountGroup::internalGroupName() const +{ + if (account_) { + return account_->id(); + } + + return QString(); +} + +ContactListItemProxy* ContactListAccountGroup::findGroup(ContactListGroup* group) const +{ + return ContactListGroup::findGroup(group); +} + +ContactListItemMenu* ContactListAccountGroup::contextMenu(ContactListModel* model) +{ + return new ContactListAccountMenu(this, model); +} + +bool ContactListAccountGroup::isEditable() const +{ + return true; +} + +bool ContactListAccountGroup::canContainSpecialGroups() const +{ + return !isRoot() && model()->groupsEnabled(); +} diff --git a/src/contactlistaccountgroup.h b/src/contactlistaccountgroup.h new file mode 100644 index 000000000..c2ee53a46 --- /dev/null +++ b/src/contactlistaccountgroup.h @@ -0,0 +1,69 @@ +/* + * contactlistaccountgroup.h + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTACCOUNTGROUP_H +#define CONTACTLISTACCOUNTGROUP_H + +#include "contactlistnestedgroup.h" + +#include + +class PsiAccount; + +class ContactListAccountGroup : public ContactListNestedGroup +{ + Q_OBJECT +public: + ContactListAccountGroup(ContactListModel* model, ContactListGroup* parent, PsiAccount* account); + ~ContactListAccountGroup(); + + PsiAccount* account() const; + ContactListAccountGroup* findAccount(PsiAccount* account) const; + + // reimplemented + virtual ContactListModel::Type type() const; + virtual const QString& displayName() const; + virtual QString internalGroupName() const; + virtual ContactListItemMenu* contextMenu(ContactListModel* model); + virtual bool isEditable() const; + virtual bool canContainSpecialGroups() const; + + // reimplemented + virtual void addContact(PsiContact* contact, QStringList contactGroups); + virtual void contactGroupsChanged(PsiContact* contact, QStringList contactGroups); + +protected: + void removeAccount(ContactListAccountGroup* accountGroup); + ContactListItemProxy* findGroup(ContactListGroup* group) const; + bool isRoot() const; + + // reimplemented + virtual void clearGroup(); + +private slots: + void accountUpdated(); + +private: + bool isRoot_; + QPointer account_; + QList accounts_; +}; + +#endif diff --git a/src/contactlistaccountmenu.cpp b/src/contactlistaccountmenu.cpp new file mode 100644 index 000000000..4a3d4e3ef --- /dev/null +++ b/src/contactlistaccountmenu.cpp @@ -0,0 +1,340 @@ +/* + * contactlistaccountmenu.cpp - context menu for contact list accounts + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistaccountmenu.h" + +#include + +#include "psiaccount.h" +#include "contactlistaccountgroup.h" +#include "statusmenu.h" +#include "serverinfomanager.h" +#include "bookmarkmanager.h" +#include "psioptions.h" +#include "iconaction.h" + +class ContactListAccountMenu::Private : public QObject +{ + Q_OBJECT + + QPointer account; + StatusMenu* statusMenu_; + QAction* moodAction_; + QAction* setAvatarAction_; + QMenu* avatarMenu_; + QAction* unsetAvatarAction_; + QMenu* bookmarksMenu_; + QAction* bookmarksManageAction_; + QList bookmarksJoinActions_; + QAction* addContactAction_; + QAction* serviceDiscoveryAction_; + QAction* newMessageAction_; + QAction* xmlConsoleAction_; + QAction* modifyAccountAction_; + QMenu* adminMenu_; + QAction* adminOnlineUsersAction_; + QAction* adminSendServerMessageAction_; + QAction* adminSetMotdAction_; + QAction* adminUpdateMotdAction_; + QAction* adminDeleteMotdAction_; + +public: + Private(ContactListAccountMenu* menu, ContactListAccountGroup* _account) + : QObject(0) + , account(_account) + , menu_(menu) + { + connect(menu, SIGNAL(aboutToShow()), SLOT(updateActions())); + connect(account->account(), SIGNAL(updatedActivity()), SLOT(updateActions())); + connect(account->account(), SIGNAL(updatedAccount()), SLOT(updateActions())); + + statusMenu_ = new StatusMenu(0); + statusMenu_->setTitle(tr("&Status")); + connect(statusMenu_, SIGNAL(statusChanged(XMPP::Status::Type)), SLOT(statusChanged(XMPP::Status::Type))); + + moodAction_ = new QAction(tr("Mood"), this); + connect(moodAction_, SIGNAL(triggered()), SLOT(setMood())); + + setAvatarAction_ = new QAction(tr("Set Avatar"), this); + connect(setAvatarAction_, SIGNAL(triggered()), SLOT(setAvatar())); + + unsetAvatarAction_ = new QAction(tr("Unset Avatar"), this); + connect(unsetAvatarAction_, SIGNAL(triggered()), SLOT(unsetAvatar())); + + bookmarksManageAction_ = new QAction(tr("Manage..."), this); + connect(bookmarksManageAction_, SIGNAL(triggered()), SLOT(bookmarksManage())); + + addContactAction_ = new IconAction(tr("&Add a Contact"), this, "psi/addContact"); + connect(addContactAction_, SIGNAL(triggered()), SLOT(addContact())); + + serviceDiscoveryAction_ = new IconAction(tr("Service &Discovery"), this, "psi/disco"); + connect(serviceDiscoveryAction_, SIGNAL(triggered()), SLOT(serviceDiscovery())); + + newMessageAction_ = new IconAction(tr("New &Blank Message"), this, "psi/sendMessage"); + connect(newMessageAction_, SIGNAL(triggered()), SLOT(newMessage())); + + xmlConsoleAction_ = new IconAction(tr("&XML Console"), this, "psi/xml"); + connect(xmlConsoleAction_, SIGNAL(triggered()), SLOT(xmlConsole())); + + modifyAccountAction_ = new IconAction(tr("&Modify Account..."), this, "psi/account"); + connect(modifyAccountAction_, SIGNAL(triggered()), SLOT(modifyAccount())); + + adminOnlineUsersAction_ = new IconAction(tr("Online Users"), this, "psi/disco"); + connect(adminOnlineUsersAction_, SIGNAL(triggered()), SLOT(adminOnlineUsers())); + + adminSendServerMessageAction_ = new IconAction(tr("Send Server Message"), this, "psi/sendMessage"); + connect(adminSendServerMessageAction_, SIGNAL(triggered()), SLOT(adminSendServerMessage())); + + adminSetMotdAction_ = new QAction(tr("Set MOTD"), this); + connect(adminSetMotdAction_, SIGNAL(triggered()), SLOT(adminSetMotd())); + + adminUpdateMotdAction_ = new QAction(tr("Update MOTD"), this); + connect(adminUpdateMotdAction_, SIGNAL(triggered()), SLOT(adminUpdateMotd())); + + adminDeleteMotdAction_ = new IconAction(tr("Delete MOTD"), this, "psi/remove"); + connect(adminDeleteMotdAction_, SIGNAL(triggered()), SLOT(adminDeleteMotd())); + + menu->addMenu(statusMenu_); + menu->addAction(moodAction_); + avatarMenu_ = menu->addMenu(tr("Avatar")); + avatarMenu_->addAction(setAvatarAction_); + avatarMenu_->addAction(unsetAvatarAction_); + bookmarksMenu_ = menu->addMenu(tr("Bookmarks")); + bookmarksMenu_->addAction(bookmarksManageAction_); + menu->addSeparator(); + menu->addAction(addContactAction_); + menu->addAction(serviceDiscoveryAction_); + menu->addAction(newMessageAction_); + menu->addSeparator(); + menu->addAction(xmlConsoleAction_); + menu->addSeparator(); + menu->addAction(modifyAccountAction_); + adminMenu_ = menu->addMenu("&Admin"); + adminMenu_->addAction(adminOnlineUsersAction_); + adminMenu_->addAction(adminSendServerMessageAction_); + adminMenu_->addAction(adminSetMotdAction_); + adminMenu_->addAction(adminUpdateMotdAction_); + adminMenu_->addAction(adminDeleteMotdAction_); + + updateActions(); + } + + ~Private() + { + delete statusMenu_; + } + +private slots: + void updateActions() + { + if (!account) + return; + + statusMenu_->setStatus(account->account()->status().type()); +#ifndef USE_PEP + moodAction_->setVisible(false); + avatarMenu_->setVisible(false); +#else + moodAction_->setEnabled(account->account()->serverInfoManager()->hasPEP()); + avatarMenu_->setEnabled(account->account()->serverInfoManager()->hasPEP()); +#endif + bookmarksMenu_->clear(); + qDeleteAll(bookmarksJoinActions_); + bookmarksJoinActions_.clear(); + bookmarksMenu_->addAction(bookmarksManageAction_); + if (account->account()->bookmarkManager()->isAvailable()) { + bookmarksMenu_->setEnabled(true); + bookmarksMenu_->addSeparator(); + foreach(ConferenceBookmark c, account->account()->bookmarkManager()->conferences()) { + QAction* joinAction = new QAction(QString(tr("Join %1")).arg(c.name()), this); + joinAction->setProperty("bookmark", bookmarksJoinActions_.count()); + connect(joinAction, SIGNAL(triggered()), SLOT(bookmarksJoin())); + bookmarksMenu_->addAction(joinAction); + bookmarksJoinActions_ << joinAction; + } + } + else { + bookmarksMenu_->setEnabled(false); + } + + newMessageAction_->setVisible(PsiOptions::instance()->getOption("options.ui.message.enabled").toBool()); + if (!PsiOptions::instance()->getOption("options.ui.menu.account.admin").toBool()) { + adminMenu_->setVisible(false); + } + adminMenu_->setEnabled(account->account()->isAvailable()); + adminSendServerMessageAction_->setVisible(newMessageAction_->isVisible()); + adminSetMotdAction_->setVisible(newMessageAction_->isVisible()); + adminUpdateMotdAction_->setVisible(newMessageAction_->isVisible()); + adminDeleteMotdAction_->setVisible(newMessageAction_->isVisible()); + } + + void statusChanged(XMPP::Status::Type statusType) + { + if (!account) + return; + + account->account()->changeStatus(static_cast(statusType)); + } + + void setMood() + { + if (!account) + return; + + account->account()->actionSetMood(); + } + + void setAvatar() + { + if (!account) + return; + + account->account()->actionSetAvatar(); + } + + void unsetAvatar() + { + if (!account) + return; + + account->account()->actionUnsetAvatar(); + } + + void bookmarksManage() + { + if (!account) + return; + + account->account()->actionManageBookmarks(); + } + + void bookmarksJoin() + { + if (!account) + return; + + QAction* joinAction = static_cast(sender()); + ConferenceBookmark c = account->account()->bookmarkManager()->conferences()[joinAction->property("bookmark").toInt()]; + account->account()->actionJoin(c, true); + } + + void addContact() + { + if (!account) + return; + + account->account()->openAddUserDlg(); + } + + void serviceDiscovery() + { + if (!account) + return; + + XMPP::Jid j = account->account()->jid().domain(); + account->account()->actionDisco(j, ""); + } + + void newMessage() + { + if (!account) + return; + + account->account()->actionSendMessage(""); + } + + void xmlConsole() + { + if (!account) + return; + + account->account()->showXmlConsole(); + } + + void modifyAccount() + { + if (!account) + return; + + account->account()->modify(); + } + + void adminOnlineUsers() + { + if (!account) + return; + + // FIXME: will it still work on XMPP servers? + XMPP::Jid j = account->account()->jid().domain() + '/' + "admin"; + account->account()->actionDisco(j, ""); + } + + void adminSendServerMessage() + { + if (!account) + return; + + XMPP::Jid j = account->account()->jid().domain() + '/' + "announce/online"; + account->account()->actionSendMessage(j); + } + + void adminSetMotd() + { + if (!account) + return; + + XMPP::Jid j = account->account()->jid().domain() + '/' + "announce/motd"; + account->account()->actionSendMessage(j); + } + + void adminUpdateMotd() + { + if (!account) + return; + + XMPP::Jid j = account->account()->jid().domain() + '/' + "announce/motd/update"; + account->account()->actionSendMessage(j); + } + + void adminDeleteMotd() + { + if (!account) + return; + + XMPP::Jid j = account->account()->jid().domain() + '/' + "announce/motd/delete"; + account->account()->actionSendMessage(j); + } + +private: + ContactListAccountMenu* menu_; +}; + +ContactListAccountMenu::ContactListAccountMenu(ContactListAccountGroup* account, ContactListModel* model) + : ContactListItemMenu(account, model) +{ + d = new Private(this, account); +} + +ContactListAccountMenu::~ContactListAccountMenu() +{ + delete d; +} + +#include "contactlistaccountmenu.moc" diff --git a/src/contactlistaccountmenu.h b/src/contactlistaccountmenu.h new file mode 100644 index 000000000..0227c3d14 --- /dev/null +++ b/src/contactlistaccountmenu.h @@ -0,0 +1,40 @@ +/* + * contactlistaccountmenu.h - context menu for contact list accounts + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTACCOUNTMENU_H +#define CONTACTLISTACCOUNTMENU_H + +#include "contactlistitemmenu.h" + +class ContactListAccountGroup; + +class ContactListAccountMenu : public ContactListItemMenu +{ + Q_OBJECT +public: + ContactListAccountMenu(ContactListAccountGroup* account, ContactListModel* model); + ~ContactListAccountMenu(); + +private: + class Private; + Private* d; +}; + +#endif diff --git a/src/contactlistdragmodel.cpp b/src/contactlistdragmodel.cpp new file mode 100644 index 000000000..60e5ac6eb --- /dev/null +++ b/src/contactlistdragmodel.cpp @@ -0,0 +1,682 @@ +/* + * contactlistdragmodel.cpp - ContactListModel with support for Drag'n'Drop operations + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistdragmodel.h" + +#include +#include + +#include "psioptions.h" +#include "psiaccount.h" +#include "contactlistgroup.h" +#include "contactlistitemproxy.h" +#include "psicontactlist.h" +#include "psicontact.h" +#include "common.h" +#include "contactlistnestedgroup.h" +#include "contactlistaccountgroup.h" +#include "contactlistmodelselection.h" +#include "contactlistgroupcache.h" +#include "contactlistmodelupdater.h" +#include "contactlistgroup.h" + +//---------------------------------------------------------------------------- +// ContactListModelOperationList +//---------------------------------------------------------------------------- + +ContactListModelOperationList::ContactListModelOperationList(Action action) + : action_(action) +{ +} + +ContactListModelOperationList::ContactListModelOperationList(Qt::DropAction action) +{ + if (action == Qt::CopyAction) + action_ = Copy; + else if (action == Qt::MoveAction) + action_ = Move; + else { + Q_ASSERT_X(false, "ContactListModelOperationList", "Passed incorrect Qt::DropAction"); + } +} + +ContactListModelOperationList::Action ContactListModelOperationList::action() const +{ + return action_; +} + +void ContactListModelOperationList::addOperation(PsiContact* contact, const QString& groupFrom, const QString& groupTo) +{ + if (!contact) { + qWarning("ContactListModelOperationList::addOperation(): contact is NULL"); + return; + } + + if (!contact->isEditable()) { + qWarning("ContactListModelOperationList::addOperation(): contact is not editable '%s'", qPrintable(contact->jid().full())); + return; + } + + if (!contact->groupOperationPermitted(groupFrom, groupTo)) { + qWarning("ContactListModelOperationList::addOperation(): contact '%s' refused group operation ('%s' -> '%s')", qPrintable(contact->jid().full()), qPrintable(groupFrom), qPrintable(groupTo)); + return; + } + + Operation op(groupFrom, groupTo); + + if (!operations_.contains(contact)) + operations_[contact] = QList(); + + operations_[contact] += op; +} + +QList ContactListModelOperationList::operations() const +{ + QList result; + + QHash >::const_iterator it; + for (it = operations_.constBegin(); it != operations_.constEnd(); ++it) { + ContactOperation op; + op.contact = it.key(); + op.operations = it.value(); + result += op; + } + + return result; +} + +void ContactListModelOperationList::removeAccidentalContactMoveOperations() +{ + if (action_ != Move) + return; + + QList contacts = operations_.keys(); + foreach(PsiContact* psiContact, contacts) { + bool remove = false; + foreach(Operation op, operations_[psiContact]) { + if (psiContact->groups().contains(op.groupTo)) { + remove = true; + break; + } + } + + if (remove) + operations_.remove(psiContact); + } +} + +//---------------------------------------------------------------------------- +// ContactListDragModel +//---------------------------------------------------------------------------- + +ContactListDragModel::ContactListDragModel(PsiContactList* contactList) + : ContactListModel(contactList) +{ + setSupportedDragActions(Qt::MoveAction | Qt::CopyAction); +} + +ContactListModel* ContactListDragModel::clone() const +{ + return new ContactListDragModel(contactList()); +} + +Qt::DropActions ContactListDragModel::supportedDropActions() const +{ + return Qt::MoveAction | Qt::CopyAction; +} + +Qt::ItemFlags ContactListDragModel::flags(const QModelIndex& index) const +{ + Qt::ItemFlags f = ContactListModel::flags(index); + + ContactListItemProxy* item = static_cast(index.internalPointer()); + + if (item && item->item()) { + return f | Qt::ItemIsDropEnabled | (item->item()->isDragEnabled() ? Qt::ItemIsDragEnabled : f); + } + + if (!index.isValid()) { + return f | Qt::ItemIsDropEnabled; + } + + return f; +} + +QStringList ContactListDragModel::mimeTypes() const +{ + return QStringList() + << ContactListModelSelection::mimeType() + << "text/plain"; +} + +QMimeData* ContactListDragModel::mimeData(const QModelIndexList& indexes) const +{ + QList items; + + foreach(QModelIndex index, indexes) { + if (index.isValid()) { + ContactListItemProxy* itemProxy = static_cast(index.internalPointer()); + if (!itemProxy) + continue; + + items << itemProxy; + } + } + + return new ContactListModelSelection(items); +} + +QModelIndexList ContactListDragModel::indexesFor(const QMimeData* data) const +{ + QModelIndexList result; + ContactListModelSelection selection(data); + if (!selection.haveRosterSelection() || !contactList()) + return result; + + foreach(ContactListModelSelection::Contact contact, selection.contacts()) { + PsiAccount* account = contactList()->getAccount(contact.account); + if (!account) + continue; + PsiContact* psiContact = account->findContact(contact.jid); + if (!psiContact) { + if (account->selfContact()->jid() == contact.jid) + psiContact = account->selfContact(); + } + + if (!psiContact) + continue; + + QList groups = groupCache()->groupsFor(psiContact); + if (!contact.group.isEmpty()) { + QList matched; + foreach(ContactListGroup* group, groups) { + if (group->fullName() == contact.group) { + matched << group; + break; + } + } + + if (!matched.isEmpty()) + groups = matched; + } + + foreach(ContactListGroup* group, groups) { + int contactIndex = group->indexOf(psiContact); + if (contactIndex >= 0) { + ContactListItemProxy* itemProxy = group->item(contactIndex); + if (itemProxy) { + QModelIndex index = itemProxyToModelIndex(itemProxy); + if (index.isValid() && !result.contains(index)) + result << index; + } + } + } + } + + foreach(ContactListModelSelection::Group group, selection.groups()) { + ContactListGroup* contactGroup = groupCache()->findGroup(group.fullName); + QModelIndex index = groupToIndex(contactGroup); + if (!result.contains(index)) + result << index; + } + + foreach(ContactListModelSelection::Account account, selection.accounts()) { + PsiAccount* acc = contactList()->getAccount(account.id); + Q_ASSERT(acc); + ContactListAccountGroup* rootGroup = dynamic_cast(this->rootGroup()); + Q_ASSERT(rootGroup); + ContactListGroup* accountGroup = rootGroup->findAccount(acc); + QModelIndex index = groupToIndex(accountGroup); + if (!result.contains(index)) + result << index; + } + + return result; +} + +bool ContactListDragModel::supportsMimeDataOnIndex(const QMimeData* data, const QModelIndex& parent) const +{ + if ((!groupsEnabled() && !accountsEnabled()) || !ContactListModelSelection(data).haveRosterSelection()) { + return false; + } + +#ifdef USE_GENERAL_CONTACT_GROUP + if (!parent.isValid()) { + return false; + } +#endif + + { + // disable dragging to special groups + ContactListItemProxy* item = itemProxy(parent); + ContactListGroup* group = dynamic_cast(item ? item->item() : 0); + if (!group) + group = item ? item->parent() : 0; + if (group && group->isSpecial()) + return false; + } + + foreach(QModelIndex index, indexesFor(data)) { + if (index == parent) { + return false; + } + + // protection against dragging parent group inside its child + ContactListItemProxy* item = itemProxy(index); + Q_ASSERT(item); + ContactListGroup* group = dynamic_cast(item->item()); + if (group) { + ContactListItemProxy* item2 = itemProxy(parent); + ContactListGroup* group2 = item2 ? item2->parent() : 0; + if (group2 && group2->fullName().startsWith(group->fullName() + ContactListGroup::groupDelimiter())) { + return false; + } + } + + PsiContact* contact = dynamic_cast(item->item()); + if (contact && accountsEnabled()) { + // disable dragging to self contacts + ContactListItemProxy* selfContactItem = itemProxy(parent); + PsiContact* selfContact = dynamic_cast(selfContactItem->item()); + if (selfContact && selfContact->isSelf()) + return false; + + // disable dragging to different accounts and to self account + QModelIndex accountIndex = parent; + while (accountIndex.isValid() && + ContactListModel::indexType(accountIndex) != ContactListModel::AccountType) + { + accountIndex = accountIndex.parent(); + } + + if (!accountIndex.isValid()) + return false; + + ContactListItemProxy* accountItem = itemProxy(accountIndex); + Q_ASSERT(accountItem); + ContactListAccountGroup* accountGroup = dynamic_cast(accountItem->item()); + Q_ASSERT(accountGroup); + return accountGroup && + parent != accountIndex && + accountGroup->account() == contact->account(); + } + } + + return true; +} + +void ContactListDragModel::addOperationsForGroupRename(const QString& currentGroupName, const QString& newGroupName, ContactListModelOperationList* operations) const +{ + ContactListGroup* group = groupCache()->findGroup(currentGroupName); + if (group) { + for (int i = 0; i < group->itemsCount(); ++i) { + ContactListItemProxy* itemProxy = group->item(i); + PsiContact* contact = 0; + ContactListGroup* childGroup = 0; + if ((contact = dynamic_cast(itemProxy->item()))) { + operations->addOperation(contact, currentGroupName, newGroupName); + } + else if ((childGroup = dynamic_cast(itemProxy->item()))) { + QString theName = childGroup->fullName().split(ContactListGroup::groupDelimiter()).last(); + QString newName = (newGroupName.isEmpty() ? "" : newGroupName + ContactListGroup::groupDelimiter()) + theName; + addOperationsForGroupRename(childGroup->fullName(), newName, operations); + } + } + } +} + +bool ContactListDragModel::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent) +{ + Q_UNUSED(row); + Q_UNUSED(column); + + ContactListModelSelection selection(data); + if (!selection.haveRosterSelection() || !contactList()) + return false; + + ContactListModelOperationList operations(action); + + foreach(ContactListModelSelection::Contact contact, selection.contacts()) { + PsiAccount* account = contactList()->getAccount(contact.account); + PsiContact* psiContact = account ? account->findContact(contact.jid) : 0; + operations.addOperation(psiContact, + contact.group, + getDropGroupName(parent)); + } + + foreach(ContactListModelSelection::Group group, selection.groups()) { + QString parentGroupName = getDropGroupName(parent); + if (parentGroupName.startsWith(group.fullName + ContactListGroup::groupDelimiter())) { + qWarning("Dropping group to its descendant is not supported ('%s' -> '%s')", qPrintable(group.fullName), qPrintable(parentGroupName)); + continue; + } + + if (parentGroupName == group.fullName) + continue; + + // TODO: unify these two lines with the ones in operationsForGroupRename + QString theName = group.fullName.split(ContactListGroup::groupDelimiter()).last(); + QString newName = (parentGroupName.isEmpty() ? "" : parentGroupName + ContactListGroup::groupDelimiter()) + theName; + if (newName == group.fullName) + continue; + + addOperationsForGroupRename(group.fullName, newName, &operations); + } + + if (selection.isMultiSelection()) + operations.removeAccidentalContactMoveOperations(); + + // qWarning("*** dropMimeData: New operation list: ***"); + // foreach(ContactListModelOperationList::ContactOperation contactOperation, operations.operations()) { + // QStringList groups; + // foreach(ContactListModelOperationList::Operation op, contactOperation.operations) + // groups += QString("'%1' -> '%2'").arg(op.groupFrom, op.groupTo); + // qWarning("\t'%s' (%s)", qPrintable(contactOperation.contact->jid().full()), qPrintable(groups.join(";"))); + // } + + performContactOperations(operations, Operation_DragNDrop); + + return true; +} + +void ContactListDragModel::renameGroup(ContactListGroup* group, const QString& newName) +{ + Q_ASSERT(group); + ContactListModelOperationList operations(ContactListModelOperationList::Move); + + QStringList name = group->fullName().split(ContactListGroup::groupDelimiter()); + if (name.isEmpty()) + return; + name.takeLast(); + if (!newName.isEmpty()) + name << newName; + addOperationsForGroupRename(group->fullName(), name.join(ContactListGroup::groupDelimiter()), &operations); + + performContactOperations(operations, Operation_GroupRename); +} + +QString ContactListDragModel::processContactSetGroupName(const QString& groupName) const +{ + if (accountsEnabled()) { + QStringList split = groupName.split(ContactListGroup::groupDelimiter()); + split.takeFirst(); + return split.join(ContactListGroup::groupDelimiter()); + } + + return groupName; +} + +QStringList ContactListDragModel::processContactSetGroupNames(const QStringList& groups) const +{ + QStringList result; + foreach(const QString& g, groups) { + result << processContactSetGroupName(g); + } + return result; +} + +void ContactListDragModel::performContactOperations(const ContactListModelOperationList& operations, OperationType operationType) +{ + QHash groupContactCount; + + foreach(ContactListModelOperationList::ContactOperation contactOperation, operations.operations()) { + PsiContact* psiContact = contactOperation.contact; + if (!psiContact) { + qWarning("Only contact moving via drag'n'drop is supported for now."); + continue; + } + + // PsiAccount* dropAccount = getDropAccount(account, parent); + // if (!dropAccount || dropAccount != account) { + // qWarning("Dropping to different accounts is not supported for now."); + // return; + // } + + QStringList groups = psiContact->groups(); + + foreach(ContactListModelOperationList::Operation op, contactOperation.operations) { + if (operations.action() == ContactListModelOperationList::Move) { + groups.removeAll(op.groupFrom); + + ContactListGroup* group = groupCache()->findGroup(op.groupFrom); + if (group && groupContactCount.contains(group)) { + groupContactCount[group] -= 1; + } + else if (group) { + groupContactCount[group] = group->contactsCount() - 1; + } + } + + if (!groups.contains(op.groupTo)) { + groups << op.groupTo; + } + } + + psiContact->setGroups(processContactSetGroupNames(groups)); + } + + contactOperationsPerformed(operations, operationType, groupContactCount); +} + +void ContactListDragModel::contactOperationsPerformed(const ContactListModelOperationList& operations, OperationType operationType, const QHash& groupContactCount) +{ + Q_UNUSED(operations); + Q_UNUSED(operationType); + Q_UNUSED(groupContactCount); +} + +// TODO: think of a way to merge this with performContactOperations +QList ContactListDragModel::removeIndexesHelper(const QMimeData* data, bool performRemove) +{ + QList result; + QMap toRemove; + ContactListModelOperationList operations = removeOperationsFor(data); + + foreach(ContactListModelOperationList::ContactOperation contactOperation, operations.operations()) { + PsiContact* psiContact = contactOperation.contact; + if (!psiContact) + continue; + + QStringList groups = psiContact->groups(); + + foreach(ContactListModelOperationList::Operation op, contactOperation.operations) { + groups.removeAll(op.groupFrom); + } + + if (!groupsEnabled()) { + groups.clear(); + } + + if (psiContact) { + if (!result.contains(psiContact)) { + result << psiContact; + } + } + + toRemove[psiContact] = groups; + } + + if (performRemove && !toRemove.isEmpty()) { + beginBulkUpdate(); + + QMapIterator it(toRemove); + while (it.hasNext()) { + it.next(); + PsiContact* psiContact = it.key(); + if (filterRemoveContact(psiContact, it.value())) + continue; + + if (!psiContact) + continue; + + QStringList groups = it.value(); + if (!groups.isEmpty()) + psiContact->setGroups(processContactSetGroupNames(groups)); + else + psiContact->remove(); + } + + endBulkUpdate(); + } + + return result; +} + +bool ContactListDragModel::filterRemoveContact(PsiContact* psiContact, const QStringList& newGroups) +{ + Q_UNUSED(psiContact); + Q_UNUSED(newGroups); + return false; +} + +void ContactListDragModel::initializeModel() +{ + ContactListModel::initializeModel(); +} + +PsiAccount* ContactListDragModel::getDropAccount(PsiAccount* account, const QModelIndex& parent) const +{ + Q_UNUSED(account); + + if (!parent.isValid()) + return 0; + + ContactListItemProxy* item = static_cast(parent.internalPointer()); + PsiContact* contact = 0; + + if (item && (contact = dynamic_cast(item->item()))) { + return contact->account(); + } + // else if ((group = dynamic_cast(item))) { + // return group->account(); + // } + + return 0; +} + +QString ContactListDragModel::getDropGroupName(const QModelIndex& parent) const +{ + if (!parent.isValid()) + return QString(); + + ContactListItemProxy* item = static_cast(parent.internalPointer()); + ContactListGroup* group = 0; + + if (item) { + if ((group = dynamic_cast(item->item()))) { + return group->fullName(); + } + else { + Q_ASSERT(item->parent()); + return item->parent()->fullName(); + } + } + + return QString(); +} + +ContactListModelOperationList ContactListDragModel::removeOperationsFor(const QMimeData* data) const +{ + ContactListModelOperationList operations(ContactListModelOperationList::Remove); + ContactListModelSelection selection(data); + + if (!contactList()) + return operations; + + foreach(ContactListModelSelection::Contact contact, selection.contacts()) { + PsiAccount* account = contactList()->getAccount(contact.account); + PsiContact* psiContact = account ? account->findContact(contact.jid) : 0; + operations.addOperation(psiContact, + processContactSetGroupName(contact.group), + QString()); + } + + foreach(ContactListModelSelection::Group group, selection.groups()) { + addOperationsForGroupRename(processContactSetGroupName(group.fullName), QString(), &operations); + } + + // qWarning("*** removeOperationsFor: New operation list: ***"); + // foreach(ContactListModelOperationList::ContactOperation contactOperation, operations.operations()) { + // QStringList groups; + // foreach(ContactListModelOperationList::Operation op, contactOperation.operations) + // groups += QString("'%1' -> '%2'").arg(op.groupFrom, op.groupTo); + // qWarning("\t'%s' (%s)", qPrintable(contactOperation.contact->jid().full()), qPrintable(groups.join(";"))); + // } + + return operations; +} + +/** + * Returns list of contacts that will be removed from roster by removeIndexes(). + * Contacts that will not be reported by this function will only lose some groups + * they belong to. + */ +QList ContactListDragModel::contactsLostByRemove(const QMimeData* data) const +{ + return ((ContactListDragModel*)this)->removeIndexesHelper(data, false); +} + +/** + * Returns list of groups for specified contact which are going to be removed + * when the specified data is removed. + */ +QStringList ContactListDragModel::contactGroupsLostByRemove(PsiContact* contact, const QMimeData* data) const +{ + Q_ASSERT(contact); + Q_ASSERT(data); + QStringList result; + if (!contact || !data) + return result; + + ContactListModelOperationList operations = removeOperationsFor(data); + Q_ASSERT(operations.action() == ContactListModelOperationList::Remove); + foreach(const ContactListModelOperationList::ContactOperation& op, operations.operations()) { + if (op.contact != contact) + continue; + + foreach(const ContactListModelOperationList::Operation& o, op.operations) { + Q_ASSERT(o.groupTo.isEmpty()); + result << o.groupFrom; + } + } + + return result; +} + +/** + * Removes selected roster items from roster by either removing some of the + * contacts' groups, or by removing them from roster altogether. + */ +void ContactListDragModel::removeIndexes(const QMimeData* data) +{ + removeIndexesHelper(data, true); +} + +QModelIndexList ContactListDragModel::indexesFor(PsiContact* contact, QMimeData* contactSelection) const +{ + QModelIndexList indexes; + if (contactSelection) { + indexes += indexesFor(contactSelection); + } + if (indexes.isEmpty() && contact) { + indexes += ContactListModel::indexesFor(contact); + } + return indexes; +} diff --git a/src/contactlistdragmodel.h b/src/contactlistdragmodel.h new file mode 100644 index 000000000..750ff0815 --- /dev/null +++ b/src/contactlistdragmodel.h @@ -0,0 +1,125 @@ +/* + * contactlistdragmodel.h - ContactListModel with support for Drag'n'Drop operations + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTDRAGMODEL_H +#define CONTACTLISTDRAGMODEL_H + +#include "contactlistmodel.h" + +class ContactListItem; +class ContactListGroupItem; +class PsiContactGroup; +class PsiAccount; +class ContactListModelSelection; +class PsiContact; + +#include "xmpp_jid.h" + +#include + +class ContactListModelOperationList +{ +public: + enum Action { + Copy = 0, + Move, + Remove + }; + + struct Operation { + Operation() + {} + Operation(const QString& _groupFrom, const QString& _groupTo) + : groupFrom(_groupFrom) + , groupTo(_groupTo) + {} + QString groupFrom; + QString groupTo; + }; + + struct ContactOperation { + PsiContact* contact; + QList operations; + }; + + ContactListModelOperationList(Action action); + ContactListModelOperationList(Qt::DropAction action); + + Action action() const; + + void addOperation(PsiContact* contact, const QString& groupFrom, const QString& groupTo); + QList operations() const; + + void removeAccidentalContactMoveOperations(); + +private: + Action action_; + QHash > operations_; +}; + +class ContactListDragModel : public ContactListModel +{ + Q_OBJECT +public: + ContactListDragModel(PsiContactList* contactList); + + // reimplemented + Qt::DropActions supportedDropActions() const; + Qt::ItemFlags flags(const QModelIndex& index) const; + QStringList mimeTypes() const; + QMimeData* mimeData(const QModelIndexList& indexes) const; + bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, const QModelIndex& parent); + virtual ContactListModel* clone() const; + void renameGroup(ContactListGroup* group, const QString& newName); + + QModelIndexList indexesFor(const QMimeData* data) const; + QModelIndexList indexesFor(PsiContact* contact, QMimeData* contactSelection) const; + + QList contactsLostByRemove(const QMimeData* data) const; + void removeIndexes(const QMimeData* data); + QStringList contactGroupsLostByRemove(PsiContact* contact, const QMimeData* data) const; + + bool supportsMimeDataOnIndex(const QMimeData* data, const QModelIndex& parent) const; + +protected: + enum OperationType { + Operation_DragNDrop = 0, + Operation_GroupRename + }; + + // reimplemented + virtual bool filterRemoveContact(PsiContact* psiContact, const QStringList& newGroups); + virtual void initializeModel(); + + virtual PsiAccount* getDropAccount(PsiAccount* account, const QModelIndex& parent) const; + virtual QString getDropGroupName(const QModelIndex& parent) const; + virtual void contactOperationsPerformed(const ContactListModelOperationList& operations, OperationType operationType, const QHash& groupContactCount); + + QString processContactSetGroupName(const QString& groupName) const; + QStringList processContactSetGroupNames(const QStringList& groups) const; + +private: + QList removeIndexesHelper(const QMimeData* data, bool performRemove); + ContactListModelOperationList removeOperationsFor(const QMimeData* data) const; + void addOperationsForGroupRename(const QString& currentGroupName, const QString& newGroupName, ContactListModelOperationList* operations) const; + void performContactOperations(const ContactListModelOperationList& operations, OperationType operationType); +}; + +#endif diff --git a/src/contactlistdragview.cpp b/src/contactlistdragview.cpp new file mode 100644 index 000000000..19f02629a --- /dev/null +++ b/src/contactlistdragview.cpp @@ -0,0 +1,919 @@ +/* + * contactlistdragview.cpp - ContactListView with support for Drag'n'Drop operations + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistdragview.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "iconaction.h" +#include "contactlistviewdelegate.h" +#include "contactlistdragmodel.h" +#include "shortcutmanager.h" +#include "contactlistmodelselection.h" +#include "contactlistitemmenu.h" +#include "contactlistgroupstate.h" + +ContactListDragView::ContactListDragView(QWidget* parent) + : ContactListView(parent) + , backedUpSelection_(0) + , backedUpVerticalScrollBarValue_(-1) + , removeAction_(0) + , dropIndicatorRect_(QRect()) + , dropIndicatorPosition_(QAbstractItemView::OnViewport) + , keyboardModifiers_(Qt::NoModifier) + , dirty_(false) + , pressedIndex_(0) + , pressedIndexWasSelected_(false) + , viewportMenu_(0) +{ + removeAction_ = new IconAction("", "psi/remove", QString(), ShortcutManager::instance()->shortcuts("contactlist.delete"), this, "act_remove"); + connect(removeAction_, SIGNAL(activated()), SLOT(removeSelection())); + addAction(removeAction_); + + connect(this, SIGNAL(entered(const QModelIndex&)), SLOT(updateCursorMouseHover(const QModelIndex&))); + connect(this, SIGNAL(clicked(const QModelIndex&)), SLOT(itemClicked(const QModelIndex&))); + connect(this, SIGNAL(viewportEntered()), SLOT(updateCursorMouseHover())); + + setSelectionMode(ExtendedSelection); + viewport()->installEventFilter(this); + + setDragEnabled(true); + setAcceptDrops(true); + setDropIndicatorShown(false); // we're painting it by ourselves + + // There are crashes related to this in Qt 4.2.3. Disabling for now. +// #ifndef Q_WS_X11 +// setAnimated(true); +// #endif +} + +ContactListDragView::~ContactListDragView() +{ + delete backedUpSelection_; + delete pressedIndex_; +} + +/** + * Make sure that all keyboard shortcuts are unique in order to avoid + * "QAction::eventFilter: Ambiguous shortcut overload: Del" messages. + */ +void ContactListDragView::addContextMenuAction(QAction* action) +{ + foreach(QAction* act, findChildren()) { + // TODO: maybe check individual shortcuts too? + if (act->shortcuts() == action->shortcuts()) { + return; + } + } + + ContactListView::addContextMenuAction(action); +} + +void ContactListDragView::setItemDelegate(QAbstractItemDelegate* delegate) +{ + if (delegate == itemDelegate()) + return; + ContactListView::setItemDelegate(delegate); + modelChanged(); + doItemsLayout(); +} + +void ContactListDragView::leaveEvent(QEvent* e) +{ + ContactListView::leaveEvent(e); + updateCursorMouseHover(); +} + +int ContactListDragView::suggestedItemHeight() +{ + int rowCount = model()->rowCount(QModelIndex()); + if (!rowCount) + return 0; + return qMin(viewport()->height() / rowCount - 2, dynamic_cast(itemDelegate())->avatarSize()); +} + +void ContactListDragView::mouseDoubleClickEvent(QMouseEvent* e) +{ + ContactListDragModel* model = dynamic_cast(realModel()); + if (model && pressedIndex_) { + QModelIndexList indexes = model->indexesFor(0, pressedIndex_); + if (e->button() == Qt::LeftButton && + indexes.count() == 1 && + (ContactListModel::indexType(indexes.first()) == ContactListModel::GroupType || + ContactListModel::indexType(indexes.first()) == ContactListModel::AccountType)) + { + return; + } + } + + if (!activateItemsOnSingleClick()) { + ContactListView::mouseDoubleClickEvent(e); + } +} + +void ContactListDragView::itemActivated(const QModelIndex& index) +{ + if ((ContactListModel::indexType(index) == ContactListModel::GroupType || + ContactListModel::indexType(index) == ContactListModel::AccountType) && + activateItemsOnSingleClick()) + { + setExpanded(index, !index.data(ContactListModel::ExpandedRole).toBool()); + return; + } + + ContactListView::itemActivated(index); +} + +/** + * Returns a list of real-model indexes. + */ +QModelIndexList ContactListDragView::indexesFor(PsiContact* contact, QMimeData* contactSelection) const +{ + QModelIndexList indexes; + ContactListDragModel* model = dynamic_cast(realModel()); + if (model) { + indexes = model->indexesFor(contact, contactSelection); + } + return indexes; +} + +void ContactListDragView::toolTipEntered(PsiContact* contact, QMimeData* contactSelection) +{ + Q_UNUSED(contact); // we don't want tooltips on multiple selections + QModelIndexList indexes = indexesFor(0, contactSelection); + if (indexes.count() == 1) { + updateCursor(proxyIndex(indexes.first()), UC_TooltipEntered, false); + } +} + +void ContactListDragView::toolTipHidden(PsiContact* contact, QMimeData* contactSelection) +{ + Q_UNUSED(contact); + Q_UNUSED(contactSelection); + if (!drawSelectionBackground()) + updateCursor(QModelIndex(), UC_TooltipHidden, false); +} + +void ContactListDragView::updateCursorMouseHover() +{ + updateCursor(QModelIndex(), UC_MouseHover, false); +} + +bool ContactListDragView::updateCursor(const QModelIndex& index, UpdateCursorOrigin origin, bool force) +{ + if (isContextMenuVisible() || + extendedSelectionAllowed() || + state() != NoState) + { + if (!force) { + return false; + } + } + + setCursor((!index.isValid() || ContactListModel::indexType(index) == ContactListModel::ContactType) ? + Qt::ArrowCursor : + Qt::PointingHandCursor); + + if (origin == UC_MouseClick) { + if (index.isValid()) + setCurrentIndex(index); + else + clearSelection(); + } + + viewport()->update(); + return true; +} + +void ContactListDragView::updateCursorMouseHover(const QModelIndex& index) +{ + updateCursor(index, UC_MouseHover, false); +} + +void ContactListDragView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + ContactListView::selectionChanged(selected, deselected); + + // we must avoid backupCurrentSelection() as result of modelChanged() + // as modelAboutToBeReset() could get emitted after model is already reset, + // and as result we'll reference bad pointers + if (!dirty_) { + backupCurrentSelection(); + } +} + +int ContactListDragView::indexCombinedHeight(const QModelIndex& parent, QAbstractItemDelegate* delegate) const +{ + if (!delegate || !model()) + return 0; + int result = delegate->sizeHint(QStyleOptionViewItem(), parent).height(); + for (int row = 0; row < model()->rowCount(parent); ++row) { + QModelIndex index = model()->index(row, 0, parent); + if (isExpanded(index)) + result += indexCombinedHeight(index, delegate); + else + result += delegate->sizeHint(QStyleOptionViewItem(), index).height(); + } + return result; +} + +void ContactListDragView::setModel(QAbstractItemModel* newModel) +{ + if (model()) { + disconnect(model(), SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(modelChanged())); + disconnect(model(), SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), this, SLOT(modelChanged())); + disconnect(model(), SIGNAL(modelAboutToBeReset()), this, SLOT(modelChanged())); + disconnect(model(), SIGNAL(layoutAboutToBeChanged()), this, SLOT(modelChanged())); + disconnect(model(), SIGNAL(layoutChanged()), this, SLOT(doItemsLayout())); + } + + // it's critical that we hook on signals prior to selectionModel, + // otherwise it would be pretty hard to maintain consistent selection + if (newModel) { + connect(newModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(modelChanged())); + connect(newModel, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), this, SLOT(modelChanged())); + connect(newModel, SIGNAL(modelAboutToBeReset()), this, SLOT(modelChanged())); + connect(newModel, SIGNAL(layoutAboutToBeChanged()), this, SLOT(modelChanged())); + // this is necessary because we could use some data immediately after + // invalidating proxy model, and we want tree state to be up to date in order + // to avoid weird impossible crashes + connect(newModel, SIGNAL(layoutChanged()), this, SLOT(doItemsLayout())); + } + + ContactListView::setModel(newModel); + + disconnect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(scrollbarValueChanged())); + connect(verticalScrollBar(), SIGNAL(valueChanged(int)), this, SLOT(scrollbarValueChanged())); +} + +bool ContactListDragView::textInputInProgress() const +{ + return state() == QAbstractItemView::EditingState; +} + +void ContactListDragView::paintEvent(QPaintEvent* e) +{ + // Q_ASSERT(!dirty_); + // if (dirty_) + // return; + ContactListView::paintEvent(e); + + // drawDragIndicator!! + if (state() == QAbstractItemView::DraggingState && + viewport()->cursor().shape() != Qt::ForbiddenCursor && + !dropIndicatorRect_.isEmpty()) + { + QPainter p(viewport()); + // QColor dragIndicatorColor = QColor(0x17, 0x0B, 0xFF); + QColor dragIndicatorColor = QColor(0x00, 0x00, 0x00); + p.setPen(QPen(dragIndicatorColor, 2)); + p.setRenderHint(QPainter::Antialiasing, true); + QPainterPath path; + path.addRoundRect(dropIndicatorRect_.adjusted(2, 1, 0, 0), 5); + p.drawPath(path); + } +} + +bool ContactListDragView::supportsDropOnIndex(QDropEvent* e, const QModelIndex& index) const +{ + ContactListModelSelection selection(e->mimeData()); + ContactListDragModel* model = dynamic_cast(realModel()); + if (!selection.haveRosterSelection()) + model = 0; + + if (model && !model->supportsMimeDataOnIndex(e->mimeData(), realIndex(index))) { + return false; + } + + if (dropPosition(e, selection, index) == OnViewport) { + return false; + } + + return true; +} + +static void updateDefaultDropAction(QDropEvent* e) +{ + if (e->keyboardModifiers() == Qt::NoModifier) { + e->setDropAction(Qt::MoveAction); + } +} + +static void acceptDropAction(QDropEvent* e) +{ + if (e->keyboardModifiers() == Qt::NoModifier) { + if (e->dropAction() != Qt::MoveAction) { + qWarning("acceptDropAction(): e->dropAction() != Qt::MoveAction"); + return; + } + Q_ASSERT(e->dropAction() == Qt::MoveAction); + e->accept(); + } + else { + e->acceptProposedAction(); + } +} + +void ContactListDragView::dragMoveEvent(QDragMoveEvent* e) +{ + QModelIndex index = indexAt(e->pos()); + dropIndicatorRect_ = QRect(); + dropIndicatorPosition_ = OnViewport; + + // this is crucial at least for auto-scrolling, possibly other things + ContactListView::dragMoveEvent(e); + + if (supportsDropOnIndex(e, index)) { + bool ok = true; + ContactListModelSelection selection(e->mimeData()); + dropIndicatorPosition_ = dropPosition(e, selection, index); + if (dropIndicatorPosition_ == AboveItem || dropIndicatorPosition_ == BelowItem) { + dropIndicatorRect_ = groupReorderDropRect(dropIndicatorPosition_, selection, index); + } + else if (dropIndicatorPosition_ == OnItem) { + dropIndicatorRect_ = onItemDropRect(index); + } + else { + ok = false; + } + + if (ok) { + updateDefaultDropAction(e); + acceptDropAction(e); + return; + } + } + + e->ignore(); + viewport()->update(); +} + +void ContactListDragView::scrollbarValueChanged() +{ + dropIndicatorRect_ = QRect(); + dropIndicatorPosition_ = OnViewport; + viewport()->update(); + updateCursorMouseHover(underMouse() ? indexAt(mousePosition()) : QModelIndex()); +} + +void ContactListDragView::dragEnterEvent(QDragEnterEvent* e) +{ + ContactListView::dragEnterEvent(e); + updateDefaultDropAction(e); + acceptDropAction(e); +} + +void ContactListDragView::dragLeaveEvent(QDragLeaveEvent* e) +{ + ContactListView::dragLeaveEvent(e); +} + +void ContactListDragView::dropEvent(QDropEvent* e) +{ + updateDefaultDropAction(e); + + QModelIndex index = indexAt(e->pos()); + dropIndicatorRect_ = QRect(); + + if (dropIndicatorPosition_ == OnViewport || !supportsDropOnIndex(e, index)) { + e->ignore(); + viewport()->update(); + return; + } + + if (dropIndicatorPosition_ == OnItem) { + if (model()->dropMimeData(e->mimeData(), + e->dropAction(), -1, -1, index)) { + } + acceptDropAction(e); + } + else if (dropIndicatorPosition_ == AboveItem || dropIndicatorPosition_ == BelowItem) { + reorderGroups(e, index); + acceptDropAction(e); + } + + stopAutoScroll(); + setState(NoState); + viewport()->update(); +} + +QAbstractItemView::DropIndicatorPosition ContactListDragView::dropPosition(QDropEvent* e, const ContactListModelSelection& selection, const QModelIndex& index) const +{ + if ((selection.groups().count() > 0 || selection.accounts().count() > 0) && selection.contacts().count() == 0) { + // TODO: return OnItem in case the special modifiers are pressed + // even in case there are multiple groups selected + // in order to create nested groups + int totalSelectedGroups = selection.groups().count() + selection.accounts().count(); + if (totalSelectedGroups == 1) { + QModelIndex group = itemToReorderGroup(selection, index); + if (group.isValid()) { + QRect rect = groupVisualRect(group); + if (e->pos().y() >= rect.center().y()) { + return BelowItem; + } + else { + return AboveItem; + } + } + } + + return OnViewport; + } + + if (selection.groups().count() + selection.contacts().count() > 0) + return OnItem; + else + return OnViewport; +} + +struct OrderedGroup { + QString name; + int order; +}; + +void ContactListDragView::reorderGroups(QDropEvent* e, const QModelIndex& index) +{ + ContactListModelSelection selection(e->mimeData()); + QModelIndex item = itemToReorderGroup(selection, index); + if (!item.isValid()) { + return; + } + ContactListDragModel* model = dynamic_cast(realModel()); + Q_ASSERT(model); + Q_ASSERT(selection.groups().count() > 0 || selection.accounts().count() > 0); + Q_ASSERT(selection.contacts().count() == 0); + QModelIndexList selectedGroups = model->indexesFor(&selection); + if (!selectedGroups.count()) + return; + + // we need to work in terms of proxy indexes because we need actually-sorted items + QModelIndex selectedGroup = proxyIndex(selectedGroups.first()); + QModelIndex groupParent = selectedGroup.parent(); + + QModelIndexList groups; + for (int row = 0; row < this->model()->rowCount(groupParent); ++row) { + QModelIndex i = this->model()->index(row, selectedGroup.column(), groupParent); + if (ContactListModel::indexType(i) == ContactListModel::GroupType || + ContactListModel::indexType(i) == ContactListModel::AccountType) + { + groups << i; + } + } + + // re-order + groups.removeAll(selectedGroup); + if (dropIndicatorPosition_ == AboveItem) { + groups.insert(groups.indexOf(item), selectedGroup); + } + else { + Q_ASSERT(dropIndicatorPosition_ == BelowItem); + groups.insert(groups.indexOf(item) + 1, selectedGroup); + } + + foreach(QModelIndex i, groups) { + model->setGroupOrder(i.data(ContactListModel::FullGroupNameRole).toString(), + groups.indexOf(i)); + } +} + +QModelIndex ContactListDragView::itemToReorderGroup(const ContactListModelSelection& selection, const QModelIndex& index) const +{ + ContactListDragModel* model = dynamic_cast(realModel()); + Q_ASSERT(model); + ContactListModel::Type groupType = + selection.groups().count() == 1 ? + ContactListModel::GroupType : + ContactListModel::AccountType; + Q_ASSERT(selection.groups().count() == 1 || selection.accounts().count() == 1); + QModelIndexList selectedGroups = model->indexesFor(&selection); + if (!selectedGroups.count()) + return QModelIndex(); + QModelIndex selectedGroup = selectedGroups.first(); + // we need to work in terms of proxy indexes because we need actually-sorted items + selectedGroup = proxyIndex(selectedGroup); + const QModelIndex groupParent = selectedGroup.parent(); + + QModelIndex indexParent = index; + while (indexParent.isValid() && indexParent.parent() != groupParent) { + indexParent = indexParent.parent(); + } + if (indexParent.parent() != groupParent || indexParent == selectedGroup) + return QModelIndex(); + + // this code is necessary if we allow contacts on the same level as group items + bool breakAtFirstGroup = (ContactListModel::indexType(index) != groupType) && index.parent() == groupParent; + QModelIndex result; + for (int row = 0; row < this->model()->rowCount(groupParent); ++row) { + result = this->model()->index(row, indexParent.column(), groupParent); + if (breakAtFirstGroup) { + if (ContactListModel::indexType(result) == groupType) + break; + } + else if (result.row() == indexParent.row()) { + break; + } + } + return result; +} + +QRect ContactListDragView::groupReorderDropRect(DropIndicatorPosition dropIndicatorPosition, const ContactListModelSelection& selection, const QModelIndex& index) const +{ + QModelIndex group = itemToReorderGroup(selection, index); + QRect r(groupVisualRect(group)); + if (dropIndicatorPosition == AboveItem) { + return QRect(r.left(), r.top(), r.width(), 1); + } + else { + Q_ASSERT(dropIndicatorPosition == BelowItem); + QRect result(r.left(), r.bottom(), r.width(), 1); + if (result.bottom() < viewport()->height() - 2) + result.translate(0, 2); + return result; + } +} + +QRect ContactListDragView::onItemDropRect(const QModelIndex& index) const +{ + if (!index.isValid()) { + return viewport()->rect().adjusted(0, 0, -1, -1); + } + + if (ContactListModel::indexType(index) != ContactListModel::GroupType && + ContactListModel::indexType(index) != ContactListModel::AccountType) + { + return onItemDropRect(index.parent()); + } + + return groupVisualRect(index); +} + +QRect ContactListDragView::groupVisualRect(const QModelIndex& index) const +{ + Q_ASSERT(ContactListModel::indexType(index) == ContactListModel::GroupType || ContactListModel::indexType(index) == ContactListModel::AccountType); + + QRect result; + combineVisualRects(index, &result); + return result.adjusted(0, 0, -1, -1); +} + +void ContactListDragView::combineVisualRects(const QModelIndex& parent, QRect* result) const +{ + Q_ASSERT(result); + *result = result->united(visualRect(parent)); + for (int row = 0; row < model()->rowCount(parent); ++row) { + QModelIndex index = model()->index(row, 0, parent); + combineVisualRects(index, result); + } +} + +void ContactListDragView::startDrag(Qt::DropActions supportedActions) +{ + ContactListView::startDrag(supportedActions); +} + +QMimeData* ContactListDragView::selection() const +{ + ContactListDragModel* model = dynamic_cast(realModel()); + if (model && model->contactList() && !selectedIndexes().isEmpty()) { + return model->mimeData(realIndexes(selectedIndexes())); + } + return 0; +} + +void ContactListDragView::restoreSelection(QMimeData* _mimeData) +{ + // setCurrentIndex() actually fires up the timers which in turn could trigger + // some delayed events that would result in mimeData deletion (if the mimeData + // passed was actually backedUpSelection_). So for additional insurance if + // clearSelection() suddenly decides to trigger timers too we put mimeData + // into a QPointer + QPointer mimeData(_mimeData); + setUpdatesEnabled(false); + + // setCurrentIndex(QModelIndex()); + clearSelection(); + + ContactListDragModel* model = dynamic_cast(realModel()); + if (model && !mimeData.isNull()) { + QModelIndexList indexes = proxyIndexes(model->indexesFor(mimeData)); + if (!indexes.isEmpty()) { + setCurrentIndex(indexes.first()); + QItemSelection selection; + foreach(QModelIndex index, indexes) + selection << QItemSelectionRange(index); + selectionModel()->select(selection, QItemSelectionModel::Select); + } + } + + setUpdatesEnabled(true); +} + +ContactListItemMenu* ContactListDragView::createContextMenuFor(ContactListItem* item) const +{ + ContactListItemMenu* menu = ContactListView::createContextMenuFor(item); + if (menu) { + if (menu->metaObject()->indexOfSignal("removeSelection()") != -1) + connect(menu, SIGNAL(removeSelection()), SLOT(removeSelection())); + if (menu->metaObject()->indexOfSignal("removeGroupWithoutContacts(QMimeData*)") != -1) + connect(menu, SIGNAL(removeGroupWithoutContacts(QMimeData*)), SIGNAL(removeGroupWithoutContacts(QMimeData*))); + } + return menu; +} + +void ContactListDragView::removeSelection() +{ + QMimeData* mimeData = selection(); + emit removeSelection(mimeData); + delete mimeData; +} + +bool ContactListDragView::extendedSelectionAllowed() const +{ + return selectedIndexes().count() > 1 || keyboardModifiers_ != 0; +} + +bool ContactListDragView::activateItemsOnSingleClick() const +{ +// #ifndef YAPSI +// return PsiOptions::instance()->getOption("options.ui.contactlist.use-single-click").toBool(); +// #endif + return false; + // return style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, this); + // return !extendedSelectionAllowed() && + // state() != QAbstractItemView::EditingState; +} + +void ContactListDragView::updateKeyboardModifiers(const QEvent* e) +{ + if (e->type() == QEvent::KeyPress || + e->type() == QEvent::KeyRelease || + e->type() == QEvent::MouseButtonDblClick || + e->type() == QEvent::MouseButtonPress || + e->type() == QEvent::MouseButtonRelease || + e->type() == QEvent::MouseMove) + { + keyboardModifiers_ = static_cast(e)->modifiers(); + } +} + +Qt::KeyboardModifiers ContactListDragView::keyboardModifiers() const +{ + return keyboardModifiers_; +} + +bool ContactListDragView::eventFilter(QObject* obj, QEvent* e) +{ + if (obj == this || obj == viewport()) { + updateKeyboardModifiers(e); + } + + return ContactListView::eventFilter(obj, e); +} + +void ContactListDragView::mousePressEvent(QMouseEvent* e) +{ + pressPosition_ = e->pos(); + delete pressedIndex_; + pressedIndex_ = 0; + pressedIndexWasSelected_ = false; + + QModelIndex index = indexAt(e->pos()); + ContactListDragModel* model = dynamic_cast(realModel()); + if (model && index.isValid()) { + QModelIndexList indexes; + indexes << realIndex(index); + pressedIndex_ = model->mimeData(indexes); + } + pressedIndexWasSelected_ = e->button() == Qt::LeftButton && + selectionModel() && + selectionModel()->isSelected(index); + if (!index.isValid()) { + // clear selection + updateCursor(index, UC_MouseClick, true); + } + ContactListView::mousePressEvent(e); +} + +void ContactListDragView::mouseMoveEvent(QMouseEvent* e) +{ + // don't allow any selections after single-clicking on the group button + if (!pressedIndex_ && !pressedIndexWasSelected_ && e->buttons() & Qt::LeftButton) { + e->accept(); + return; + } + + ContactListView::mouseMoveEvent(e); +} + +void ContactListDragView::itemClicked(const QModelIndex& index) +{ + if (activateItemsOnSingleClick()) + return; + + // if clicked item was selected prior to mouse press event, activate it on release + // if (pressedIndexWasSelected_) { + // activate(index); + // } + Q_UNUSED(index); +} + +void ContactListDragView::contextMenuEvent(QContextMenuEvent* e) +{ + QModelIndex index = indexAt(e->pos()); + if (!selectedIndexes().contains(index)) { + updateCursor(index, UC_MouseClick, true); + } + + ContactListView::contextMenuEvent(e); + + if (!e->isAccepted() && viewportMenu_) { + viewportMenu_->exec(e->globalPos()); + } + + repairMouseTracking(); +} + +bool ContactListDragView::drawSelectionBackground() const +{ + return focusPolicy() == Qt::NoFocus || + testAttribute(Qt::WA_UnderMouse) || + extendedSelectionAllowed(); +} + +void ContactListDragView::closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint) +{ + ContactListView::closeEditor(editor, hint); +} + +void ContactListDragView::backupCurrentSelection() +{ + if (backedUpSelection_) { + delete backedUpSelection_; + backedUpSelection_ = 0; + } + + backedUpSelection_ = selection(); +} + +void ContactListDragView::restoreBackedUpSelection() +{ + restoreSelection(backedUpSelection_); + + // if (backedUpSelection_) { + // delete backedUpSelection_; + // backedUpSelection_ = 0; + // } +} + +void ContactListDragView::modelChanged() +{ + if (!dirty_) { + setUpdatesEnabled(false); + backedUpVerticalScrollBarValue_ = verticalScrollBar()->value(); + if (currentEditor()) { + backedUpEditorValue_ = currentEditor()->text(); + closeEditor(currentEditor(), QAbstractItemDelegate::NoHint); + setEditingIndex(currentIndex(), true); + } + else { + backedUpEditorValue_ = QString(); + } + dirty_ = true; + scheduleDelayedItemsLayout(); + } + + // TODO: save the object we're currently editing and the QVariant of the value the user inputted + + // make sure selectionModel() doesn't cache any currently selected indexes + // otherwise it'll overwrite our correctly restored selection with its own + if (selectionModel()) { + selectionModel()->reset(); + } + + updateContextMenu(); +} + +void ContactListDragView::doItemsLayoutStart() +{ +} + +void ContactListDragView::doItemsLayoutFinish() +{ + if (backedUpVerticalScrollBarValue_ != -1) { + verticalScrollBar()->setValue(backedUpVerticalScrollBarValue_); + } +} + +void ContactListDragView::doItemsLayout() +{ + if (dirty_) { + dirty_ = false; + + ContactListDragModel* model = dynamic_cast(realModel()); + if (model) { + doItemsLayoutStart(); + + ContactListGroupState::GroupExpandedState groupExpandedState; + groupExpandedState = model->groupState()->groupExpandedState(); + + // clear all QTreeView::d->expandedIndexes, in order to avoid any potential crashes + // we're going to re-expand them correctly using updateGroupExpandedState() call + collapseAll(); + // this is also useful since restoring selection could mess the group expanding state + model->groupState()->restoreGroupExpandedState(ContactListGroupState::GroupExpandedState()); + + ContactListView::doItemsLayout(); + + model->groupState()->restoreGroupExpandedState(groupExpandedState); + updateGroupExpandedState(); + + doItemsLayoutFinish(); + + restoreBackedUpSelection(); + + if (!backedUpEditorValue_.isNull()) { + // QSortFilterProxyModel* proxyModel = dynamic_cast(model); + // if (proxyModel) { + // proxyModel->invalidate(); + // } + + setFocus(); + rename(); + if (currentEditor()) { + currentEditor()->setText(backedUpEditorValue_); + } + } + } + } + + setUpdatesEnabled(true); +} + +/** + * Prevent emitting activated events when clicking with right mouse button. + */ +void ContactListDragView::mouseReleaseEvent(QMouseEvent* event) +{ + bool filter = false; + ContactListDragModel* model = dynamic_cast(realModel()); + if (model && pressedIndex_) { + QModelIndexList indexes = model->indexesFor(0, pressedIndex_); + QModelIndex index = indexes.count() == 1 ? proxyIndex(indexes.first()) : QModelIndex(); + if (event->button() == Qt::LeftButton && + index.isValid() && + keyboardModifiers() == 0 && + (ContactListModel::indexType(index) == ContactListModel::GroupType || + ContactListModel::indexType(index) == ContactListModel::AccountType)) + { + if ((pressPosition_ - event->pos()).manhattanLength() < QApplication::startDragDistance()) { + QStyleOptionViewItem option; + setExpanded(index, !index.data(ContactListModel::ExpandedRole).toBool()); + event->accept(); + filter = true; + } + } + } + + if (!filter && event->button() & Qt::LeftButton || !activateItemsOnSingleClick()) + ContactListView::mouseReleaseEvent(event); + + pressPosition_ = QPoint(); + delete pressedIndex_; + pressedIndex_ = 0; +} + +void ContactListDragView::setViewportMenu(QMenu* menu) +{ + viewportMenu_ = menu; +} + +int ContactListDragView::backedUpVerticalScrollBarValue() const +{ + return backedUpVerticalScrollBarValue_; +} diff --git a/src/contactlistdragview.h b/src/contactlistdragview.h new file mode 100644 index 000000000..6abf09b5f --- /dev/null +++ b/src/contactlistdragview.h @@ -0,0 +1,158 @@ +/* + * contactlistdragview.h - ContactListView with support for Drag'n'Drop operations + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTDRAGVIEW_H +#define CONTACTLISTDRAGVIEW_H + +#include "contactlistview.h" + +class QTimer; +class QMimeData; +class ContactListDragModel; +class ContactListModelSelection; +class PsiContact; + +class ContactListDragView : public ContactListView +{ + Q_OBJECT + +public: + ContactListDragView(QWidget* parent); + ~ContactListDragView(); + + // reimplemented + void setModel(QAbstractItemModel* model); + + bool textInputInProgress() const; + + enum AvatarMode { + AvatarMode_Disable = 0, + AvatarMode_Auto = 1, + AvatarMode_Big = 2, + AvatarMode_Small = 3 + }; + + AvatarMode avatarMode() const; + void setAvatarMode(AvatarMode avatarMode); + + void setViewportMenu(QMenu* menu); + + QMimeData* selection() const; + void restoreSelection(QMimeData* mimeData); + + bool activateItemsOnSingleClick() const; + bool extendedSelectionAllowed() const; + + virtual bool drawSelectionBackground() const; + +public: + virtual int suggestedItemHeight(); + +signals: + void removeSelection(QMimeData* selection); + void removeGroupWithoutContacts(QMimeData* selection); + +public slots: + void toolTipEntered(PsiContact* contact, QMimeData* contactSelection); + void toolTipHidden(PsiContact* contact, QMimeData* contactSelection); + +protected: + // reimplemented + void mouseDoubleClickEvent(QMouseEvent*); + void leaveEvent(QEvent*); + void paintEvent(QPaintEvent*); + void dragMoveEvent(QDragMoveEvent*); + void dropEvent(QDropEvent*); + void dragEnterEvent(QDragEnterEvent*); + void dragLeaveEvent(QDragLeaveEvent*); + void setItemDelegate(QAbstractItemDelegate* delegate); + void startDrag(Qt::DropActions supportedActions); + bool eventFilter(QObject* obj, QEvent* e); + void closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint); + +protected slots: + // reimplemented + virtual void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + virtual void doItemsLayout(); + + virtual void scrollbarValueChanged(); + void modelChanged(); + +protected: + // reimplemented + virtual void itemActivated(const QModelIndex& index); + virtual void mousePressEvent(QMouseEvent*); + virtual void mouseMoveEvent(QMouseEvent*); + virtual void contextMenuEvent(QContextMenuEvent*); + virtual ContactListItemMenu* createContextMenuFor(ContactListItem* item) const; + virtual void addContextMenuAction(QAction* action); + virtual void mouseReleaseEvent(QMouseEvent* event); + + virtual void doItemsLayoutStart(); + virtual void doItemsLayoutFinish(); + + enum UpdateCursorOrigin { + UC_MouseClick, + UC_MouseHover, + UC_TooltipEntered, + UC_TooltipHidden + }; + + virtual bool updateCursor(const QModelIndex& index, UpdateCursorOrigin origin, bool force); + void updateKeyboardModifiers(const QEvent* e); + Qt::KeyboardModifiers keyboardModifiers() const; + int backedUpVerticalScrollBarValue() const; + int indexCombinedHeight(const QModelIndex& index, QAbstractItemDelegate* delegate) const; + +private slots: + void itemClicked(const QModelIndex& index); + void updateCursorMouseHover(const QModelIndex&); + void updateCursorMouseHover(); + void removeSelection(); + +private: + QMimeData* backedUpSelection_; + int backedUpVerticalScrollBarValue_; + QString backedUpEditorValue_; + QAction* removeAction_; + QRect dropIndicatorRect_; + DropIndicatorPosition dropIndicatorPosition_; + Qt::KeyboardModifiers keyboardModifiers_; + bool dirty_; + QMimeData* pressedIndex_; + QPoint pressPosition_; + bool pressedIndexWasSelected_; + QMenu* viewportMenu_; + + void backupCurrentSelection(); + void restoreBackedUpSelection(); + + QModelIndexList indexesFor(PsiContact* contact, QMimeData* contactSelection) const; + QRect onItemDropRect(const QModelIndex& index) const; + QRect groupReorderDropRect(DropIndicatorPosition dropIndicatorPosition, const ContactListModelSelection& selection, const QModelIndex& index) const; + QModelIndex itemToReorderGroup(const ContactListModelSelection& selection, const QModelIndex& index) const; + DropIndicatorPosition dropPosition(QDropEvent* e, const ContactListModelSelection& selection, const QModelIndex& index) const; + QRect groupVisualRect(const QModelIndex& index) const; + void combineVisualRects(const QModelIndex& index, QRect* result) const; + bool supportsDropOnIndex(QDropEvent* e, const QModelIndex& index) const; + void reorderGroups(QDropEvent* e, const QModelIndex& index); +}; + +#endif diff --git a/src/contactlistgroup.cpp b/src/contactlistgroup.cpp new file mode 100755 index 000000000..22282704e --- /dev/null +++ b/src/contactlistgroup.cpp @@ -0,0 +1,491 @@ +/* + * contactlistgroup.cpp - flat contact list group class + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistgroup.h" + +#include + +#include "contactlistitemproxy.h" +#include "contactlistgroupmenu.h" +#include "contactlistmodel.h" +#include "psicontact.h" +#include "contactlistgroupstate.h" +#include "contactlistgroupcache.h" +#ifdef YAPSI +#include "fakegroupcontact.h" +#endif + +static QString GROUP_DELIMITER = "::"; + +/** + * Flat group class. + */ +ContactListGroup::ContactListGroup(ContactListModel* model, ContactListGroup* parent) + : ContactListItem() + , model_(model) + , parent_(parent) + , updateOnlineContactsTimer_(0) + , haveOnlineContacts_(false) + , onlineContactsCount_(0) + , totalContactsCount_(0) +{ + updateOnlineContactsTimer_ = new QTimer(this); + connect(updateOnlineContactsTimer_, SIGNAL(timeout()), SLOT(updateOnlineContactsFlag())); + updateOnlineContactsTimer_->setSingleShot(true); + updateOnlineContactsTimer_->setInterval(100); +} + +ContactListGroup::~ContactListGroup() +{ + clearGroup(); +} + +void ContactListGroup::clearGroup() +{ + foreach(PsiContact* contact, contacts_) + removeContact(contact); + qDeleteAll(contacts_); + contacts_.clear(); +} + +/** + * Used only in fullName(). + */ +QString ContactListGroup::internalGroupName() const +{ + return name(); +} + +QString ContactListGroup::fullName() const +{ + QStringList name; + const ContactListGroup* group = this; + while (group) { + if (!group->internalGroupName().isEmpty()) + name.prepend(group->internalGroupName()); + group = group->parent(); + } + return name.join(groupDelimiter()); +} + +const QString& ContactListGroup::groupDelimiter() +{ + return GROUP_DELIMITER; +} + +void ContactListGroup::setGroupDelimiter(const QString& str) +{ + GROUP_DELIMITER = str; +} + +QString ContactListGroup::sanitizeGroupName(const QString& name) const +{ + return name.split(groupDelimiter(), QString::SkipEmptyParts).join(groupDelimiter()); +} + +QStringList ContactListGroup::sanitizeGroupNames(const QStringList& names) const +{ + QStringList sanitized; + foreach(QString name, names) { + sanitized.append(sanitizeGroupName(name)); + } + return sanitized; +} + +ContactListModel::Type ContactListGroup::type() const +{ + return ContactListModel::GroupType; +} + +const QString& ContactListGroup::name() const +{ + return name_; +} + +void ContactListGroup::setName(const QString& name) +{ + model()->renameGroup(this, name); +} + +/** + * Renames the group without telling the model. + */ +void ContactListGroup::quietSetName(const QString& name) +{ + if (!name_.isNull()) + model_->groupCache()->removeGroup(this); + + name_ = name; + + if (!name_.isNull()) + model_->groupCache()->addGroup(this); +} + +bool ContactListGroup::isExpandable() const +{ + return true; +} + +bool ContactListGroup::expanded() const +{ + return model()->groupState()->groupExpanded(this); +} + +void ContactListGroup::setExpanded(bool expanded) +{ + model()->groupState()->setGroupExpanded(this, expanded); +} + +bool ContactListGroup::isEditable() const +{ + bool result = false; + foreach(ContactListItemProxy* proxy, items_) { + if (proxy->item()) { + if (proxy->item()->isEditable()) { + result = true; + break; + } + } + } + + return result && model()->contactList()->haveAvailableAccounts(); +} + +bool ContactListGroup::isRemovable() const +{ + bool result = false; + foreach(ContactListItemProxy* proxy, items_) { + if (proxy->item()) { + if (proxy->item()->isRemovable()) { + result = true; + break; + } + } + } + + return result && isEditable(); +} + +ContactListItemMenu* ContactListGroup::contextMenu(ContactListModel* model) +{ + return new ContactListGroupMenu(this, model); +} + +void ContactListGroup::addItem(ContactListItemProxy* item) +{ + Q_ASSERT(!items_.contains(item)); + int index = items_.count(); + model_->itemAboutToBeInserted(this, index); + items_.append(item); + model_->insertedItem(this, index); + updateOnlineContactsTimer_->start(); +} + +void ContactListGroup::removeItem(ContactListItemProxy* item) +{ + int index = items_.indexOf(item); + Q_ASSERT(index != -1); + model_->itemAboutToBeRemoved(this, index); + items_.remove(index); + delete item; + model_->removedItem(this, index); + updateOnlineContactsTimer_->start(); +} + +/** + * contactGroups handling rules: + * 1. List is empty: we must not add this contact to self; + * 2. List contains null QString() element: we must add contact to General group + * 3. List contains non-null QString() elements: those contain group names (with group separators) + */ +void ContactListGroup::addContact(PsiContact* contact, QStringList contactGroups) +{ + if (contactGroups.isEmpty()) + return; + + if (findContact(contact)) + return; + Q_ASSERT(!contacts_.contains(contact)); +// qWarning("ContactListGroup(%x)::addContact: %s (items = %d, contacts = %d)", this, qPrintable(contact->jid().full()), items_.count(), contacts_.count()); + contacts_.append(contact); + addItem(new ContactListItemProxy(this, contact)); + + model_->groupCache()->addContact(this, contact); +} + +void ContactListGroup::removeContact(PsiContact* contact) +{ + int index = contacts_.indexOf(contact); + Q_ASSERT(index != -1); +// qWarning("ContactListGroup(%x)::removeContact: %s (items = %d, contacts = %d)", this, qPrintable(contact->jid().full()), items_.count(), contacts_.count()); + removeItem(findContact(contact)); + contacts_.remove(index); + + model_->groupCache()->removeContact(this, contact); +} + +// Some room for future optimizations here +ContactListItemProxy* ContactListGroup::findContact(PsiContact* contact) const +{ + foreach(ContactListItemProxy* item, items_) + if (item->item() == contact) + return item; + return 0; +} + +ContactListItemProxy* ContactListGroup::findGroup(ContactListGroup* group) const +{ + foreach(ContactListItemProxy* item, items_) + if (item->item() == group) + return item; + return 0; +} + +// ContactListItemProxy* ContactListGroup::findAccount(ContactListAccountGroup* account) const +// { +// foreach(ContactListItemProxy* item, items_) +// if (item->item() == account) +// return item; +// return 0; +// } + +void ContactListGroup::contactUpdated(PsiContact* contact) +{ + ContactListItemProxy* item = findContact(contact); + if (!item) + return; + updateOnlineContactsTimer_->start(); + model_->updatedItem(item); +} + +void ContactListGroup::contactGroupsChanged(PsiContact* contact, QStringList contactGroups) +{ + if (contactGroups.isEmpty()) { + if (contacts_.contains(contact)) + removeContact(contact); + } + else if (!findContact(contact)) { + addContact(contact, contactGroups); + } + + updateOnlineContactsTimer_->start(); +} + +ContactListItemProxy* ContactListGroup::item(int index) const +{ + Q_ASSERT(index >= 0); + Q_ASSERT(index < items_.count()); + return items_.at(index); +} + +int ContactListGroup::itemsCount() const +{ + return items_.count(); +} + +// Some room for optimizations here +int ContactListGroup::indexOf(const ContactListItem* item) const +{ + for (int i = 0; i < items_.count(); ++i) + if (items_.at(i)->item() == item) + return i; + Q_ASSERT(false); + return -1; +} + +ContactListGroup* ContactListGroup::parent() const +{ + return parent_; +} + +QModelIndex ContactListGroup::toModelIndex() const +{ + if (!parent()) + return QModelIndex(); + + int index = parent()->indexOf(this); + return model()->itemProxyToModelIndex(parent()->item(index), index); +} + +const QVector& ContactListGroup::items() const +{ + return items_; +} + +bool ContactListGroup::haveOnlineContacts() const +{ + return haveOnlineContacts_; +} + +int ContactListGroup::onlineContactsCount() const +{ + return onlineContactsCount_; +} + +int ContactListGroup::totalContactsCount() const +{ + return totalContactsCount_; +} + +int ContactListGroup::contactsCount() const +{ + return contacts_.count(); +} + +void ContactListGroup::updateOnlineContactsFlag() +{ + updateOnlineContactsTimer_->stop(); + if (!parent()) + return; + + bool haveOnlineContacts = false; + int onlineContactsCount = 0; + int totalContactsCount = 0; + foreach(ContactListItemProxy* item, items_) { + PsiContact* contact = 0; + ContactListGroup* group = 0; + if ((contact = dynamic_cast(item->item()))) { + ++totalContactsCount; + if (contact->isOnline()) { + haveOnlineContacts = true; + ++onlineContactsCount; + // break; + } + } + else if ((group = dynamic_cast(item->item()))) { + totalContactsCount += group->totalContactsCount(); + if (group->haveOnlineContacts()) { + haveOnlineContacts = true; + onlineContactsCount += group->onlineContactsCount(); + // break; + } + } + } + + if (haveOnlineContacts_ != haveOnlineContacts) { + haveOnlineContacts_ = haveOnlineContacts; + if (parent()) { + parent()->updateOnlineContactsFlag(); + model_->updatedGroupVisibility(this); + } + } + + if (onlineContactsCount != onlineContactsCount_ || + totalContactsCount != totalContactsCount_) + { + onlineContactsCount_ = onlineContactsCount; + totalContactsCount_ = totalContactsCount; + if (parent()) { + model()->updatedItem(parent()->findGroup(this)); + } + } +} + +bool ContactListGroup::isFake() const +{ + if (items_.count() != contacts_.count()) + return false; + + foreach(PsiContact* contact, contacts_) { + if (!contact->isFake()) + return false; + } + +// FIXME +#ifdef YAPSI + return name().startsWith(FakeGroupContact::defaultGroupName()); +#else + return false; +#endif +} + +bool ContactListGroup::compare(const ContactListItem* other) const +{ + const ContactListGroup* group = dynamic_cast(other); + if (group) { + if (group->isSpecial()) { + return !group->compare(this); + } + + int order = model()->groupState()->groupOrder(group) - model()->groupState()->groupOrder(this); + if (order) { + return order > 0; + } + } + + return ContactListItem::compare(other); +} + +QList ContactListGroup::contacts() const +{ + QList result; + contactsHelper(&result); + return result; +} + +void ContactListGroup::contactsHelper(QList* contacts) const +{ + foreach(PsiContact* contact, contacts_) { + if (!contacts->contains(contact)) + contacts->append(contact); + } + + foreach(ContactListItemProxy* item, items_) { + ContactListGroup* group = dynamic_cast(item->item()); + if (group) + contactsHelper(contacts); + } +} + +#ifdef UNIT_TEST +void ContactListGroup::dumpTree(int indent) const +{ + dumpInfo(this, 0); + foreach(ContactListItemProxy* item, items_) { + ContactListGroup* group = dynamic_cast(item->item()); + if (group) + dumpTree(indent + 1); + else + dumpInfo(item->item(), indent + 1); + } +} + +void ContactListGroup::dumpInfo(const ContactListItem* item, int indent) const +{ + qWarning("%sname = '%s', type = %s", qPrintable(QString(indent, ' ')), + qPrintable(item->name()), + dynamic_cast(item) ? "group" : "contact"); +} + +void ContactListGroup::dumpTree() const +{ + dumpTree(0); +} +#endif + +bool ContactListGroup::isSpecial() const +{ + return false; +} + +ContactListGroup::SpecialType ContactListGroup::specialGroupType() const +{ + return SpecialType_None; +} diff --git a/src/contactlistgroup.h b/src/contactlistgroup.h new file mode 100755 index 000000000..99b593815 --- /dev/null +++ b/src/contactlistgroup.h @@ -0,0 +1,134 @@ +/* + * contactlistgroup.h - flat contact list group class + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTGROUP_H +#define CONTACTLISTGROUP_H + +#include "contactlistitem.h" + +#include +#include + +class QTimer; + +class PsiContact; +class ContactListModel; +class ContactListItemMenu; +class ContactListItemProxy; +class ContactListAccountGroup; + +class ContactListGroup : public ContactListItem +{ + Q_OBJECT +public: + ContactListGroup(ContactListModel* model, ContactListGroup* parent); + ~ContactListGroup(); + + ContactListItemProxy* item(int index) const; + int itemsCount() const; + int indexOf(const ContactListItem* item) const; + + ContactListModel* model() const { return model_; } + + ContactListGroup* parent() const; + QModelIndex toModelIndex() const; + + bool isFake() const; + bool haveOnlineContacts() const; + int onlineContactsCount() const; + int totalContactsCount() const; + int contactsCount() const; + + QList contacts() const; + + virtual void addContact(PsiContact* contact, QStringList contactGroups); + virtual void contactUpdated(PsiContact* contact); + virtual void contactGroupsChanged(PsiContact* contact, QStringList contactGroups); + + virtual QString internalGroupName() const; + QString fullName() const; + + // reimplemented + virtual ContactListModel::Type type() const; + virtual const QString& name() const; + virtual void setName(const QString& name); + virtual bool isEditable() const; + virtual bool isRemovable() const; + virtual bool isExpandable() const; + virtual bool expanded() const; + virtual void setExpanded(bool expanded); + virtual ContactListItemMenu* contextMenu(ContactListModel* model); + virtual bool compare(const ContactListItem* other) const; + + static const QString& groupDelimiter(); + static void setGroupDelimiter(const QString&); + QString sanitizeGroupName(const QString&) const; + QStringList sanitizeGroupNames(const QStringList& names) const; + +#ifdef UNIT_TEST + void dumpTree() const; +#endif + + enum SpecialType { + SpecialType_None = 0, + + // SpecialType_General, + SpecialType_NotInList, + SpecialType_Transports, + SpecialType_MUCPrivateChats + }; + + virtual bool isSpecial() const; + virtual SpecialType specialGroupType() const; + +protected: + void addItem(ContactListItemProxy* item); + void removeItem(ContactListItemProxy* item); + ContactListItemProxy* findContact(PsiContact* contact) const; + ContactListItemProxy* findGroup(ContactListGroup* group) const; + // ContactListItemProxy* findAccount(ContactListAccountGroup* account) const; + const QVector& items() const; + void quietSetName(const QString& name); + + virtual void clearGroup(); + virtual void contactsHelper(QList* contacts) const; + +public slots: + void updateOnlineContactsFlag(); + +private: + ContactListModel* model_; + ContactListGroup* parent_; + QTimer* updateOnlineContactsTimer_; + QString name_; + QVector contacts_; + QVector items_; + bool haveOnlineContacts_; + int onlineContactsCount_; + int totalContactsCount_; + + void removeContact(PsiContact* contact); +#ifdef UNIT_TEST + void dumpInfo(const ContactListItem* item, int indent) const; + void dumpTree(int indent) const; +#endif +}; + +#endif diff --git a/src/contactlistgroupcache.cpp b/src/contactlistgroupcache.cpp new file mode 100644 index 000000000..e30f525ab --- /dev/null +++ b/src/contactlistgroupcache.cpp @@ -0,0 +1,100 @@ +/* + * contactlistgroupcache.cpp - contact list group cache + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistgroupcache.h" + +#include + +#include "contactlistgroup.h" +#include "psicontact.h" + +ContactListGroupCache::ContactListGroupCache(QObject *parent) + : QObject(parent) +{ +} + +ContactListGroupCache::~ContactListGroupCache() +{ +} + +QStringList ContactListGroupCache::groups() const +{ + return groups_.keys(); +} + +QList ContactListGroupCache::groupsFor(PsiContact* contact) const +{ + return contacts_[contact]; +} + +ContactListGroup* ContactListGroupCache::findGroup(const QString& fullName) const +{ + return groups_[fullName]; +} + +void ContactListGroupCache::addContact(ContactListGroup* group, PsiContact* contact) +{ + Q_ASSERT(group); + Q_ASSERT(contact); + if (!contacts_.contains(contact)) + contacts_[contact] = QList(); + + if (!contacts_[contact].contains(group)) + contacts_[contact] << group; +} + +void ContactListGroupCache::removeContact(ContactListGroup* group, PsiContact* contact) +{ + Q_ASSERT(group); + Q_ASSERT(contact); + if (contacts_[contact].contains(group)) + contacts_[contact].removeAll(group); + + if (contacts_[contact].isEmpty()) + contacts_.remove(contact); +} + +void ContactListGroupCache::addGroup(ContactListGroup* group) +{ + Q_ASSERT(group); + groups_[group->fullName()] = group; +} + +void ContactListGroupCache::removeGroup(ContactListGroup* group) +{ + groups_.remove(group->fullName()); +} + +bool ContactListGroupCache::hasContacts(bool onlineOnly) const +{ + bool result = false; + QHashIterator > it(contacts_); + while (it.hasNext()) { + it.next(); + if (!it.key()->isHidden()) { + if (onlineOnly && !it.key()->isOnline()) + continue; + + result = true; + break; + } + } + return result; +} diff --git a/src/contactlistgroupcache.h b/src/contactlistgroupcache.h new file mode 100644 index 000000000..c581143e5 --- /dev/null +++ b/src/contactlistgroupcache.h @@ -0,0 +1,53 @@ +/* + * contactlistgroupcache.h - contact list group cache + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTGROUPCACHE_H +#define CONTACTLISTGROUPCACHE_H + +#include + +class ContactListGroup; +class PsiContact; + +class ContactListGroupCache : public QObject +{ + Q_OBJECT +public: + ContactListGroupCache(QObject *parent); + ~ContactListGroupCache(); + + QStringList groups() const; + bool hasContacts(bool onlineOnly) const; + + QList groupsFor(PsiContact* contact) const; + ContactListGroup* findGroup(const QString& fullName) const; + + void addContact(ContactListGroup* group, PsiContact* contact); + void removeContact(ContactListGroup* group, PsiContact* contact); + + void addGroup(ContactListGroup* group); + void removeGroup(ContactListGroup* group); + +private: + QHash > contacts_; + QHash groups_; +}; + +#endif diff --git a/src/contactlistgroupmenu.cpp b/src/contactlistgroupmenu.cpp new file mode 100755 index 000000000..37bc5ad1d --- /dev/null +++ b/src/contactlistgroupmenu.cpp @@ -0,0 +1,176 @@ +/* + * contactlistgroupmenu.cpp - context menu for contact list groups + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistgroupmenu.h" + +#include +#include + +#include "iconaction.h" +#include "iconset.h" +#include "contactlistgroup.h" +#include "psioptions.h" +#include "shortcutmanager.h" +#include "psicontactlist.h" +#include "iconaction.h" +#include "psicontact.h" +#include "psiaccount.h" + +class ContactListGroupMenu::Private : public QObject +{ + Q_OBJECT + + QPointer group; + QAction* renameAction_; + QAction* removeGroupAndContactsAction_; +#ifdef YAPSI + QAction* addGroupAction_; +#endif + QAction* sendMessageAction_; + QAction* removeGroupWithoutContactsAction_; + +public: + Private(ContactListGroupMenu* menu, ContactListGroup* _group) + : QObject(0) + , group(_group) + , menu_(menu) + { + connect(menu, SIGNAL(aboutToShow()), SLOT(updateActions())); + + renameAction_ = new IconAction("", tr("Re&name"), menu->shortcuts("contactlist.rename"), this, "act_rename"); + connect(renameAction_, SIGNAL(triggered()), this, SLOT(rename())); + + removeGroupAndContactsAction_ = new IconAction(tr("Remove Group and Contacts"), this, "psi/remove"); +#ifdef YAPSI + removeGroupAndContactsAction_->setText(tr("&Remove")); +#endif + removeGroupAndContactsAction_->setShortcuts(ShortcutManager::instance()->shortcuts("contactlist.delete")); + connect(removeGroupAndContactsAction_, SIGNAL(triggered()), SLOT(removeGroupAndContacts())); + + removeGroupWithoutContactsAction_ = new IconAction(tr("Remove Group"), this, "psi/remove"); + connect(removeGroupWithoutContactsAction_, SIGNAL(triggered()), this, SLOT(removeGroupWithoutContacts())); + + sendMessageAction_ = new IconAction(tr("Send Message to Group"), this, "psi/sendMessage"); + connect(sendMessageAction_, SIGNAL(triggered()), SLOT(sendMessage())); + +#ifdef YAPSI + addGroupAction_ = new QAction(tr("&Add group..."), this); + connect(addGroupAction_, SIGNAL(triggered()), SLOT(addGroup())); +#endif + + updateActions(); + +#ifdef YAPSI + menu->addAction(renameAction_); + menu->addAction(removeGroupAndContactsAction_); + menu->addAction(addGroupAction_); +#else + menu->addAction(renameAction_); + menu->addAction(sendMessageAction_); + menu->addSeparator(); + menu->addAction(removeGroupWithoutContactsAction_); + menu->addAction(removeGroupAndContactsAction_); +#endif + } + +private slots: + void updateActions() + { + if (!group) + return; + + sendMessageAction_->setVisible(PsiOptions::instance()->getOption("options.ui.message.enabled").toBool()); + renameAction_->setEnabled(group->isEditable()); + removeGroupAndContactsAction_->setEnabled(group->isRemovable()); + removeGroupWithoutContactsAction_->setEnabled(group->isRemovable()); +#ifdef YAPSI + addGroupAction_->setEnabled(group->model()->contactList()->haveAvailableAccounts()); +#endif + } + + void rename() + { + if (!group) + return; + + menu_->model()->renameSelectedItem(); + } + + void removeGroupAndContacts() + { + if (!group) + return; + + emit menu_->removeSelection(); + } + + void removeGroupWithoutContacts() + { + if (!group) + return; + + QModelIndexList indexes; + indexes += group->model()->groupToIndex(group); + QMimeData* data = group->model()->mimeData(indexes); + emit menu_->removeGroupWithoutContacts(data); + delete data; + } + + void sendMessage() + { + if (!group) + return; + + QList contacts = group->contacts(); + if (!contacts.isEmpty()) { + QList list; + foreach(PsiContact* contact, contacts) { + list << contact->jid(); + } + contacts.first()->account()->actionSendMessage(list); + } + } + +#ifdef YAPSI + void addGroup() + { + if (!group) + return; + + emit menu_->addGroup(); + } +#endif + +private: + ContactListGroupMenu* menu_; +}; + +ContactListGroupMenu::ContactListGroupMenu(ContactListGroup* group, ContactListModel* model) + : ContactListItemMenu(group, model) +{ + d = new Private(this, group); +} + +ContactListGroupMenu::~ContactListGroupMenu() +{ + delete d; +} + +#include "contactlistgroupmenu.moc" diff --git a/src/contactlistgroupmenu.h b/src/contactlistgroupmenu.h new file mode 100755 index 000000000..f186e042b --- /dev/null +++ b/src/contactlistgroupmenu.h @@ -0,0 +1,48 @@ +/* + * contactlistgroupmenu.h - context menu for contact list groups + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTGROUPMENU_H +#define CONTACTLISTGROUPMENU_H + +#include "contactlistitemmenu.h" + +class ContactListGroup; +class QMimeData; + +class ContactListGroupMenu : public ContactListItemMenu +{ + Q_OBJECT +public: + ContactListGroupMenu(ContactListGroup* group, ContactListModel* model); + ~ContactListGroupMenu(); + +signals: + void removeSelection(); + void removeGroupWithoutContacts(QMimeData*); +#ifdef YAPSI + void addGroup(); +#endif + +private: + class Private; + Private* d; +}; + +#endif diff --git a/src/contactlistgroupstate.cpp b/src/contactlistgroupstate.cpp new file mode 100644 index 000000000..f349a2271 --- /dev/null +++ b/src/contactlistgroupstate.cpp @@ -0,0 +1,227 @@ +/* + * contactlistgroupstate.cpp - saves state of groups in a contact list model + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistgroupstate.h" + +#include +#include +#include + +#include "contactlistmodel.h" +#include "contactlistgroup.h" +#include "psioptions.h" +#include "xmpp_xmlcommon.h" + +static const QString groupStateOptionPath = "options.ya.main-window.contact-list.group-state.%1"; + +ContactListGroupState::ContactListGroupState(QObject* parent) + : QObject(parent) +{ + orderChangedTimer_ = new QTimer(this); + connect(orderChangedTimer_, SIGNAL(timeout()), SIGNAL(orderChanged())); + orderChangedTimer_->setSingleShot(true); + orderChangedTimer_->setInterval(0); + + saveGroupStateTimer_ = new QTimer(this); + connect(saveGroupStateTimer_, SIGNAL(timeout()), SLOT(save())); + saveGroupStateTimer_->setSingleShot(true); + saveGroupStateTimer_->setInterval(1000); +} + +ContactListGroupState::~ContactListGroupState() +{ +} + +bool ContactListGroupState::groupExpanded(const ContactListGroup* group) const +{ + Q_ASSERT(group); + QString fullName = group->fullName(); + if (expanded_.contains(fullName)) + return expanded_[fullName]; + return true; +} + +void ContactListGroupState::setGroupExpanded(const ContactListGroup* group, bool expanded) +{ + Q_ASSERT(group); + expanded_[group->fullName()] = expanded; + saveGroupStateTimer_->start(); +} + +ContactListGroupState::GroupExpandedState ContactListGroupState::groupExpandedState() const +{ + return expanded_; +} + +void ContactListGroupState::restoreGroupExpandedState(ContactListGroupState::GroupExpandedState groupExpandedState) +{ + expanded_ = groupExpandedState; +} + +int ContactListGroupState::groupOrder(const ContactListGroup* group) const +{ + Q_ASSERT(group); + QString fullName = group->fullName(); + if (order_.contains(fullName)) + return order_[fullName]; + if (group->isFake()) + return -1; + return 0; +} + +void ContactListGroupState::setGroupOrder(const ContactListGroup* group, int order) +{ + orderChangedTimer_->start(); + saveGroupStateTimer_->start(); + + Q_ASSERT(group); + order_[group->fullName()] = order; +} + +void ContactListGroupState::updateGroupList(const ContactListModel* model) +{ + GroupExpandedState newExpanded; + QMap newOrder; + + foreach(QString group, groupNames(model, QModelIndex(), QStringList())) { + if (expanded_.contains(group)) + if (!expanded_[group]) + newExpanded[group] = expanded_[group]; + + if (order_.contains(group)) + newOrder[group] = order_[group]; + } + + expanded_ = newExpanded; + order_ = newOrder; +} + +QStringList ContactListGroupState::groupNames(const ContactListModel* model, const QModelIndex& parent, QStringList parentName) const +{ + QStringList result; + + for (int row = 0; row < model->rowCount(parent); ++row) { + QModelIndex index = model->index(row, 0, parent); + if (model->indexType(index) == ContactListModel::GroupType || + model->indexType(index) == ContactListModel::AccountType) + { + QStringList groupName = parentName; + groupName << index.data(ContactListModel::InternalGroupNameRole).toString(); + foreach(QString name, groupNames(model, index, groupName)) { + if (!result.contains(name)) + result += name; + } + } + } + + if (result.isEmpty()) { + for (int len = 1; len < parentName.count() + 1; ++len) + result += QStringList(parentName.mid(0, len)).join(ContactListGroup::groupDelimiter()); + } + + return result; +} + +void ContactListGroupState::load(const QString& id) +{ + expanded_.clear(); + order_.clear(); + + id_ = id; + if (id_.isEmpty()) + return; + + QDomDocument doc; + if (!doc.setContent(PsiOptions::instance()->getOption(groupStateOptionPath.arg(id_)).toString())) + return; + + QDomElement root = doc.documentElement(); + if (root.tagName() != "group-state" || root.attribute("version") != "1.0") + return; + + { + QDomElement expanded = findSubTag(root, "expanded", 0); + if (!expanded.isNull()) { + for (QDomNode n = expanded.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if (e.isNull()) + continue; + + if (e.tagName() == "item") { + expanded_[e.attribute("fullName")] = e.text() == "true"; + } + } + } + } + { + QDomElement order = findSubTag(root, "order", 0); + if (!order.isNull()) { + for (QDomNode n = order.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if (e.isNull()) + continue; + + if (e.tagName() == "item") { + order_[e.attribute("fullName")] = e.text().toInt(); + } + } + } + } +} + +void ContactListGroupState::save() +{ + if (id_.isEmpty()) + return; + + QDomDocument doc; + QDomElement root = doc.createElement("group-state"); + root.setAttribute("version", "1.0"); + doc.appendChild(root); + + { + QDomElement expanded = XMLHelper::emptyTag(&doc, "expanded"); + root.appendChild(expanded); + + QMap::iterator i = expanded_.begin(); + for (; i != expanded_.end(); ++i) { + if (i.value() == false) { + QDomElement item = textTag(&doc, "item", "false"); + item.setAttribute("fullName", i.key()); + expanded.appendChild(item); + } + } + } + { + QDomElement order = XMLHelper::emptyTag(&doc, "order"); + root.appendChild(order); + + QMap::iterator i = order_.begin(); + for (; i != order_.end(); ++i) { + if (i.value() != 0) { + QDomElement item = textTag(&doc, "item", QString::number(i.value())); + item.setAttribute("fullName", i.key()); + order.appendChild(item); + } + } + } + + PsiOptions::instance()->setOption(groupStateOptionPath.arg(id_), doc.toString()); +} diff --git a/src/contactlistgroupstate.h b/src/contactlistgroupstate.h new file mode 100644 index 000000000..90d7bfa75 --- /dev/null +++ b/src/contactlistgroupstate.h @@ -0,0 +1,73 @@ +/* + * contactlistgroupstate.h - saves state of groups in a contact list model + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTGROUPSTATE_H +#define CONTACTLISTGROUPSTATE_H + +#include +#include + +class ContactListModel; +class ContactListGroup; + +class QTimer; +class QModelIndex; +class QDomElement; +class QDomDocument; + +class ContactListGroupState : public QObject +{ + Q_OBJECT +public: + typedef QMap GroupExpandedState; + + ContactListGroupState(QObject* parent = 0); + ~ContactListGroupState(); + + bool groupExpanded(const ContactListGroup* group) const; + void setGroupExpanded(const ContactListGroup* group, bool expanded); + + GroupExpandedState groupExpandedState() const; + void restoreGroupExpandedState(GroupExpandedState groupExpandedState); + + int groupOrder(const ContactListGroup* group) const; + void setGroupOrder(const ContactListGroup* group, int order); + + void updateGroupList(const ContactListModel* model); + + void load(const QString& id); + +public slots: + void save(); + +signals: + void orderChanged(); + +private: + QTimer* orderChangedTimer_; + QTimer* saveGroupStateTimer_; + QString id_; + GroupExpandedState expanded_; + QMap order_; + + QStringList groupNames(const ContactListModel* model, const QModelIndex& parent, QStringList parentName) const; +}; + +#endif diff --git a/src/contactlistitem.cpp b/src/contactlistitem.cpp new file mode 100644 index 000000000..33b901ff6 --- /dev/null +++ b/src/contactlistitem.cpp @@ -0,0 +1,104 @@ +/* + * contactlistitem.cpp - base class for contact list items + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistitem.h" + +#include "psicontact.h" + +ContactListItem::ContactListItem(QObject* parent) + : QObject(parent) + , editing_(false) +{ +} + +ContactListItem::~ContactListItem() +{ +} + +bool ContactListItem::isEditable() const +{ + return false; +} + +bool ContactListItem::isDragEnabled() const +{ + return isEditable(); +} + +bool ContactListItem::isRemovable() const +{ + return false; +} + +bool ContactListItem::isExpandable() const +{ + return false; +} + +bool ContactListItem::expanded() const +{ + return false; +} + +void ContactListItem::setExpanded(bool expanded) +{ + Q_UNUSED(expanded); +} + +ContactListItemMenu* ContactListItem::contextMenu(ContactListModel* model) +{ + Q_UNUSED(model); + return 0; +} + +bool ContactListItem::isFixedSize() const +{ + return true; +} + +bool ContactListItem::compare(const ContactListItem* other) const +{ + const PsiContact* left = dynamic_cast(this); + const PsiContact* right = dynamic_cast(other); + if (!left ^ !right) { + return !right; + } + return comparisonName() < other->comparisonName(); +} + +QString ContactListItem::comparisonName() const +{ + return name(); +} + +bool ContactListItem::editing() const +{ + return editing_; +} + +void ContactListItem::setEditing(bool editing) +{ + editing_ = editing; +} + +const QString& ContactListItem::displayName() const +{ + return name(); +} diff --git a/src/contactlistitem.h b/src/contactlistitem.h new file mode 100644 index 000000000..44cb636c8 --- /dev/null +++ b/src/contactlistitem.h @@ -0,0 +1,65 @@ +/* + * contactlistitem.h - base class for contact list items + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTITEM_H +#define CONTACTLISTITEM_H + +#include +#include + +#include "contactlistmodel.h" + +class ContactListItemMenu; + +class ContactListItem : public QObject +{ +public: + ContactListItem(QObject* parent = 0); + virtual ~ContactListItem(); + + virtual ContactListModel::Type type() const = 0; + + virtual const QString& displayName() const; + virtual const QString& name() const = 0; + virtual void setName(const QString& name) = 0; + virtual QString comparisonName() const; + + virtual bool isEditable() const; + virtual bool isDragEnabled() const; + virtual bool isRemovable() const; + + virtual bool isExpandable() const; + virtual bool expanded() const; + virtual void setExpanded(bool expanded); + + virtual ContactListItemMenu* contextMenu(ContactListModel* model); + + virtual bool isFixedSize() const; + + virtual bool compare(const ContactListItem* other) const; + + virtual bool editing() const; + virtual void setEditing(bool editing); + +private: + bool editing_; +}; + +#endif diff --git a/src/contactlistitemmenu.cpp b/src/contactlistitemmenu.cpp new file mode 100644 index 000000000..777a92bc4 --- /dev/null +++ b/src/contactlistitemmenu.cpp @@ -0,0 +1,78 @@ +/* + * contactlistitemmenu.cpp - base class for contact list item context menus + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistitemmenu.h" + +#include "shortcutmanager.h" + +ContactListItemMenu::ContactListItemMenu(ContactListItem* item, ContactListModel* model) + : QMenu(0) + , item_(item) + , model_(model) +{ +} + +ContactListItemMenu::~ContactListItemMenu() +{ +} + +ContactListItem* ContactListItemMenu::item() const +{ + return item_; +} + +/** + * Removes all actions which objectNames are present in \param actionNames. + */ +void ContactListItemMenu::removeActions(QStringList actionNames) +{ + foreach(QString actionName, actionNames) { + foreach(QAction* action, actions()) { + if (action->objectName() == actionName) { + delete action; + break; + } + } + } +} + +QList ContactListItemMenu::availableActions() const +{ + QList result; + foreach(QAction* action, actions()) + if (!action->isSeparator()) + result << action; + return result; +} + +QList ContactListItemMenu::shortcuts(const QString& name) const +{ + return ShortcutManager::instance()->shortcuts(name); +} + +QKeySequence ContactListItemMenu::shortcut(const QString& name) const +{ + return ShortcutManager::instance()->shortcut(name); +} + +ContactListModel* ContactListItemMenu::model() const +{ + return model_; +} diff --git a/src/contactlistitemmenu.h b/src/contactlistitemmenu.h new file mode 100644 index 000000000..c581bdc74 --- /dev/null +++ b/src/contactlistitemmenu.h @@ -0,0 +1,53 @@ +/* + * contactlistitemmenu.h - base class for contact list item context menus + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTITEMMENU_H +#define CONTACTLISTITEMMENU_H + +#include +#include + +class ContactListModel; +class ContactListItem; +class QAction; + +class ContactListItemMenu : public QMenu +{ + Q_OBJECT +public: + ContactListItemMenu(ContactListItem* item, ContactListModel* model); + virtual ~ContactListItemMenu(); + + virtual ContactListItem* item() const; + + virtual void removeActions(QStringList actionNames); + virtual QList availableActions() const; + +protected: + QKeySequence shortcut(const QString& name) const; + QList shortcuts(const QString& name) const; + ContactListModel* model() const; + +private: + ContactListItem* item_; + ContactListModel* model_; +}; + +#endif diff --git a/src/contactlistitemproxy.cpp b/src/contactlistitemproxy.cpp new file mode 100644 index 000000000..ca17b512e --- /dev/null +++ b/src/contactlistitemproxy.cpp @@ -0,0 +1,37 @@ +/* + * contactlistitemproxy.cpp - proxy item contact list item class + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistitemproxy.h" + +#include "contactlistgroup.h" +#include "contactlistmodel.h" + +ContactListItemProxy::ContactListItemProxy(ContactListGroup* parent, ContactListItem* item) + : item_(item) + , parent_(parent) +{ + Q_ASSERT(item); + Q_ASSERT(parent); + parent->model()->contactListItemProxyCreated(this); +} + +ContactListItemProxy::~ContactListItemProxy() +{ +} diff --git a/src/contactlistitemproxy.h b/src/contactlistitemproxy.h new file mode 100644 index 000000000..e6f9d2747 --- /dev/null +++ b/src/contactlistitemproxy.h @@ -0,0 +1,44 @@ +/* + * contactlistitemproxy.h - proxy item contact list item class + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTITEMPROXY_H +#define CONTACTLISTITEMPROXY_H + +#include +#include + +class ContactListItem; +class ContactListGroup; + +class ContactListItemProxy : public QObject +{ +public: + ContactListItemProxy(ContactListGroup* parent, ContactListItem* item); + ~ContactListItemProxy(); + + ContactListItem* item() const { return item_; } + ContactListGroup* parent() const { return parent_; } + +private: + QPointer item_; + QPointer parent_; +}; + +#endif diff --git a/src/contactlistmodel.cpp b/src/contactlistmodel.cpp new file mode 100755 index 000000000..db4a7f58d --- /dev/null +++ b/src/contactlistmodel.cpp @@ -0,0 +1,878 @@ +/* + * contactlistmodel.cpp - model of contact list + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistmodel.h" + +#include +#include +#include +#include + +#include "psiaccount.h" +#include "psicontact.h" +#include "psicontactlist.h" +#include "contactlistitem.h" +#include "contactlistgroup.h" +#include "contactlistnestedgroup.h" +#include "contactlistaccountgroup.h" +#include "contactlistitemproxy.h" +#include "contactlistgroupstate.h" +#include "contactlistgroupcache.h" +#include "contactlistmodelupdater.h" +#include "contactlistspecialgroup.h" +#include "userlist.h" +#ifdef YAPSI +#include "yacommon.h" +#endif + +ContactListModel::ContactListModel(PsiContactList* contactList) + : QAbstractItemModel(contactList) + , groupsEnabled_(false) + , accountsEnabled_(false) + , contactList_(contactList) + , updater_(0) + , rootGroup_(0) + , bulkUpdateCount_(0) + , emitDeltaSignals_(true) + , groupState_(0) + , groupCache_(0) +{ + groupState_ = new ContactListGroupState(this); + updater_ = new ContactListModelUpdater(contactList, this); + groupCache_ = new ContactListGroupCache(this); + + connect(groupState_, SIGNAL(orderChanged()), SLOT(orderChanged())); + connect(updater_, SIGNAL(addedContact(PsiContact*)), SLOT(addContact(PsiContact*))); + connect(updater_, SIGNAL(removedContact(PsiContact*)), SLOT(removeContact(PsiContact*))); + connect(updater_, SIGNAL(contactUpdated(PsiContact*)), SLOT(contactUpdated(PsiContact*))); + connect(updater_, SIGNAL(contactGroupsChanged(PsiContact*)), SLOT(contactGroupsChanged(PsiContact*))); + connect(updater_, SIGNAL(beginBulkContactUpdate()), SLOT(beginBulkUpdate())); + connect(updater_, SIGNAL(endBulkContactUpdate()), SLOT(endBulkUpdate())); + connect(contactList_, SIGNAL(destroying()), SLOT(destroyingContactList())); + connect(contactList_, SIGNAL(showOfflineChanged(bool)), SIGNAL(showOfflineChanged())); + connect(contactList_, SIGNAL(showSelfChanged(bool)), SIGNAL(showSelfChanged())); + connect(contactList_, SIGNAL(showAgentsChanged(bool)), SIGNAL(showTransportsChanged())); + connect(contactList_, SIGNAL(rosterRequestFinished()), SLOT(rosterRequestFinished())); +} + +ContactListModel::~ContactListModel() +{ + emitDeltaSignals_ = false; + delete rootGroup_; +} + +void ContactListModel::storeGroupState(const QString& id) +{ + groupState_->load(id); +} + +void ContactListModel::updaterCommit() +{ + if (!updater()) { + Q_ASSERT(false); + return; + } + updater()->commit(); +} + +void ContactListModel::destroyingContactList() +{ + if (updater_) { + updater_->commit(); + delete updater_; + updater_ = 0; + } + + groupState_->updateGroupList(this); + groupState_->save(); + + contactList_ = 0; + invalidateLayout(); +} + +ContactListGroup* ContactListModel::createRootGroup() +{ + if (accountsEnabled_) + return new ContactListAccountGroup(this, 0, 0); + + if (!groupsEnabled_) + return new ContactListGroup(this, 0); + + return new ContactListNestedGroup(this, 0, QString()); +} + +bool ContactListModel::groupsEnabled() const +{ + return groupsEnabled_; +} + +void ContactListModel::setGroupsEnabled(bool enabled) +{ + if (groupsEnabled_ != enabled) { + groupsEnabled_ = enabled; + invalidateLayout(); + } +} + +bool ContactListModel::accountsEnabled() const +{ + return accountsEnabled_; +} + +void ContactListModel::setAccountsEnabled(bool enabled) +{ + if (accountsEnabled_ != enabled) { + accountsEnabled_ = enabled; + invalidateLayout(); + } +} + +void ContactListModel::beginBulkUpdate() +{ + Q_ASSERT(bulkUpdateCount_ >= 0); + if (!bulkUpdateCount_) { + emitDeltaSignals_ = false; + doResetAfterBulkUpdate_ = false; + doLayoutUpdateAfterBulkUpdate_ = false; + + // blockSignals(true); + emit layoutAboutToBeChanged(); + } + + ++bulkUpdateCount_; +} + +void ContactListModel::endBulkUpdate() +{ + --bulkUpdateCount_; + Q_ASSERT(bulkUpdateCount_ >= 0); + + if (!bulkUpdateCount_) { + // blockSignals(false); + emitDeltaSignals_ = true; + if (doResetAfterBulkUpdate_) { + reset(); + + // in Qt 4.3.4 emitting modelReset() leads to QSortFilterProxyModel + // calling invalidate() on itself first, then immediately it will emit + // modelReset() too. + // + // It's very easy to reproduce on yapsi r2193 and Qt 4.3.4: enable groups in roster, + // then disable them. And all you could see now is blank contacts roster. + emit layoutAboutToBeChanged(); + emit layoutChanged(); + } + else if (doLayoutUpdateAfterBulkUpdate_) { + // emit layoutAboutToBeChanged(); + emit layoutChanged(); + } + } +} + +void ContactListModel::rosterRequestFinished() +{ + if (rowCount(QModelIndex()) == 0) { + reset(); + + emit layoutAboutToBeChanged(); + emit layoutChanged(); + } +} + +void ContactListModel::orderChanged() +{ + emit layoutAboutToBeChanged(); + emit layoutChanged(); +} + +void ContactListModel::invalidateLayout() +{ + beginBulkUpdate(); + + if (rootGroup_) + delete rootGroup_; + + if (updater()) { + updater()->clear(); + } + + rootGroup_ = createRootGroup(); + + initializeModel(); + + endBulkUpdate(); +} + +void ContactListModel::initializeModel() +{ + if (contactList_ && updater()) { + foreach(PsiContact* contact, contactList_->contacts()) { + updater()->addContact(contact); + } + } + + foreach(PsiContact* contact, additionalContacts()) { + addContact(contact); + } +} + +QStringList ContactListModel::filterContactGroups(QStringList groups) const +{ + return groups; +} + +void ContactListModel::addContact(PsiContact* contact) +{ + Q_ASSERT(contact); + if (!accountsEnabled() && contact->isSelf()) + return; + + addContact(contact, contact->groups()); +} + +void ContactListModel::removeContact(PsiContact* contact) +{ + Q_ASSERT(contact); + if (!accountsEnabled() && contact->isSelf()) + return; + + contactGroupsChanged(contact, QStringList()); +} + +void ContactListModel::contactGroupsChanged(PsiContact* contact) +{ + Q_ASSERT(contact); + if (!accountsEnabled() && contact->isSelf()) + return; + + Q_ASSERT(rootGroup_); + contactGroupsChanged(contact, contact->groups()); +} + +void ContactListModel::addContact(PsiContact* contact, QStringList contactGroups) +{ + Q_ASSERT(rootGroup_); + rootGroup_->addContact(contact, rootGroup_->sanitizeGroupNames(filterContactGroups(contactGroups))); +} + +PsiContact* ContactListModel::contactFor(const QModelIndex& index) const +{ + if (indexType(index) != ContactType) + return 0; + + ContactListItemProxy* proxy = static_cast(index.internalPointer()); + Q_ASSERT(proxy); + PsiContact* result = dynamic_cast(proxy->item()); + Q_ASSERT(result); + return result; +} + +QModelIndexList ContactListModel::indexesFor(PsiContact* contact) const +{ + Q_ASSERT(contact); + QModelIndexList result; + foreach(ContactListGroup* group, groupCache()->groupsFor(contact)) { + result += index(group->indexOf(contact), 0, group->toModelIndex()); + } + return result; +} + +void ContactListModel::contactUpdated(PsiContact* contact) +{ + Q_ASSERT(rootGroup_); + // rootGroup_->contactUpdated(contact); + foreach(ContactListGroup* group, groupCache()->groupsFor(contact)) { + group->contactUpdated(contact); + } +} + +void ContactListModel::contactGroupsChanged(PsiContact* contact, QStringList contactGroups) +{ + Q_ASSERT(rootGroup_); + rootGroup_->contactGroupsChanged(contact, rootGroup_->sanitizeGroupNames(filterContactGroups(contactGroups))); +} + +void ContactListModel::itemAboutToBeInserted(ContactListGroup* group, int index) +{ + doResetAfterBulkUpdate_ = true; + + if (!emitDeltaSignals_) + return; + + beginInsertRows(group->toModelIndex(), index, index); +} + +void ContactListModel::insertedItem(ContactListGroup* group, int index) +{ + Q_ASSERT(doResetAfterBulkUpdate_); + Q_UNUSED(group); + Q_UNUSED(index); + + if (!emitDeltaSignals_) + return; + + endInsertRows(); +} + +void ContactListModel::itemAboutToBeRemoved(ContactListGroup* group, int index) +{ + doResetAfterBulkUpdate_ = true; + + if (!emitDeltaSignals_) + return; + + beginRemoveRows(group->toModelIndex(), index, index); +} + +void ContactListModel::removedItem(ContactListGroup* group, int index) +{ + Q_ASSERT(doResetAfterBulkUpdate_); + Q_UNUSED(group); + Q_UNUSED(index); + + if (!emitDeltaSignals_) + return; + + endRemoveRows(); +} + +void ContactListModel::updatedItem(ContactListItemProxy* item) +{ + doLayoutUpdateAfterBulkUpdate_ = true; + + if (!emitDeltaSignals_) + return; + + QModelIndex index = itemProxyToModelIndex(item); + emit dataChanged(index, index); +} + +void ContactListModel::updatedGroupVisibility(ContactListGroup* group) +{ + doLayoutUpdateAfterBulkUpdate_ = true; + + if (!emitDeltaSignals_) + return; + + Q_UNUSED(group); + // FIXME: Ideally this should work, but in current Qt it doesn't + // updatedItem(group->parent()->findGroup(group)); + emit layoutAboutToBeChanged(); + emit layoutChanged(); +} + +QVariant ContactListModel::contactListItemData(const ContactListItem* item, int role) const +{ + Q_ASSERT(item); + if (role == TypeRole) { + return QVariant(item->type()); + } + else if ((role == Qt::DisplayRole || role == Qt::EditRole) /*&& index.column() == NameColumn*/) { + return QVariant(item->displayName()); + } + else if (role == ExpandedRole) { + return QVariant(item->expanded()); + } + + return QVariant(); +} + +QVariant ContactListModel::contactGroupData(const ContactListGroup* group, int role) const +{ + Q_ASSERT(group); + + if (role == TotalItemsRole) { + return QVariant(group->itemsCount()); + } + else if (role == FullGroupNameRole) { + return QVariant(group->fullName()); + } + else if (role == OnlineContactsRole) { + return QVariant(group->onlineContactsCount()); + } + else if (role == TotalContactsRole) { + return QVariant(group->totalContactsCount()); + } + else if (role == InternalGroupNameRole) { + return QVariant(group->internalGroupName()); + } + else if (role == SpecialGroupTypeRole) { + return QVariant(group->specialGroupType()); + } + + return contactListItemData(group, role); +} + +QVariant ContactListModel::accountData(const ContactListAccountGroup* account, int role) const +{ + Q_ASSERT(account); + if (!account->account()) { + return QVariant(); + } + + if (role == JidRole) { + return QVariant(account->account()->jid().full()); + } + // else if (role == PictureRole) { + // return QVariant(account->account()->picture()); + // } + else if (role == StatusTextRole) { + return QVariant(account->account()->status().status().simplified()); + } + else if (role == StatusTypeRole) { + return QVariant(account->account()->status().type()); + } + else if (role == OnlineContactsRole) { + // FIXME: cache this somewhere + return QVariant(0); + } + else if (role == TotalContactsRole) { + return QVariant(account->account()->contactList().count()); + } + else if (role == UsingSSLRole) { + return QVariant(account->account()->usingSSL()); + } + else if (role == IsAlertingRole) { + return QVariant(account->account()->alerting()); + } + else if (role == AlertPictureRole) { + return QVariant(account->account()->alertPicture()); + } + + return contactGroupData(account, role); +} + +QVariant ContactListModel::contactData(const PsiContact* contact, int role) const +{ + Q_ASSERT(contact); + + if (role == JidRole) { + return QVariant(contact->jid().full()); + } + else if (role == PictureRole) { + return QVariant(contact->picture()); + } + else if (role == StatusTextRole) { + return QVariant(contact->status().status().simplified()); + } + else if (role == StatusTypeRole) { + return QVariant(contact->status().type()); + } + else if (role == PresenceErrorRole) { + return QVariant(contact->userListItem().presenceError()); + } + else if (role == IsAgentRole) { + return QVariant(contact->isAgent()); + } + else if (role == AuthorizesToSeeStatusRole) { + return QVariant(contact->authorizesToSeeStatus()); + } + else if (role == AskingForAuthRole) { + return QVariant(contact->askingForAuth()); + } + else if (role == IsAlertingRole) { + return QVariant(contact->alerting()); + } + else if (role == AlertPictureRole) { + return QVariant(contact->alertPicture()); + } +#ifdef YAPSI + else if (role == Qt::ForegroundRole) { + return QVariant(Ya::statusColor(contact->status().type())); + } + else if (role == GenderRole) { + return QVariant(contact->gender()); + } + else if (role == Qt::DisplayRole || role == Qt::EditRole) { + return QVariant(Ya::contactName( + contactListItemData(contact, role).toString(), + contactData(contact, ContactListModel::JidRole).toString() + )); + } +#endif + + return contactListItemData(contact, role); +} + +QVariant ContactListModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) + return QVariant(); + + ContactListItemProxy* item = itemProxy(index); + Q_ASSERT(item); + Q_ASSERT(item->item()); + return itemData(item->item(), role); +} + +QVariant ContactListModel::itemData(const ContactListItem* item, int role) const +{ + Q_ASSERT(item); + if (const PsiContact* contact = dynamic_cast(item)) { + return contactData(contact, role); + } + else if (const ContactListAccountGroup* accountGroup = dynamic_cast(item)) { + return accountData(accountGroup, role); + } + else if (const ContactListGroup* group = dynamic_cast(item)) { + return contactGroupData(group, role); + } + + return contactListItemData(item, role); +} + +bool ContactListModel::setData(const QModelIndex& index, const QVariant& data, int role) +{ + if (!index.isValid()) + return false; + + ContactListItemProxy* item = itemProxy(index); + if (!item) + return false; + ContactListGroup* group = 0; + PsiContact* contact = 0; + + if (role == ActivateRole) { + PsiContact* contact = dynamic_cast(item->item()); + if (!contact) + return false; + + contact->activate(); + return true; + } + else if (role == Qt::EditRole) { + QString name = data.toString(); + if ((contact = dynamic_cast(item->item()))) { + if (name.isEmpty()) { + // QMessageBox::information(0, tr("Error"), tr("You can't set a blank name.")); + return false; + } + else { + contact->setName(name); + } + } + else if ((group = dynamic_cast(item->item()))) { + // if (name.isEmpty()) { + // QMessageBox::information(0, tr("Error"), tr("You can't set a blank group name.")); + // return false; + // } + // else { + // // make sure we don't have it already + // if (group->account()->groupList().contains(name)) { + // QMessageBox::information(0, tr("Error"), tr("You already have a group with that name.")); + // return false; + // } + group->setName(name); + // } + } + emit dataChanged(index, index); + return true; + } + else if (role == ExpandedRole) { + if ((group = dynamic_cast(item->item()))) { + group->setExpanded(data.toBool()); + } + } + + return true; +} + +QVariant ContactListModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + Q_UNUSED(section); + Q_UNUSED(orientation); + Q_UNUSED(role); + return QVariant(); +} + +/** + * Returns the model item index for the item in the \param parent + * with the given \param row and \param column. + */ +QModelIndex ContactListModel::index(int row, int column, const QModelIndex &parent) const +{ + if (column < 0 || column >= 1 || row < 0 || parent.column() > 0) + return QModelIndex(); + + ContactListGroup* group = 0; + if (!parent.isValid()) { + group = rootGroup_; + } + else { + ContactListItemProxy* item = itemProxy(parent); + group = dynamic_cast(item->item()); + } + + if (group && row < group->itemsCount()) + return itemProxyToModelIndex(group->item(row), row); + + return QModelIndex(); +} + +/** + * Return the parent of the given \param index model item. + */ +QModelIndex ContactListModel::parent(const QModelIndex &index) const +{ + if (!index.isValid()) + return QModelIndex(); + + ContactListItemProxy* item = itemProxy(index); + if (item && item->parent()) + return item->parent()->toModelIndex(); + + return QModelIndex(); +} + +/** + * Returns the number of rows in the \param parent model index. + */ +int ContactListModel::rowCount(const QModelIndex &parent) const +{ + if (parent.column() > 0) + return 0; + + ContactListGroup* group = 0; + if (parent.isValid()) { + ContactListItemProxy* item = itemProxy(parent); + group = dynamic_cast(item->item()); + } + else { + group = rootGroup_; + } + + return group ? group->itemsCount() : 0; +} + +/** + * Returns the number of columns in the \param parent model item. + */ +int ContactListModel::columnCount(const QModelIndex& parent) const +{ + if (parent.column() > 0) + return 0; + return 1; +} + +/** + * Returns the item flags for the given \param index in the model. + */ +Qt::ItemFlags ContactListModel::flags(const QModelIndex& index) const +{ + if (!index.isValid()) + return Qt::ItemIsDropEnabled; + + Qt::ItemFlags f = QAbstractItemModel::flags(index); + f |= Qt::ItemIsSelectable | Qt::ItemIsEnabled; + + ContactListItemProxy* item = itemProxy(index); + if ((index.column() == NameColumn) && item && item->item()->isEditable()) + f |= Qt::ItemIsEditable; + return f; +} + +/** + * Returns true if the \param parent model item has children; otherwise + * returns false. + */ +bool ContactListModel::hasChildren(const QModelIndex& parent) +{ + return rowCount(parent) > 0; +} + +/** + * Call this slot to notify \param index that it's now displayed in the 'expanded' state. + */ +void ContactListModel::expanded(const QModelIndex& index) +{ + setData(index, QVariant(true), ExpandedRole); +} + +/** +* Call this slot to notify \param index that it's now displayed in the 'collapsed' state. + */ +void ContactListModel::collapsed(const QModelIndex& index) +{ + setData(index, QVariant(false), ExpandedRole); +} + +PsiContactList* ContactListModel::contactList() const +{ + return contactList_; +} + +ContactListItemProxy* ContactListModel::modelIndexToItemProxy(const QModelIndex& index) const +{ + return itemProxy(index); +} + +QModelIndex ContactListModel::itemProxyToModelIndex(ContactListItemProxy* item) const +{ + Q_ASSERT(item); + int row = item->parent() ? item->parent()->indexOf(item->item()) : 0; + return itemProxyToModelIndex(item, row); +} + +QModelIndex ContactListModel::itemProxyToModelIndex(ContactListItemProxy* item, int index) const +{ + Q_ASSERT(item); + return createIndex(index, 0, item); +} + +void ContactListModel::renameSelectedItem() +{ + emit inPlaceRename(); +} + +ContactListModel::Type ContactListModel::indexType(const QModelIndex& index) +{ + QVariant type = index.data(ContactListModel::TypeRole); + if (type.isValid()) + return static_cast(type.toInt()); + + return ContactListModel::InvalidType; +} + +ContactListItemProxy* ContactListModel::itemProxy(const QModelIndex& index) const +{ + if ((index.row() < 0) || (index.column() < 0) || (index.model() != this)) + return 0; + ContactListItemProxy* proxy = static_cast(index.internalPointer()); +#if 0 + if (contactListItemProxyHash_.contains(proxy) && !contactListItemProxyHash_[proxy].isNull()) + return proxy; + return 0; +#else + return proxy; +#endif +} + +PsiAccount* ContactListModel::account(const QModelIndex& index) const +{ + ContactListItemProxy* item = itemProxy(index); + if (item) { + PsiContact* contact = dynamic_cast(item->item()); + if (contact) + return contact->account(); + } + return 0; +} + +ContactListGroupState* ContactListModel::groupState() const +{ + return groupState_; +} + +ContactListModelUpdater* ContactListModel::updater() const +{ + return updater_; +} + +ContactListGroupCache* ContactListModel::groupCache() const +{ + return groupCache_; +} + +ContactListGroup* ContactListModel::rootGroup() const +{ + return rootGroup_; +} + +QList ContactListModel::additionalContacts() const +{ + return QList(); +} + +int ContactListModel::groupOrder(const QString& groupFullName) const +{ + ContactListGroup* group = groupCache()->findGroup(groupFullName); + if (group) { + return groupState()->groupOrder(group); + } + return 0; +} + +void ContactListModel::setGroupOrder(const QString& groupFullName, int order) +{ + ContactListGroup* group = groupCache()->findGroup(groupFullName); + if (group) { + groupState()->setGroupOrder(group, order); + } +} + +bool ContactListModel::showOffline() const +{ + if (!contactList_) + return false; + return contactList_->showOffline(); +} + +bool ContactListModel::showSelf() const +{ + if (!contactList_) + return false; + return contactList_->showSelf(); +} + +bool ContactListModel::showTransports() const +{ + if (!contactList_) + return false; + return contactList_->showAgents(); +} + +bool ContactListModel::updatesEnabled() const +{ + return updater()->updatesEnabled(); +} + +void ContactListModel::setUpdatesEnabled(bool updatesEnabled) +{ + updater()->setUpdatesEnabled(updatesEnabled); +} + +bool ContactListModel::hasContacts(bool onlineOnly) const +{ + return groupCache()->hasContacts(onlineOnly); +} + +QModelIndex ContactListModel::groupToIndex(ContactListGroup* group) const +{ + if (group && group->parent()) { + int groupIndex = group->parent()->indexOf(group); + if (groupIndex >= 0) { + ContactListItemProxy* itemProxy = group->parent()->item(groupIndex); + if (itemProxy) { + QModelIndex index = itemProxyToModelIndex(itemProxy); + return index; + } + } + } + + return QModelIndex(); +} + +void ContactListModel::contactListItemProxyCreated(ContactListItemProxy* proxy) +{ + Q_UNUSED(proxy); +#if 0 + contactListItemProxyHash_[proxy] = proxy; +#endif +} diff --git a/src/contactlistmodel.h b/src/contactlistmodel.h new file mode 100644 index 000000000..1eb21a196 --- /dev/null +++ b/src/contactlistmodel.h @@ -0,0 +1,216 @@ +/* + * contactlistmodel.h - model of contact list + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTMODEL_H +#define CONTACTLISTMODEL_H + +#include +#include +#include +#include +#include + +class PsiAccount; +class PsiContact; +class PsiContactList; +class ContactListItem; +class ContactListGroup; +class ContactListAccountGroup; +class ContactListItemProxy; +class ContactListGroupState; +class ContactListGroupCache; +class ContactListModelUpdater; + +class ContactListModel : public QAbstractItemModel +{ + Q_OBJECT + +public: + enum Type { + InvalidType = 0, + ContactType = 1, + GroupType = 2, + AccountType = 3 + }; + + static ContactListModel::Type indexType(const QModelIndex& index); + + enum { + // generic + TypeRole = Qt::UserRole + 0, + ActivateRole = Qt::UserRole + 1, + + // contacts + JidRole = Qt::UserRole + 2, + PictureRole = Qt::UserRole + 3, + StatusTextRole = Qt::UserRole + 4, + StatusTypeRole = Qt::UserRole + 5, + PresenceErrorRole = Qt::UserRole + 6, + IsAgentRole = Qt::UserRole + 7, + AuthorizesToSeeStatusRole = Qt::UserRole + 8, + AskingForAuthRole = Qt::UserRole + 9, + IsAlertingRole = Qt::UserRole + 10, + AlertPictureRole = Qt::UserRole + 11, + + // groups + ExpandedRole = Qt::UserRole + 12, + TotalItemsRole = Qt::UserRole + 13, + FullGroupNameRole = Qt::UserRole + 14, + OnlineContactsRole = Qt::UserRole + 15, + TotalContactsRole = Qt::UserRole + 16, + InternalGroupNameRole = Qt::UserRole + 17, + SpecialGroupTypeRole = Qt::UserRole + 18, + + // accounts + UsingSSLRole = Qt::UserRole + 19, + +#ifdef YAPSI + GenderRole = Qt::UserRole + 20, +#endif + }; + + enum { + NameColumn = 0 + }; + + ContactListModel(PsiContactList* contactList); + virtual ~ContactListModel(); + + virtual PsiContactList* contactList() const; + + void invalidateLayout(); + ContactListItemProxy* modelIndexToItemProxy(const QModelIndex& index) const; + QModelIndex itemProxyToModelIndex(ContactListItemProxy* item) const; + QModelIndex itemProxyToModelIndex(ContactListItemProxy* item, int index) const; + QModelIndex groupToIndex(ContactListGroup* group) const; + + virtual ContactListModel* clone() const = 0; + void contactListItemProxyCreated(ContactListItemProxy* proxy); + + bool groupsEnabled() const; + void setGroupsEnabled(bool enabled); + void storeGroupState(const QString& id); + + bool accountsEnabled() const; + void setAccountsEnabled(bool enabled); + + bool showOffline() const; + bool showSelf() const; + bool showTransports() const; + bool hasContacts(bool onlineOnly) const; + + int groupOrder(const QString& groupFullName) const; + void setGroupOrder(const QString& groupFullName, int order); + + void renameSelectedItem(); + + PsiAccount* account(const QModelIndex& index) const; + ContactListGroupState* groupState() const; + ContactListGroupCache* groupCache() const; + void updaterCommit(); + + bool updatesEnabled() const; + void setUpdatesEnabled(bool updatesEnabled); + + virtual void renameGroup(ContactListGroup* group, const QString& newName) = 0; + + PsiContact* contactFor(const QModelIndex& index) const; + QModelIndexList indexesFor(PsiContact* contact) const; + + virtual QVariant contactListItemData(const ContactListItem* item, int role) const; + virtual QVariant contactData(const PsiContact* contact, int role) const; + virtual QVariant contactGroupData(const ContactListGroup* group, int role) const; + virtual QVariant accountData(const ContactListAccountGroup* account, int role) const; + + // reimplemented + QVariant data(const QModelIndex& index, int role) const; + QVariant itemData(const ContactListItem* item, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + virtual QModelIndex index(int row, int column, const QModelIndex& parent) const; + virtual QModelIndex parent(const QModelIndex& index) const; + virtual int rowCount(const QModelIndex& parent) const; + virtual int columnCount(const QModelIndex& parent) const; + Qt::ItemFlags flags(const QModelIndex& index) const; + virtual bool setData(const QModelIndex&, const QVariant&, int role); + virtual bool hasChildren(const QModelIndex& index); + +public: + void itemAboutToBeInserted(ContactListGroup* group, int index); + void insertedItem(ContactListGroup* group, int index); + void itemAboutToBeRemoved(ContactListGroup* group, int index); + void removedItem(ContactListGroup* group, int index); + void updatedItem(ContactListItemProxy* item); + void updatedGroupVisibility(ContactListGroup* group); + +signals: + void showOfflineChanged(); + void showSelfChanged(); + void showTransportsChanged(); + void inPlaceRename(); + +public slots: + void expanded(const QModelIndex&); + void collapsed(const QModelIndex&); + +protected slots: + void addContact(PsiContact*); + void removeContact(PsiContact*); + + void contactUpdated(PsiContact*); + void contactGroupsChanged(PsiContact*); + + void destroyingContactList(); + void orderChanged(); + +protected slots: + void beginBulkUpdate(); + void endBulkUpdate(); + void rosterRequestFinished(); + +protected: + virtual QList additionalContacts() const; + ContactListModelUpdater* updater() const; + ContactListGroup* rootGroup() const; + +private: + bool groupsEnabled_; + bool accountsEnabled_; + PsiContactList* contactList_; + ContactListModelUpdater* updater_; + ContactListGroup* rootGroup_; + int bulkUpdateCount_; + bool doResetAfterBulkUpdate_; + bool doLayoutUpdateAfterBulkUpdate_; + bool emitDeltaSignals_; + ContactListGroupState* groupState_; + ContactListGroupCache* groupCache_; + QHash > contactListItemProxyHash_; + +protected: + virtual QStringList filterContactGroups(QStringList groups) const; + virtual ContactListGroup* createRootGroup(); + virtual void initializeModel(); + ContactListItemProxy* itemProxy(const QModelIndex& index) const; + + void addContact(PsiContact* contact, QStringList contactGroups); + void contactGroupsChanged(PsiContact* contact, QStringList contactGroups); +}; + +#endif diff --git a/src/contactlistmodelselection.cpp b/src/contactlistmodelselection.cpp new file mode 100644 index 000000000..290442dfd --- /dev/null +++ b/src/contactlistmodelselection.cpp @@ -0,0 +1,232 @@ +/* + * contactlistmodelselection.cpp - stores persistent contact list selections + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistmodelselection.h" + +#include +#include + +#include "xmpp_xmlcommon.h" +#include "psiaccount.h" +#include "psicontact.h" +#include "contactlistnestedgroup.h" +#include "contactlistaccountgroup.h" +#include "contactlistitemproxy.h" +#include "textutil.h" + +static const QString psiRosterSelectionMimeType = "application/psi-roster-selection"; + +ContactListModelSelection::ContactListModelSelection(QList items) + : QMimeData() + , mimeData_(0) +{ + QDomDocument doc; + QDomElement root = doc.createElement("items"); + root.setAttribute("version", "2.0"); + doc.appendChild(root); + + // TODO: maybe also embed a random instance-specific token to + // prevent drag'n'drop with other running Psi instances? + + QStringList jids; + + foreach(ContactListItemProxy* itemProxy, items) { + Q_ASSERT(itemProxy); + + PsiContact* contact = 0; + ContactListNestedGroup* group = 0; + ContactListAccountGroup* account = 0; + if ((contact = dynamic_cast(itemProxy->item()))) { + QDomElement tag = textTag(&doc, "contact", contact->jid().full()); + tag.setAttribute("account", contact->account()->id()); + tag.setAttribute("group", itemProxy->parent() ? itemProxy->parent()->fullName() : ""); + root.appendChild(tag); + + jids << contact->jid().full(); + } + else if ((account = dynamic_cast(itemProxy->item()))) { + QDomElement tag = doc.createElement("account"); + tag.setAttribute("id", account->account()->id()); + root.appendChild(tag); + + jids << account->displayName(); + } + else if ((group = dynamic_cast(itemProxy->item()))) { + // if group->fullName() consists only of whitespace when we'll try + // to read it back we'll get an empty string, so we're using CDATA + // QDomElement tag = textTag(&doc, "group", group->fullName()); + QDomElement tag = doc.createElement("group"); + QDomText text = doc.createCDATASection(TextUtil::escape(group->fullName())); + tag.appendChild(text); + + root.appendChild(tag); + + jids << group->fullName(); + } + else { + qWarning("ContactListModelSelection::ContactListModelSelection(): Unable to serialize %d, unsupported type", itemProxy->item()->type()); + } + } + + setText(jids.join(";")); + setData(psiRosterSelectionMimeType, doc.toByteArray()); +} + +ContactListModelSelection::ContactListModelSelection(const QMimeData* mimeData) + : QMimeData() + , mimeData_(mimeData) +{ + const ContactListModelSelection* other = dynamic_cast(mimeData_); + if (other) { + mimeData_ = other->mimeData(); + } +} + +const QString& ContactListModelSelection::mimeType() +{ + return psiRosterSelectionMimeType; +} + +QDomElement ContactListModelSelection::rootElementFor(const QMimeData* mimeData) const +{ + QDomDocument doc; + if (!doc.setContent(mimeData->data(psiRosterSelectionMimeType))) + return QDomElement(); + + QDomElement root = doc.documentElement(); + if (root.tagName() != "items" || root.attribute("version") != "2.0") + return QDomElement(); + + return root; +} + +bool ContactListModelSelection::haveRosterSelectionIn(const QMimeData* mimeData) const +{ + return !rootElementFor(mimeData).isNull(); +} + +QList ContactListModelSelection::contactsFor(const QMimeData* mimeData) const +{ + QList result; + QDomElement root = rootElementFor(mimeData); + if (root.isNull()) + return result; + + for (QDomNode n = root.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if (e.isNull()) + continue; + + if (e.tagName() == "contact") { + Jid jid = tagContent(e); + result << Contact(jid.full(), + e.attribute("account"), + e.attribute("group")); + } + } + + return result; +} + +QList ContactListModelSelection::groupsFor(const QMimeData* mimeData) const +{ + QList result; + QDomElement root = rootElementFor(mimeData); + if (root.isNull()) + return result; + + for (QDomNode n = root.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if (e.isNull()) + continue; + + if (e.tagName() == "group") { + QString groupName = TextUtil::unescape(tagContent(e)); + result << Group(groupName); + } + } + + return result; +} + +QList ContactListModelSelection::accountsFor(const QMimeData* mimeData) const +{ + QList result; + QDomElement root = rootElementFor(mimeData); + if (root.isNull()) + return result; + + for (QDomNode n = root.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement e = n.toElement(); + if (e.isNull()) + continue; + + if (e.tagName() == "account") { + result << Account(e.attribute("id")); + } + } + + return result; +} + +const QMimeData* ContactListModelSelection::mimeData() const +{ + return mimeData_ ? mimeData_ : this; +} + +bool ContactListModelSelection::haveRosterSelection() const +{ + return haveRosterSelectionIn(mimeData()); +} + +QList ContactListModelSelection::contacts() const +{ + return contactsFor(mimeData()); +} + +QList ContactListModelSelection::groups() const +{ + return groupsFor(mimeData()); +} + +QList ContactListModelSelection::accounts() const +{ + return accountsFor(mimeData()); +} + +bool ContactListModelSelection::isMultiSelection() const +{ + return (contacts().count() + groups().count()) > 1; +} + +void ContactListModelSelection::debugSelection(const QMimeData* data, const QString& name) +{ + qWarning("*** debugSelection %s", qPrintable(name)); + ContactListModelSelection selection(data); + foreach(const ContactListModelSelection::Contact& c, selection.contacts()) { + qWarning("\tc: '%s' group: '%s' account: '%s'", qPrintable(c.jid), qPrintable(c.group), qPrintable(c.account)); + } + foreach(const ContactListModelSelection::Group& g, selection.groups()) { + qWarning("\tg: '%s'", qPrintable(g.fullName)); + } + foreach(const ContactListModelSelection::Account& a, selection.accounts()) { + qWarning("\ta: '%s'", qPrintable(a.id)); + } +} diff --git a/src/contactlistmodelselection.h b/src/contactlistmodelselection.h new file mode 100644 index 000000000..319aa8fcf --- /dev/null +++ b/src/contactlistmodelselection.h @@ -0,0 +1,85 @@ +/* + * contactlistmodelselection.h - stores persistent contact list selections + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTMODELSELECTION_H +#define CONTACTLISTMODELSELECTION_H + +#include + +class ContactListItemProxy; +class QDomElement; + +class ContactListModelSelection : public QMimeData +{ + Q_OBJECT +public: + ContactListModelSelection(QList items); + ContactListModelSelection(const QMimeData* mimeData); + + static const QString& mimeType(); + + struct Contact { + Contact(QString _jid, QString _account, QString _group) + : jid(_jid) + , account(_account) + , group(_group) + {} + QString jid; + QString account; + QString group; + }; + + struct Group { + Group(QString _fullName) + : fullName(_fullName) + {} + QString fullName; + }; + + struct Account { + Account(QString _id) + : id(_id) + {} + QString id; + }; + + bool haveRosterSelection() const; + + QList contacts() const; + QList groups() const; + QList accounts() const; + + bool isMultiSelection() const; + + static void debugSelection(const QMimeData* data, const QString& name); + +private: + const QMimeData* mimeData_; + + const QMimeData* mimeData() const; + + QDomElement rootElementFor(const QMimeData* mimeData) const; + bool haveRosterSelectionIn(const QMimeData* mimeData) const; + QList contactsFor(const QMimeData* mimeData) const; + QList groupsFor(const QMimeData* mimeData) const; + QList accountsFor(const QMimeData* mimeData) const; +}; + +#endif diff --git a/src/contactlistmodelupdater.cpp b/src/contactlistmodelupdater.cpp new file mode 100644 index 000000000..63572a17d --- /dev/null +++ b/src/contactlistmodelupdater.cpp @@ -0,0 +1,198 @@ +/* + * contactlistmodelupdater.cpp - class to group model update operations together + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistmodelupdater.h" + +#include + +#include "psicontactlist.h" +#include "psicontact.h" + +static const int MAX_COMMIT_DELAY = 30; // in seconds + +ContactListModelUpdater::ContactListModelUpdater(PsiContactList* contactList, QObject *parent) + : QObject(parent) + , updatesEnabled_(true) + , contactList_(contactList) + , commitTimer_(0) +{ + commitTimer_ = new QTimer(this); + connect(commitTimer_, SIGNAL(timeout()), SLOT(commit())); + commitTimer_->setSingleShot(true); + commitTimer_->setInterval(100); + + connect(contactList_, SIGNAL(addedContact(PsiContact*)), SLOT(addContact(PsiContact*))); + connect(contactList_, SIGNAL(removedContact(PsiContact*)), SLOT(removeContact(PsiContact*))); + connect(contactList_, SIGNAL(beginBulkContactUpdate()), SLOT(beginBulkUpdate())); + connect(contactList_, SIGNAL(endBulkContactUpdate()), SLOT(endBulkUpdate())); +} + +ContactListModelUpdater::~ContactListModelUpdater() +{ +} + +void ContactListModelUpdater::clear() +{ + QHashIterator it(monitoredContacts_); + while (it.hasNext()) { + it.next(); + disconnect(it.key(), 0, this, 0); + } + monitoredContacts_.clear(); + operationQueue_.clear(); +} + +void ContactListModelUpdater::commit() +{ + if (!updatesEnabled()) + return; + +// qWarning("updater(%x):commit", (int)this); + // qWarning("*** ContactListModelUpdater::commit(). operationQueue_.count() = %d", operationQueue_.count()); + commitTimerStartTime_ = QDateTime(); + commitTimer_->stop(); + if (operationQueue_.isEmpty()) + return; + + bool doBulkUpdate = operationQueue_.count() > 1; + if (doBulkUpdate) + emit beginBulkContactUpdate(); + + QHashIterator it(operationQueue_); + while (it.hasNext()) { + it.next(); + + int operations = simplifiedOperationList(it.value()); + if (operations & AddContact) + emit addedContact(it.key()); + if (operations & RemoveContact) + Q_ASSERT(false); + if (operations & UpdateContact) + emit contactUpdated(it.key()); + if (operations & ContactGroupsChanged) + emit contactGroupsChanged(it.key()); + } + + if (doBulkUpdate) + emit endBulkContactUpdate(); + + operationQueue_.clear(); +} + +void ContactListModelUpdater::addContact(PsiContact* contact) +{ + // qWarning(">>> addContact: %s", qPrintable(contact->jid())); + Q_ASSERT(!monitoredContacts_.contains(contact)); + if (monitoredContacts_.contains(contact)) + return; + monitoredContacts_[contact] = true; +// qWarning("updater(%x):addContact: %s", (int)this, qPrintable(contact->jid().full())); + addOperation(contact, AddContact); + connect(contact, SIGNAL(destroyed(PsiContact*)), SLOT(removeContact(PsiContact*))); + connect(contact, SIGNAL(updated()), SLOT(contactUpdated())); + connect(contact, SIGNAL(groupsChanged()), SLOT(contactGroupsChanged())); +} + +/*! + * removeContact() could be called directly by PsiContactList when account is disabled, or + * by contact's destructor. We just ensure that the calls are balanced. + */ +void ContactListModelUpdater::removeContact(PsiContact* contact) +{ + // qWarning("<<< removeContact: %s", qPrintable(contact->jid())); + Q_ASSERT(monitoredContacts_.contains(contact)); + if (!monitoredContacts_.contains(contact)) + return; + monitoredContacts_.remove(contact); + disconnect(contact, 0, this, 0); + emit removedContact(contact); + operationQueue_.remove(contact); +} + +void ContactListModelUpdater::contactUpdated() +{ + PsiContact* contact = static_cast(sender()); + Q_ASSERT(monitoredContacts_.contains(contact)); + if (!monitoredContacts_.contains(contact)) + return; +// qWarning("updater(%x):contactUpdated: %s", (int)this, qPrintable(contact->jid().full())); + addOperation(contact, UpdateContact); +} + +void ContactListModelUpdater::contactGroupsChanged() +{ + PsiContact* contact = static_cast(sender()); + Q_ASSERT(monitoredContacts_.contains(contact)); + if (!monitoredContacts_.contains(contact)) + return; +// qWarning("updater(%x):contactGroupsChanged: %s", (int)this, qPrintable(contact->jid().full())); + addOperation(contact, ContactGroupsChanged); +} + +void ContactListModelUpdater::beginBulkUpdate() +{ + emit beginBulkContactUpdate(); +} + +void ContactListModelUpdater::endBulkUpdate() +{ + emit endBulkContactUpdate(); +} + +void ContactListModelUpdater::addOperation(PsiContact* contact, ContactListModelUpdater::Operation operation) +{ + if (!operationQueue_.contains(contact)) { + operationQueue_[contact] = operation; + } + else { + operationQueue_[contact] |= operation; + } + + if (commitTimerStartTime_.isNull()) + commitTimerStartTime_ = QDateTime::currentDateTime(); + + if (commitTimerStartTime_.secsTo(QDateTime::currentDateTime()) > MAX_COMMIT_DELAY) + commit(); + else + commitTimer_->start(); +} + +int ContactListModelUpdater::simplifiedOperationList(int operations) const +{ + if (operations & AddContact) + return AddContact; + + return operations; +} + +bool ContactListModelUpdater::updatesEnabled() const +{ + return updatesEnabled_; +} + +void ContactListModelUpdater::setUpdatesEnabled(bool updatesEnabled) +{ + if (updatesEnabled_ = updatesEnabled) { + updatesEnabled_ = updatesEnabled; + if (updatesEnabled_) { + commitTimer_->start(); + } + } +} diff --git a/src/contactlistmodelupdater.h b/src/contactlistmodelupdater.h new file mode 100644 index 000000000..7a024ddc7 --- /dev/null +++ b/src/contactlistmodelupdater.h @@ -0,0 +1,87 @@ +/* + * contactlistmodelupdater.h - class to group model update operations together + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTMODELUPDATER_H +#define CONTACTLISTMODELUPDATER_H + +#include +#include +#include + +class QTimer; + +class PsiContact; +class PsiContactList; + +class ContactListModelUpdater : public QObject +{ + Q_OBJECT +public: + ContactListModelUpdater(PsiContactList* contactList, QObject *parent); + ~ContactListModelUpdater(); + + bool updatesEnabled() const; + void setUpdatesEnabled(bool updatesEnabled); + +public slots: + void commit(); + void clear(); + +signals: + void addedContact(PsiContact*); + void removedContact(PsiContact*); + void contactUpdated(PsiContact*); + void contactGroupsChanged(PsiContact*); + + void beginBulkContactUpdate(); + void endBulkContactUpdate(); + +public slots: + void addContact(PsiContact*); + +private slots: + void removeContact(PsiContact*); + + void contactUpdated(); + void contactGroupsChanged(); + + void beginBulkUpdate(); + void endBulkUpdate(); + +private: + bool updatesEnabled_; + PsiContactList* contactList_; + QTimer* commitTimer_; + QDateTime commitTimerStartTime_; + QHash monitoredContacts_; + + enum Operation { + AddContact = 1 << 0, + RemoveContact = 1 << 1, + UpdateContact = 1 << 2, + ContactGroupsChanged = 1 << 3 + }; + QHash operationQueue_; + + void addOperation(PsiContact* contact, Operation operation); + int simplifiedOperationList(int operations) const; +}; + +#endif diff --git a/src/contactlistnestedgroup.cpp b/src/contactlistnestedgroup.cpp new file mode 100644 index 000000000..8eb61c298 --- /dev/null +++ b/src/contactlistnestedgroup.cpp @@ -0,0 +1,303 @@ +/* + * contactlistnestedgroup.cpp - class to handle nested contact list groups + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistnestedgroup.h" + +#include + +#include "psicontact.h" +#include "contactlistitemproxy.h" +#include "contactlistspecialgroup.h" + +ContactListNestedGroup::ContactListNestedGroup(ContactListModel* model, ContactListGroup* parent, QString name) + : ContactListGroup(model, parent) +{ + quietSetName(name); +} + +ContactListNestedGroup::~ContactListNestedGroup() +{ + clearGroup(); +} + +void ContactListNestedGroup::clearGroup() +{ + quietSetName(QString()); + foreach(ContactListGroup* group, groups_) { +// qWarning("~ContactListNextedGroup(%x): %s", this, qPrintable(group->fullName())); + removeItem(ContactListGroup::findGroup(group)); + } + qDeleteAll(groups_); + groups_.clear(); + + ContactListGroup::clearGroup(); +} + +void ContactListNestedGroup::addContact(PsiContact* contact, QStringList contactGroups) +{ + if (canContainSpecialGroups()) { + ContactListGroup* group = specialGroupFor(contact); + if (group) { + group->addContact(contact, contactGroups); + return; + } + } + + foreach(QString groupName, contactGroups) { + if (groupName == name()) { + ContactListGroup::addContact(contact, contactGroups); + } + else { + QStringList nestedGroups; +#ifdef CONTACTLIST_NESTED_GROUPS + nestedGroups = groupName.split(groupDelimiter()); +#else + nestedGroups += groupName; +#endif + if (!name().isEmpty()) { + QString firstPart = nestedGroups.takeFirst(); + Q_ASSERT(firstPart == name()); + } + + Q_ASSERT(!nestedGroups.isEmpty()); + + ContactListGroup* group = findGroup(nestedGroups.first()); + if (!group) { + group = new ContactListNestedGroup(model(), this, nestedGroups.first()); + addGroup(group); +// qWarning("ContactListNextedGroup(%x)::addContact: %s", this, qPrintable(group->fullName())); + } + + QStringList moreGroups; + moreGroups << nestedGroups.join(groupDelimiter()); + group->addContact(contact, moreGroups); + } + } +} + +void ContactListNestedGroup::addGroup(ContactListGroup* group) +{ + Q_ASSERT(group); + Q_ASSERT(!groups_.contains(group)); + groups_.append(group); + addItem(new ContactListItemProxy(this, group)); +} + +void ContactListNestedGroup::contactUpdated(PsiContact* contact) +{ + // since now we're using groupCache() passing contact to + // all nested groups is not necessary anymore + // foreach(ContactListGroup* group, groups_) + // group->contactUpdated(contact); + ContactListGroup::contactUpdated(contact); +} + +#define CONTACTLISTNESTEDGROUP_OLD_CONTACTGROUPSCHANGED + +void ContactListNestedGroup::contactGroupsChanged(PsiContact* contact, QStringList contactGroups) +{ + if (canContainSpecialGroups()) { + ContactListGroup* specialGroup = specialGroupFor(contact); + QHashIterator > it(specialGroups_); + while (it.hasNext()) { + it.next(); + if (!it.value().isNull()) { + it.value()->contactGroupsChanged(contact, it.value() == specialGroup ? + contactGroups : QStringList()); + } + } + + if (specialGroup) { + return; + } + } + +#ifdef CONTACTLISTNESTEDGROUP_OLD_CONTACTGROUPSCHANGED + bool addToSelf = false; + QList splitGroupNames; + foreach(QString group, contactGroups) { + if (group == name()) { + addToSelf = true; + continue; + } + + QStringList split; +#ifdef CONTACTLIST_NESTED_GROUPS + split = group.split(groupDelimiter()); +#else + split += group; +#endif + if (!name().isEmpty()) { + QString firstPart = split.takeFirst(); + // hmm, probably should continue as the data should be invalid + } + splitGroupNames << split; + } + + // here we process splitGroupNames and update nested groups which are already present + QList emptyGroups; + QVector unnotifiedGroups = groups_; + QList newGroups; + while (!splitGroupNames.isEmpty()) { + const QStringList& split = splitGroupNames.first(); + ContactListGroup* group = 0; + if (!split.isEmpty()) + group = findGroup(split.first()); + + if (!group) { + newGroups << splitGroupNames.takeFirst(); + continue; + } + + QStringList mergedGroupNames; + foreach(QStringList i, splitGroupNames) + if (!i.isEmpty() && i.first() == split.first()) + mergedGroupNames += i.join(groupDelimiter()); + + foreach(QString i, mergedGroupNames) { +#ifdef CONTACTLIST_NESTED_GROUPS + splitGroupNames.removeAll(i.split(groupDelimiter())); +#else + splitGroupNames.removeAll(QStringList() << i); +#endif + } + + group->contactGroupsChanged(contact, mergedGroupNames); + if (!group->itemsCount()) { + emptyGroups << group; + } + unnotifiedGroups.remove(unnotifiedGroups.indexOf(group)); + } + + // remove the contact from the unnotified groups + foreach(ContactListGroup* group, unnotifiedGroups) { + group->contactGroupsChanged(contact, QStringList()); + if (!group->itemsCount()) { + emptyGroups << group; + } + } + + // remove empty groups afterwards + foreach(ContactListGroup* group, emptyGroups) { +// qWarning("ContactListNextedGroup(%x)::contactGroupsChanged: removing empty group: %s", this, qPrintable(group->fullName())); + removeItem(ContactListGroup::findGroup(group)); + groups_.remove(groups_.indexOf(group)); + delete group; + } + + // create new groups, if required + foreach(QStringList split, newGroups) { + QStringList fullGroupName; + if (!name().isEmpty()) + fullGroupName << name(); + fullGroupName += split; + QStringList tmp; + tmp << fullGroupName.join(groupDelimiter()); + addContact(contact, tmp); + } + + if (addToSelf) { + // contact should be located here + if (!findContact(contact)) { + Q_ASSERT(!contactGroups.isEmpty()); + ContactListGroup::contactGroupsChanged(contact, contactGroups); + } + } + else { + // remove self + if (findContact(contact)) { + ContactListGroup::contactGroupsChanged(contact, QStringList()); + } + } +#else + QStringList newNestedGroups = fullName().isEmpty() + ? contactGroups + : contactGroups.filter(QRegExp(QString("^%1($|%2)").arg(fullName(), groupDelimiter()))); + + QStringList directChildren; + foreach(QString nnGroup, newNestedGroups) { + QString unqualifiedName = nnGroup.mid(QString(fullName().isEmpty() ? "" : fullName() + groupDelimiter()).length()); + if (!unqualifiedName.isEmpty()) { + // direct children! + directChildren << QString(name().isEmpty() ? "" : name() + groupDelimiter()) + unqualifiedName; + } + } + + // let's add contacts to the children + addContact(contact, directChildren); + + // we're running into recursion + foreach(ContactListGroup* child, groups_) { + child->contactGroupsChanged(contact, newNestedGroups); + if (!child->itemsCount()) { +// qWarning("ContactListNextedGroup(%x)::contactGroupsChanged: removing empty group2: %s", this, qPrintable(child->fullName())); + removeItem(ContactListGroup::findGroup(child)); + groups_.removeAll(groups_.indexOf(child)); + delete group; + } + } + + // let's add/remove the contact from itself, if necessary + ContactListGroup::contactGroupsChanged(contact, newNestedGroups.filter(QRegExp(QString("^%1$").arg(fullName())))); +#endif +} + +ContactListGroup* ContactListNestedGroup::findGroup(const QString& groupName) const +{ + foreach(ContactListGroup* group, groups_) { + if (group->name() == groupName) { + if (!dynamic_cast(group)) + return group; + } + } + + return 0; +} + +bool ContactListNestedGroup::canContainSpecialGroups() const +{ + return !parent() && model()->groupsEnabled(); +} + +ContactListGroup* ContactListNestedGroup::specialGroupFor(PsiContact* contact) +{ + ContactListGroup::SpecialType type = ContactListGroup::SpecialType_None; + if (!contact->inList()) { + type = ContactListGroup::SpecialType_NotInList; + } + else if (contact->isPrivate()) { + type = ContactListGroup::SpecialType_MUCPrivateChats; + } + else if (contact->isAgent()) { + type = ContactListGroup::SpecialType_Transports; + } + // else if (contact->noGroups()) { + // type = ContactListGroup::SpecialType_General; + // } + else { + return 0; + } + + if (!specialGroups_.contains(type) || specialGroups_[type].isNull()) { + specialGroups_[type] = new ContactListSpecialGroup(model(), this, type); + addGroup(specialGroups_[type]); + } + return specialGroups_[type]; +} diff --git a/src/contactlistnestedgroup.h b/src/contactlistnestedgroup.h new file mode 100644 index 000000000..0119e8e0e --- /dev/null +++ b/src/contactlistnestedgroup.h @@ -0,0 +1,53 @@ +/* + * contactlistnestedgroup.h - class to handle nested contact list groups + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTNESTEDGROUP_H +#define CONTACTLISTNESTEDGROUP_H + +#include "contactlistgroup.h" + +class ContactListNestedGroup : public ContactListGroup +{ + Q_OBJECT +public: + ContactListNestedGroup(ContactListModel* model, ContactListGroup* parent, QString name); + ~ContactListNestedGroup(); + + virtual bool canContainSpecialGroups() const; + + // reimplemented + virtual void addContact(PsiContact* contact, QStringList contactGroups); + virtual void contactUpdated(PsiContact* contact); + virtual void contactGroupsChanged(PsiContact* contact, QStringList contactGroups); + +protected: + ContactListGroup* specialGroupFor(PsiContact* contact); + void addGroup(ContactListGroup* group); + ContactListGroup* findGroup(const QString& groupName) const; + + // reimplemented + virtual void clearGroup(); + +private: + QVector groups_; + QHash > specialGroups_; +}; + +#endif diff --git a/src/contactlistproxymodel.cpp b/src/contactlistproxymodel.cpp new file mode 100644 index 000000000..abeef679d --- /dev/null +++ b/src/contactlistproxymodel.cpp @@ -0,0 +1,145 @@ +/* + * contactlistproxymodel.cpp - contact list model sorting and filtering + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistproxymodel.h" + +#include "psicontactlist.h" +#include "psicontact.h" +#include "contactlistitem.h" +#include "contactlistgroup.h" +#include "contactlistitemproxy.h" +#include "contactlistspecialgroup.h" + +ContactListProxyModel::ContactListProxyModel(QObject* parent) + : QSortFilterProxyModel(parent) +{ + sort(0, Qt::AscendingOrder); + setDynamicSortFilter(true); +} + +void ContactListProxyModel::setSourceModel(QAbstractItemModel* model) +{ + Q_ASSERT(dynamic_cast(model)); + QSortFilterProxyModel::setSourceModel(model); + connect(model, SIGNAL(showOfflineChanged()), SLOT(filterParametersChanged())); + connect(model, SIGNAL(showSelfChanged()), SLOT(filterParametersChanged())); + connect(model, SIGNAL(showSelfChanged()), SLOT(filterParametersChanged())); + connect(model, SIGNAL(showTransportsChanged()), SLOT(filterParametersChanged())); +} + +bool ContactListProxyModel::showOffline() const +{ + return static_cast(sourceModel())->showOffline(); +} + +bool ContactListProxyModel::showSelf() const +{ + return static_cast(sourceModel())->showSelf(); +} + +bool ContactListProxyModel::showTransports() const +{ + return static_cast(sourceModel())->showTransports(); +} + +bool ContactListProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const +{ + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + if (!index.isValid()) + return false; + + ContactListItemProxy* itemProxy = static_cast(index.internalPointer()); + ContactListItem* item = itemProxy ? itemProxy->item() : 0; + if (!item) { + Q_ASSERT(false); + return false; + } + + if (item->editing()) { + return true; + } + + switch (ContactListModel::indexType(index)) { + case ContactListModel::ContactType: { + PsiContact* psiContact = dynamic_cast(item); + + if (psiContact->isHidden()) { + return false; + } + + if (psiContact->isSelf()) { + return showSelf(); + } + else if (psiContact->isAgent()) { + return showTransports(); + } + + if (!showOffline()) { + return psiContact->isOnline(); + } + else { + return true; + } + } + case ContactListModel::GroupType: + { + ContactListGroup::SpecialType specialGroupType = static_cast(index.data(ContactListModel::SpecialGroupTypeRole).toInt()); + if (specialGroupType != ContactListGroup::SpecialType_None) { + if (specialGroupType == ContactListGroup::SpecialType_Transports) + return showTransports(); + } + + if (!showOffline()) { + ContactListGroup* group = dynamic_cast(item); + return group->haveOnlineContacts(); + } + else { + return true; + } + } + case ContactListModel::AccountType: + return true; + case ContactListModel::InvalidType: + return true; + default: + Q_ASSERT(false); + } + + return true; +} + +bool ContactListProxyModel::lessThan(const QModelIndex& left, const QModelIndex& right) const +{ + ContactListItemProxy* item1 = static_cast(left.internalPointer()); + ContactListItemProxy* item2 = static_cast(right.internalPointer()); + if (!item1 || !item2) + return false; + return item1->item()->compare(item2->item()); +} + +void ContactListProxyModel::filterParametersChanged() +{ + invalidateFilter(); +} + +void ContactListProxyModel::updateSorting() +{ + invalidate(); +} diff --git a/src/contactlistproxymodel.h b/src/contactlistproxymodel.h new file mode 100644 index 000000000..9d1cebfbf --- /dev/null +++ b/src/contactlistproxymodel.h @@ -0,0 +1,50 @@ +/* + * contactlistproxymodel.h - contact list model sorting and filtering + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTPROXYMODEL_H +#define CONTACTLISTPROXYMODEL_H + +#include + +class PsiContactList; + +class ContactListProxyModel : public QSortFilterProxyModel +{ + Q_OBJECT +public: + ContactListProxyModel(QObject* parent); + + void setSourceModel(QAbstractItemModel* model); + + void updateSorting(); + +protected: + bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const; + bool lessThan(const QModelIndex& left, const QModelIndex& right) const; + + bool showOffline() const; + bool showSelf() const; + bool showTransports() const; + +private slots: + void filterParametersChanged(); +}; + +#endif diff --git a/src/contactlistspecialgroup.cpp b/src/contactlistspecialgroup.cpp new file mode 100644 index 000000000..a8fe2da21 --- /dev/null +++ b/src/contactlistspecialgroup.cpp @@ -0,0 +1,112 @@ +/* + * contactlistspecialgroup.cpp + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistspecialgroup.h" + +#include + +ContactListSpecialGroup::ContactListSpecialGroup(ContactListModel* model, ContactListGroup* parent, ContactListGroup::SpecialType type) + : ContactListNestedGroup(model, parent, QString()) + , specialType_(type) +{ + name_ = QString::fromUtf8("☣special_group_"); + switch (specialType_) { + // case SpecialType_General: + // name_ += "general"; + // displayName_ = tr("General"); + // break; + case SpecialType_NotInList: + name_ += "notinlist"; + displayName_ = tr("Not in List"); + break; + case SpecialType_Transports: + name_ += "transports"; + displayName_ = tr("Agents/Transports"); + break; + case SpecialType_MUCPrivateChats: + name_ += "mucprivatechats"; + displayName_ = tr("Private messages"); + default: + Q_ASSERT(false); + } + quietSetName(name_); +} + +bool ContactListSpecialGroup::isSpecial() const +{ + return true; +} + +ContactListGroup::SpecialType ContactListSpecialGroup::specialGroupType() const +{ + return specialType_; +} + +QString ContactListSpecialGroup::internalGroupName() const +{ + return name_; +} + +const QString& ContactListSpecialGroup::name() const +{ + return displayName_; +} + +bool ContactListSpecialGroup::compare(const ContactListItem* other) const +{ + const ContactListGroup* group = dynamic_cast(other); + if (group) { + if (!group->isSpecial()) + return false; + + const ContactListSpecialGroup* specialGroup = dynamic_cast(other); + Q_ASSERT(specialGroup); + + QMap rank; + rank[SpecialType_MUCPrivateChats] = 0; + rank[SpecialType_Transports] = 1; + rank[SpecialType_NotInList] = 2; + Q_ASSERT(rank.contains(specialGroupType())); + Q_ASSERT(rank.contains(specialGroup->specialGroupType())); + return rank[specialGroupType()] > rank[specialGroup->specialGroupType()]; + } + + return ContactListGroup::compare(other); +} + +bool ContactListSpecialGroup::isEditable() const +{ + return false; +} + +bool ContactListSpecialGroup::isRemovable() const +{ + return false; +} + +void ContactListSpecialGroup::addContact(PsiContact* contact, QStringList contactGroups) +{ + ContactListGroup::addContact(contact, contactGroups); +} + +void ContactListSpecialGroup::contactGroupsChanged(PsiContact* contact, QStringList contactGroups) +{ + ContactListGroup::contactGroupsChanged(contact, contactGroups); +} diff --git a/src/contactlistspecialgroup.h b/src/contactlistspecialgroup.h new file mode 100644 index 000000000..c4fd840de --- /dev/null +++ b/src/contactlistspecialgroup.h @@ -0,0 +1,52 @@ +/* + * contactlistspecialgroup.h + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTSPECIALGROUP_H +#define CONTACTLISTSPECIALGROUP_H + +#include "contactlistnestedgroup.h" + +class ContactListSpecialGroup : public ContactListNestedGroup +{ + Q_OBJECT +public: + ContactListSpecialGroup(ContactListModel* model, ContactListGroup* parent, ContactListGroup::SpecialType type); + + // reimplemented + virtual bool isEditable() const; + virtual bool isRemovable() const; + virtual bool isSpecial() const; + virtual SpecialType specialGroupType() const; + virtual QString internalGroupName() const; + virtual const QString& name() const; + virtual bool compare(const ContactListItem* other) const; + + // reimplemented + virtual void addContact(PsiContact* contact, QStringList contactGroups); + virtual void contactGroupsChanged(PsiContact* contact, QStringList contactGroups); + +private: + SpecialType specialType_; + QString name_; + QString displayName_; +}; + + +#endif diff --git a/src/contactlistutil.cpp b/src/contactlistutil.cpp new file mode 100644 index 000000000..d4bae9233 --- /dev/null +++ b/src/contactlistutil.cpp @@ -0,0 +1,275 @@ +/* + * contactlistutil.cpp + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistutil.h" + +#include +#include // for Qt::escape() + +#include "psicontact.h" +#include "contactlistdragmodel.h" +#include "contactlistmodelselection.h" +#include "removeconfirmationmessagebox.h" +#include "contactlistdragview.h" + +#ifdef YAPSI +#include "fakegroupcontact.h" +#endif + +void ContactListUtil::removeContact(PsiContact* contact, QMimeData* _selection, ContactListDragModel* model, QWidget* widget, QObject* obj) +{ + Q_ASSERT(model); + QModelIndexList indexes = model ? model->indexesFor(contact, _selection) : QModelIndexList(); + if (model && !indexes.isEmpty()) { + QMimeData* selection = model->mimeData(indexes); + QString selectionData = confirmationData(contact, _selection, model); + + // WARNING: selection could also contain groups. and when there are groups, + // all groups' contacts are marked for deletion too + QList contactsLost = model->contactsLostByRemove(selection); + bool removeConfirmed = contactsLost.isEmpty(); + + if (!removeConfirmed) { + QStringList contactNames = contactNamesFor(contactsLost); + + QString destructiveActionName; + QString msg; + if (!contactNames.isEmpty()) { + msg = tr("This will permanently remove
" + "%1" + "
from your contact list." + ).arg(contactNames.join(", ")); + } + +#ifdef YAPSI + QString complimentaryActionName; + const char* complimentaryActionSlot = 0; + + ContactListModelSelection contactListSelection(selection); + if (contactNames.count() > 1 || contactListSelection.groups().count()) { + QStringList tmp; + QStringList tmpContactNames = contactNames; + while (!tmpContactNames.isEmpty()) { + if (tmp.count() >= 10) + break; + + tmp << QString("%1. %2").arg(tmp.count() + 1).arg(tmpContactNames.takeFirst()); + } + + QString andNContacts; + if (!tmpContactNames.isEmpty()) { + andNContacts = tr("and %n contacts ", 0, + tmpContactNames.count()); + } + + if (contactListSelection.groups().count() > 1) { + msg = tr("This will permanently remove:
" + "%1" + "
%2and %n groups from your contact list.", + 0, + contactListSelection.groups().count() + ).arg(tmp.join("
")) + .arg(andNContacts); + } + else if (contactListSelection.groups().count() == 1) { + msg = tr("This will permanently remove:
" + "%1" + "
%2and \"%3\" group from your contact list." + ).arg(tmp.join("
")) + .arg(andNContacts) + .arg(contactListSelection.groups().first().fullName); + } + else { + msg = tr("This will permanently remove:
" + "%1" + "
%2from your contact list." + ).arg(tmp.join("
")) + .arg(andNContacts); + } + } + + if (indexes.count() == 1 && model->indexType(indexes.first()) == ContactListModel::GroupType) { + if (YaContactListContactsModel::virtualUnremovableGroups().contains(contactListSelection.groups().first().fullName)) { + msg = tr("This is a system group and can't be removed. " + "Permanently remove all its contacts from your contact list?"); + destructiveActionName = tr("Clear Group"); + } + else { + msg = tr("This will permanently remove
" + "%1" + "
group and all its contacts from your contact list.").arg(Qt::escape(indexes.first().data().toString())); + } + } + else if (indexes.count() == 1 && model->indexType(indexes.first()) == ContactListModel::ContactType) { + Q_ASSERT(contactListSelection.contacts().count() == 1); + ContactListModelSelection::Contact c = contactListSelection.contacts().first(); + + PsiAccount* account = contactList()->getAccount(c.account); + PsiContact* psiContact = account ? account->findContact(c.jid) : 0; + QStringList contactGroupsLostByRemove; + if (psiContact) { + contactGroupsLostByRemove = model->contactGroupsLostByRemove(psiContact, selection); + } + + if (psiContact && !psiContact->inList() && psiContact->blockAvailable()) { + msg = tr("This will permanently remove %1 from your contact list. " + "You could block it in order to avoid further messages.") + .arg(contactNames.join(", ")); + destructiveActionName = tr("Delete"); + complimentaryActionName = tr("Block"); + complimentaryActionSlot = "blockContactConfirmation"; + } + else if (psiContact && psiContact->groups().count() > 1 && contactGroupsLostByRemove.count() == 1) { + // TODO: needs to be translated + msg = tr("This will remove %1 from \"%2\" group. " + "You could also remove it from all groups.") + .arg(contactNames.join(", ")) + .arg(contactGroupsLostByRemove.first()); + destructiveActionName = tr("Delete"); + complimentaryActionName = tr("Delete From All Groups"); + // TODO: needs to be implemented + complimentaryActionSlot = "removeContactFullyConfirmation"; + } + } +#endif + + if (!msg.isEmpty()) { +#ifdef YAPSI + if (complimentaryActionSlot) { + RemoveConfirmationMessageBoxManager::instance()-> + removeConfirmation(selectionData, + obj, "removeContactConfirmation", + obj, complimentaryActionSlot, + tr("Deleting contacts"), + msg, + widget, + destructiveActionName, + complimentaryActionName); + } +#else + if (false) { + } +#endif + else { + RemoveConfirmationMessageBoxManager::instance()-> + removeConfirmation(selectionData, + obj, "removeContactConfirmation", + tr("Deleting contacts"), + msg, + widget, + destructiveActionName); + } + + removeConfirmed = false; + } + } + + QMetaObject::invokeMethod(obj, "removeContactConfirmation", Qt::DirectConnection, + QGenericReturnArgument(), + Q_ARG(QString, selectionData), Q_ARG(bool, removeConfirmed)); + delete selection; + } +} + +QString ContactListUtil::confirmationData(PsiContact* contact, QMimeData* _selection, ContactListDragModel* model) +{ + QString selectionData; + QModelIndexList indexes = model->indexesFor(contact, _selection); + if (model && !indexes.isEmpty()) { + QMimeData* selection = model->mimeData(indexes); + ContactListModelSelection* contactListModelSelection = dynamic_cast(selection); + if (contactListModelSelection) { + selectionData = QCA::Base64().arrayToString(contactListModelSelection->data(contactListModelSelection->mimeType())); + } + } + return selectionData; +} + +QList ContactListUtil::contactsFor(const QString& confirmationData, ContactListDragModel* model) +{ + Q_ASSERT(model); + QList result; + if (model) { + ContactListModelSelection selection(0); + selection.setData(selection.mimeType(), QCA::Base64().stringToArray(confirmationData).toByteArray()); + + QModelIndexList indexes = model->indexesFor(0, &selection); + foreach(const QModelIndex& index, indexes) { + PsiContact* contact = model->contactFor(index); + if (!contact) + continue; + + result << contact; + } + } + + return result; +} + +QStringList ContactListUtil::contactNamesFor(QList contacts) +{ + QStringList contactNames; + foreach(PsiContact* contact, contacts) { +#ifdef YAPSI + if (dynamic_cast(contact)) { + continue; + } +#endif + + QString name = contact->name(); + if (name != contact->jid().full()) { + // TODO: ideally it should be wrapped in , + // but it breaks layout in Qt 4.3.4 + name = tr("%1 (%2)").arg(name, Qt::escape(contact->jid().full())); + } + contactNames << QString("%1").arg(Qt::escape(name)); + } + return contactNames; +} + +void ContactListUtil::removeContactConfirmation(const QString& id, bool confirmed, ContactListDragModel* model, ContactListDragView* contactListView) +{ + Q_ASSERT(model); + Q_ASSERT(contactListView); + + ContactListModelSelection selection(0); + selection.setData(selection.mimeType(), QCA::Base64().stringToArray(id).toByteArray()); + + if (confirmed) { + removeContactConfirmation(&selection, model); + } + + // message box steals focus, so we're restoring it back + contactListView->restoreSelection(&selection); + bringToFront(contactListView); + contactListView->setFocus(); +} + +void ContactListUtil::removeContactConfirmation(QMimeData* contactSelection, ContactListDragModel* model) +{ + Q_ASSERT(contactSelection); + Q_ASSERT(model); + QModelIndexList indexes = model->indexesFor(0, contactSelection); + if (model && !indexes.isEmpty()) { + QMimeData* selection = model->mimeData(indexes); + model->removeIndexes(selection); + delete selection; + } +} diff --git a/src/contactlistutil.h b/src/contactlistutil.h new file mode 100644 index 000000000..e9a6a1774 --- /dev/null +++ b/src/contactlistutil.h @@ -0,0 +1,48 @@ +/* + * contactlistutil.h + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTUTIL_H +#define CONTACTLISTUTIL_H + +#include + +class PsiContact; +class QMimeData; +class ContactListDragModel; +class ContactListDragView; + +class ContactListUtil : public QObject +{ + Q_OBJECT +public: + static void removeContact(PsiContact* contact, QMimeData* _selection, ContactListDragModel* model, QWidget* widget, QObject* obj); + static QString confirmationData(PsiContact* contact, QMimeData* _selection, ContactListDragModel* model); + + static void removeContactConfirmation(const QString& id, bool confirmed, ContactListDragModel* model, ContactListDragView* contactListView); + static void removeContactConfirmation(QMimeData* contactSelection, ContactListDragModel* model); + + static QList contactsFor(const QString& confirmationData, ContactListDragModel* model); + static QStringList contactNamesFor(QList contacts); + +private: + ContactListUtil() {} +}; + +#endif diff --git a/src/contactlistview.cpp b/src/contactlistview.cpp new file mode 100644 index 000000000..67b4d5524 --- /dev/null +++ b/src/contactlistview.cpp @@ -0,0 +1,518 @@ +/* + * contactlistview.cpp - base contact list widget class + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "contactlistitem.h" +#include "contactlistitemmenu.h" +#include "contactlistitemproxy.h" +#include "contactlistmodel.h" +#include "contactlistview.h" +#include "psioptions.h" +#include "psitooltip.h" +#include "contactlistgroupcache.h" +#include "contactlistgroup.h" +#include "contactlistproxymodel.h" +#ifdef YAPSI +#include "smoothscrollbar.h" +#include "yawindowtheme.h" +#include "yavisualutil.h" +#endif + +#ifdef YAPSI +class ContactListViewCorner : public QWidget +{ +public: + ContactListViewCorner() + : QWidget(0) + {} + +protected: + // reimplemented + void paintEvent(QPaintEvent*) + { + QPainter p(this); + + Ya::VisualUtil::paintRosterBackground(this, &p); + + QLinearGradient linearGrad( + QPointF(rect().left(), rect().top()), QPointF(rect().right(), rect().bottom()) + ); + QColor transparent(Qt::white); + transparent.setAlpha(0); + linearGrad.setColorAt(0, Qt::white); + linearGrad.setColorAt(0.4, Qt::white); + linearGrad.setColorAt(1, transparent); + p.fillRect(rect(), linearGrad); + } +}; +#endif + +ContactListView::ContactListView(QWidget* parent) + : HoverableTreeView(parent) + , contextMenuActive_(false) +{ + setUniformRowHeights(false); + setAlternatingRowColors(true); + setRootIsDecorated(false); + // on Macs Enter key is the default EditKey, so we can't use EditKeyPressed + setEditTriggers(QAbstractItemView::EditKeyPressed); // lesser evil, otherwise no editing will be possible at all due to Qt bug + // setEditTriggers(QAbstractItemView::NoEditTriggers); + setIndentation(5); + header()->hide(); + setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + + // setItemDelegate(new PsiContactListViewDelegate(this)); + +#ifdef Q_WS_MAC + setFrameShape(QFrame::NoFrame); +#endif + +#ifdef YAPSI + setCornerWidget(new ContactListViewCorner()); +#endif + + connect(this, SIGNAL(activated(const QModelIndex&)), SLOT(itemActivated(const QModelIndex&))); + // showStatus_ = PsiOptions::instance()->getOption("options.ui.contactlist.status-messages.show").toBool(); +} + +static void setExpandedState(QTreeView* view, QAbstractItemModel* model, const QModelIndex& parent) +{ + Q_ASSERT(model); + for (int i = 0; i < model->rowCount(parent); i++) { + QModelIndex index = model->index(i, 0, parent); + view->setExpanded(index, model->data(index, ContactListModel::ExpandedRole).toBool()); + if (model->hasChildren(index)) { + setExpandedState(view, model, index); + } + } +} + +void ContactListView::doItemsLayout() +{ + if (!model()) + return; + HoverableTreeView::doItemsLayout(); + updateGroupExpandedState(); +} + +void ContactListView::updateGroupExpandedState() +{ + if (!model()) + return; + setExpandedState(this, model(), QModelIndex()); +} + +void ContactListView::showOfflineChanged() +{ + updateGroupExpandedState(); +} + +bool ContactListView::isContextMenuVisible() +{ + return contextMenu_ && contextMenuActive_; +} + +void ContactListView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) +{ + HoverableTreeView::selectionChanged(selected, deselected); + + updateContextMenu(); +} + +void ContactListView::updateContextMenu() +{ + if (isContextMenuVisible()) + return; + + if (contextMenu_) + delete contextMenu_; + contextMenu_ = 0; + + // FIXME: need to implement context menu merging + if (selectedIndexes().count() == 1) { + ContactListItemProxy* item = itemProxy(selectedIndexes().first()); + if (item) { + contextMenu_ = createContextMenuFor(item->item()); + addContextMenuActions(); + } + } +} + +ContactListItemMenu* ContactListView::createContextMenuFor(ContactListItem* item) const +{ + if (item) + return item->contextMenu(dynamic_cast(realModel())); + return 0; +} + +void ContactListView::focusInEvent(QFocusEvent* event) +{ + HoverableTreeView::focusInEvent(event); + addContextMenuActions(); +} + +void ContactListView::focusOutEvent(QFocusEvent* event) +{ + HoverableTreeView::focusOutEvent(event); + removeContextMenuActions(); +} + +void ContactListView::addContextMenuActions() +{ + if (contextMenu_) { + foreach(QAction* action, contextMenu_->availableActions()) { + addContextMenuAction(action); + } + } +} + +void ContactListView::addContextMenuAction(QAction* action) +{ + addAction(action); +} + +void ContactListView::removeContextMenuActions() +{ + if (contextMenu_) + foreach(QAction* action, contextMenu_->availableActions()) + removeAction(action); +} + +void ContactListView::contextMenuEvent(QContextMenuEvent* e) +{ + if (contextMenu_ && contextMenu_->availableActions().count() > 0) { + e->accept(); + contextMenuActive_ = true; + contextMenu_->exec(e->globalPos()); + contextMenuActive_ = false; + } + else { + e->ignore(); + } +} + +void ContactListView::setModel(QAbstractItemModel* model) +{ + HoverableTreeView::setModel(model); + QAbstractItemModel* connectToModel = realModel(); + if (dynamic_cast(connectToModel)) { + connect(this, SIGNAL(expanded(const QModelIndex&)), SLOT(itemExpanded(const QModelIndex&))); + connect(this, SIGNAL(collapsed(const QModelIndex&)), SLOT(itemCollapsed(const QModelIndex&))); + connect(this, SIGNAL(realExpanded(const QModelIndex&)), connectToModel, SLOT(expanded(const QModelIndex&))); + connect(this, SIGNAL(realCollapsed(const QModelIndex&)), connectToModel, SLOT(collapsed(const QModelIndex&))); + connect(connectToModel, SIGNAL(inPlaceRename()), SLOT(rename())); + + // connection to showOfflineChanged() should be established after the proxy model does so, + // otherwise we won't get consistently re-expanded groups. Qt::QueuedConnection could + // be advised for in such case. + connect(connectToModel, SIGNAL(showOfflineChanged()), SLOT(showOfflineChanged())); + } +} + +void ContactListView::resizeEvent(QResizeEvent* e) +{ + HoverableTreeView::resizeEvent(e); + if (header()->count() > 0) + header()->resizeSection(0, viewport()->width()); +} + +void ContactListView::itemExpanded(const QModelIndex& index) +{ + emit realExpanded(realIndex(index)); +} + +void ContactListView::itemCollapsed(const QModelIndex& index) +{ + emit realCollapsed(realIndex(index)); +} + +/** + * Branches? We don't want no steenking branches! + */ +void ContactListView::drawBranches(QPainter*, const QRect&, const QModelIndex&) const +{ +} + +/** + * Make Enter/Return/F2 to not trigger editing, and make Enter/Return call activated(). + */ +void ContactListView::keyPressEvent(QKeyEvent* event) +{ + switch (event->key()) { + case Qt::Key_F2: + event->ignore(); + break; + case Qt::Key_Enter: + case Qt::Key_Return: + if (state() != EditingState || hasFocus()) { + if (currentIndex().isValid()) + emit activated(currentIndex()); + } + else { + event->ignore(); + } + break; + default: + HoverableTreeView::keyPressEvent(event); + } +} + + +/** + * TODO + */ +void ContactListView::rename() +{ + if (!hasFocus()) + return; + + QModelIndexList indexes = selectedIndexes(); + if (!indexes.count()) { + return; + } + if (indexes.count() > 1) { + qWarning("ContactListView::rename(): selectedIndexes().count() > 1"); + return; + } + + QModelIndex indexToEdit = indexes.first(); + QModelIndex parent; + while (parent.isValid()) { + expand(parent); + parent = parent.parent(); + } + + scrollTo(indexToEdit); + edit(indexToEdit); +} + +/** + * Re-implemented in order to use PsiToolTip class to display tooltips. + */ +bool ContactListView::viewportEvent(QEvent* event) +{ + if (event->type() == QEvent::ToolTip && + (isActiveWindow() || window()->testAttribute(Qt::WA_AlwaysShowToolTips))) + { + QHelpEvent* he = static_cast(event); + showToolTip(indexAt(he->pos()), he->globalPos()); + return true; + } + + return HoverableTreeView::viewportEvent(event); +} + +void ContactListView::showToolTip(const QModelIndex& index, const QPoint& globalPos) const +{ + if (!model()) + return; + QVariant toolTip = model()->data(index, Qt::ToolTipRole); + PsiToolTip::showText(globalPos, toolTip.toString(), this); +} + +void ContactListView::activate(const QModelIndex& index) +{ + itemActivated(index); +} + +void ContactListView::itemActivated(const QModelIndex& index) +{ + model()->setData(index, QVariant(true), ContactListModel::ActivateRole); +} + +static QAbstractItemModel* realModel(QAbstractItemModel* model) +{ + QSortFilterProxyModel* proxyModel = dynamic_cast(model); + if (proxyModel) + return realModel(proxyModel->sourceModel()); + return model; +} + +QAbstractItemModel* ContactListView::realModel() const +{ + return ::realModel(model()); +} + +QModelIndexList ContactListView::realIndexes(const QModelIndexList& indexes) const +{ + QModelIndexList result; + foreach(QModelIndex index, indexes) { + QModelIndex ri = realIndex(index); + if (ri.isValid()) + result << ri; + } + return result; +} + +static QModelIndex realIndex(QAbstractItemModel* model, QModelIndex index) +{ + QSortFilterProxyModel* proxyModel = dynamic_cast(model); + if (proxyModel) + return realIndex(proxyModel->sourceModel(), proxyModel->mapToSource(index)); + return index; +} + +QModelIndex ContactListView::realIndex(const QModelIndex& index) const +{ + return ::realIndex(model(), index); +} + +QModelIndexList ContactListView::proxyIndexes(const QModelIndexList& indexes) const +{ + QModelIndexList result; + foreach(QModelIndex index, indexes) { + QModelIndex pi = proxyIndex(index); + if (pi.isValid()) + result << pi; + } + return result; +} + +static QModelIndex proxyIndex(QAbstractItemModel* model, QModelIndex index) +{ + QSortFilterProxyModel* proxyModel = dynamic_cast(model); + if (proxyModel) + return proxyIndex(proxyModel->sourceModel(), proxyModel->mapFromSource(index)); + return index; +} + +QModelIndex ContactListView::proxyIndex(const QModelIndex& index) const +{ + return ::proxyIndex(model(), index); +} + +ContactListItemProxy* ContactListView::itemProxy(const QModelIndex& index) const +{ + if (!index.isValid()) + return 0; + return static_cast(realIndex(index).internalPointer()); +} + +QLineEdit* ContactListView::currentEditor() const +{ + QWidget* w = indexWidget(currentIndex()); + return dynamic_cast(w); +} + +void ContactListView::setEditingIndex(const QModelIndex& index, bool editing) const +{ + ContactListItemProxy* itemProxy = this->itemProxy(index); + if (itemProxy) { + itemProxy->item()->setEditing(editing); + } + + ContactListModel* contactListModel = dynamic_cast(realModel()); + if (contactListModel) { + contactListModel->setUpdatesEnabled(!editing); + } +} + +void ContactListView::closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint) +{ + setEditingIndex(currentIndex(), false); + + HoverableTreeView::closeEditor(editor, hint); +} + +void ContactListView::saveCurrentGroupOrder(const QModelIndex& parent) +{ + ContactListModel* contactListModel = dynamic_cast(realModel()); + Q_ASSERT(contactListModel); + if (!contactListModel) + return; + + QModelIndexList groups; + for (int row = 0; row < this->model()->rowCount(parent); ++row) { + QModelIndex i = this->model()->index(row, parent.column(), parent); + if (ContactListModel::indexType(i) == ContactListModel::GroupType) { + groups << i; + + saveCurrentGroupOrder(i); + } + } + + foreach(QModelIndex i, groups) { + contactListModel->setGroupOrder(i.data(ContactListModel::FullGroupNameRole).toString(), + groups.indexOf(i)); + } +} + +// TODO: FIXME: need to adapt the code in order to make it work with nested groups +void ContactListView::commitData(QWidget* editor) +{ + QString newGroupName; + int groupOrder = 0; + + QLineEdit* lineEdit = currentEditor(); + ContactListModel* contactListModel = dynamic_cast(realModel()); + if (lineEdit && contactListModel && contactListModel->indexType(realIndex(currentIndex())) == ContactListModel::GroupType) { + // TODO: deal with nested groups too! + newGroupName = lineEdit->text(); + groupOrder = contactListModel->groupOrder(currentIndex().data(ContactListModel::FullGroupNameRole).toString()); + } + + HoverableTreeView::commitData(editor); + + if (!newGroupName.isEmpty() && contactListModel) { + setEditingIndex(currentIndex(), false); + contactListModel->updaterCommit(); + contactListModel->setGroupOrder(newGroupName, groupOrder); + + ContactListGroup* group = contactListModel->groupCache()->findGroup(newGroupName); + if (group) { + group->updateOnlineContactsFlag(); + } + ContactListGroup* parent = group ? group->parent() : 0; + int i = (group && parent) ? parent->indexOf(group) : -1; + ContactListItemProxy* proxy = (parent && (i >= 0)) ? parent->item(i) : 0; + + saveCurrentGroupOrder(QModelIndex()); + + if (proxy) { + // we don't want doItemsLayout() to restore a previous selection (now out of date) + doItemsLayout(); + + QModelIndex index = contactListModel->itemProxyToModelIndex(proxy); + QModelIndex proxyIndex = this->proxyIndex(index); + if (proxyIndex.isValid()) { +#ifdef YAPSI + dynamic_cast(verticalScrollBar())->setEnableSmoothScrolling(false); +#endif + setCurrentIndex(proxyIndex); + scrollTo(proxyIndex); +#ifdef YAPSI + dynamic_cast(verticalScrollBar())->setEnableSmoothScrolling(true); +#endif + } + else { + // TODO: proxy model somehow is not always updated + } + } + } + setFocus(); + updateContextMenu(); +} diff --git a/src/contactlistview.h b/src/contactlistview.h new file mode 100644 index 000000000..eed86e331 --- /dev/null +++ b/src/contactlistview.h @@ -0,0 +1,106 @@ +/* + * contactlistview.h - base contact list widget class + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTVIEW_H +#define CONTACTLISTVIEW_H + +#include "hoverabletreeview.h" + +#include + +class QWidget; +class ContactListItem; +class ContactListItemMenu; +class ContactListItemProxy; +class QLineEdit; + +class ContactListView : public HoverableTreeView +{ + Q_OBJECT + +public: + ContactListView(QWidget* parent = 0); + + QAbstractItemModel* realModel() const; + QModelIndexList realIndexes(const QModelIndexList& indexes) const; + QModelIndex realIndex(const QModelIndex& index) const; + QModelIndexList proxyIndexes(const QModelIndexList& indexes) const; + QModelIndex proxyIndex(const QModelIndex& index) const; + ContactListItemProxy* itemProxy(const QModelIndex& index) const; + void setEditingIndex(const QModelIndex& index, bool editing) const; + + void activate(const QModelIndex& index); + + // reimplemented + void setModel(QAbstractItemModel* model); + +public slots: + virtual void rename(); + +signals: + void realExpanded(const QModelIndex&); + void realCollapsed(const QModelIndex&); + +protected: + // reimplemented + bool viewportEvent(QEvent* event); + void commitData(QWidget* editor); + void closeEditor(QWidget* editor, QAbstractItemDelegate::EndEditHint hint); + + virtual void showToolTip(const QModelIndex& index, const QPoint& globalPos) const; + +protected slots: + virtual void itemActivated(const QModelIndex& index); + + virtual void showOfflineChanged(); + void updateGroupExpandedState(); + virtual void itemExpanded(const QModelIndex&); + virtual void itemCollapsed(const QModelIndex&); + + // reimplamented + virtual void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected); + virtual void focusInEvent(QFocusEvent* event); + virtual void focusOutEvent(QFocusEvent* event); + +protected: + // reimplamented + void contextMenuEvent(QContextMenuEvent*); + virtual void doItemsLayout(); + void drawBranches(QPainter*, const QRect&, const QModelIndex&) const; + void keyPressEvent(QKeyEvent*); + void resizeEvent(QResizeEvent*); + + QLineEdit* currentEditor() const; + + bool isContextMenuVisible(); + virtual ContactListItemMenu* createContextMenuFor(ContactListItem* item) const; + + virtual void addContextMenuActions(); + virtual void removeContextMenuActions(); + virtual void addContextMenuAction(QAction* action); + virtual void updateContextMenu(); + void saveCurrentGroupOrder(const QModelIndex& parent); + +private: + QPointer contextMenu_; + bool contextMenuActive_; +}; + +#endif diff --git a/src/contactlistviewdelegate.cpp b/src/contactlistviewdelegate.cpp new file mode 100755 index 000000000..d94db8f07 --- /dev/null +++ b/src/contactlistviewdelegate.cpp @@ -0,0 +1,431 @@ +/* + * contactlistviewdelegate.cpp - base class for painting contact list items + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactlistviewdelegate.h" + +#include // for INT_MAX + +#include +#include +#include + +#include "contactlistmodel.h" +#include "contactlistview.h" + +#ifdef YAPSI +#include "yavisualutil.h" +#endif + +class ContactListViewDelegate::Private +{ +public: + ContactListView* contactList; + HoverableStyleOptionViewItem opt; + QIcon::Mode iconMode; + QIcon::State iconState; +}; + +ContactListViewDelegate::ContactListViewDelegate(ContactListView* parent) + : QItemDelegate(parent) + , horizontalMargin_(5) + , verticalMargin_(3) +{ + d = new Private; + d->contactList = parent; +} + +ContactListViewDelegate::~ContactListViewDelegate() +{ + delete d; +} + +QIcon::Mode ContactListViewDelegate::iconMode() const +{ + return d->iconMode; +} + +QIcon::State ContactListViewDelegate::iconState() const +{ + return d->iconState; +} + +const HoverableStyleOptionViewItem& ContactListViewDelegate::opt() const +{ + return d->opt; +} + +const ContactListView* ContactListViewDelegate::contactList() const +{ + return d->contactList; +} + +inline static QIcon::Mode iconMode(QStyle::State state) +{ + if (!(state & QStyle::State_Enabled)) return QIcon::Disabled; + if (state & QStyle::State_Selected) return QIcon::Selected; + return QIcon::Normal; +} + +inline static QIcon::State iconState(QStyle::State state) +{ + return state & QStyle::State_Open ? QIcon::On : QIcon::Off; +} + +void ContactListViewDelegate::doSetOptions(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + d->opt = setOptions(index, option); + const QStyleOptionViewItemV2 *v2 = qstyleoption_cast(&option); + d->opt.features = v2 ? v2->features : QStyleOptionViewItemV2::ViewItemFeatures(QStyleOptionViewItemV2::None); + + const HoverableStyleOptionViewItem *hoverable = qstyleoption_cast(&option); + d->opt.hovered = hoverable ? hoverable->hovered : false; + d->opt.hoveredPosition = hoverable ? hoverable->hoveredPosition : QPoint(); + + // see hoverabletreeview.cpp + if ((d->opt.displayAlignment & Qt::AlignLeft) && + (d->opt.displayAlignment & Qt::AlignRight) && + (d->opt.displayAlignment & Qt::AlignHCenter) && + (d->opt.displayAlignment & Qt::AlignJustify)) + { + d->opt.hovered = true; + d->opt.hoveredPosition = QPoint(d->opt.decorationSize.width(), d->opt.decorationSize.height()); + } +} + +void ContactListViewDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + doSetOptions(option, index); + + // prepare + painter->save(); + // if (hasClipping()) + // painter->setClipRect(d->opt.rect); + + QVariant value; + + d->iconMode = ::iconMode(opt().state); + d->iconState = ::iconState(opt().state); + + switch (ContactListModel::indexType(index)) { + case ContactListModel::ContactType: + drawContact(painter, opt(), index); + break; + case ContactListModel::GroupType: + drawGroup(painter, opt(), index); + break; + case ContactListModel::AccountType: + drawAccount(painter, opt(), index); + break; + case ContactListModel::InvalidType: + painter->fillRect(option.rect, Qt::red); + break; + default: + QItemDelegate::paint(painter, option, index); + } + painter->restore(); + +} + +void ContactListViewDelegate::defaultDraw(QPainter* painter, const QStyleOptionViewItem& option) const +{ + Q_UNUSED(painter); + Q_UNUSED(option); +} + +int ContactListViewDelegate::avatarSize() const +{ + return 32; +} + +void ContactListViewDelegate::drawContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(painter); + Q_UNUSED(option); + Q_UNUSED(index); +} + +void ContactListViewDelegate::drawGroup(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(painter); + Q_UNUSED(option); + Q_UNUSED(index); +} + +void ContactListViewDelegate::drawAccount(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(painter); + Q_UNUSED(option); + Q_UNUSED(index); +} + +void ContactListViewDelegate::drawText(QPainter* painter, const QStyleOptionViewItem& option, const QRect& rect, const QString& text, const QModelIndex& index) const +{ + if (text.isEmpty()) + return; + + QPalette::ColorGroup cg = option.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + if (option.state & QStyle::State_Selected) { + painter->setPen(option.palette.color(cg, QPalette::HighlightedText)); + } + else { + painter->setPen(option.palette.color(cg, QPalette::Text)); + } + + QString txt = text; + if (rect.width() < option.fontMetrics.width(text)) { +#ifndef YAPSI + txt = option.fontMetrics.elidedText(text, option.textElideMode, rect.width()); +#endif + painter->save(); + painter->setClipRect(rect); + } + + // painter->save(); + // painter->setPen(Qt::gray); + // painter->drawLine(rect.left(), rect.top() + option.fontMetrics.ascent(), rect.right(), rect.top() + option.fontMetrics.ascent()); + // painter->restore(); + + painter->setFont(option.font); + painter->drawText(rect.x(), rect.y() + option.fontMetrics.ascent(), txt); + + if (rect.width() < option.fontMetrics.width(text)) { + painter->restore(); +// FIXME +#ifdef YAPSI + Ya::VisualUtil::drawTextFadeOut(painter, rect, backgroundColor(option, index), 15); +#else + Q_UNUSED(index); +#endif + } +} + +QSize ContactListViewDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(option); + if (index.isValid()) { + return QSize(16, avatarSize()); + } + + return QSize(0, 0); +} + +// copied from void QItemDelegate::drawBackground(), Qt 4.3.4 +QColor ContactListViewDelegate::backgroundColor(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QPalette::ColorGroup cg = option.state & QStyle::State_Enabled + ? QPalette::Normal : QPalette::Disabled; + if (cg == QPalette::Normal && !(option.state & QStyle::State_Active)) + cg = QPalette::Inactive; + + if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { + return option.palette.brush(cg, QPalette::Highlight).color(); + } + else { + QVariant value = index.data(Qt::BackgroundRole); + if (qVariantCanConvert(value)) { + return qvariant_cast(value).color(); + } + else { + return option.palette.brush(cg, QPalette::Base).color(); + } + } + + return Qt::white; +} + +void ContactListViewDelegate::drawBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QStyleOptionViewItem opt = option; + + // otherwise we're not painting the left item margin sometimes + // (for example when changing current selection by keyboard) + opt.rect.setLeft(0); + + { + if (option.showDecorationSelected && (option.state & QStyle::State_Selected)) { + painter->fillRect(opt.rect, backgroundColor(option, index)); + } + else { + QPointF oldBO = painter->brushOrigin(); + painter->setBrushOrigin(opt.rect.topLeft()); + painter->fillRect(opt.rect, backgroundColor(option, index)); + painter->setBrushOrigin(oldBO); + } + } +} + +bool ContactListViewDelegate::hovered() const +{ + return opt().hovered; +} + +void ContactListViewDelegate::setHovered(bool hovered) const +{ + d->opt.hovered = hovered; +} + +QPoint ContactListViewDelegate::hoveredPosition() const +{ + return opt().hoveredPosition; +} + +void ContactListViewDelegate::getEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index, QRect* widgetRect, QRect* lineEditRect) const +{ + Q_UNUSED(editor); + Q_ASSERT(widgetRect); + Q_ASSERT(lineEditRect); + switch (ContactListModel::indexType(index)) { + case ContactListModel::ContactType: + *widgetRect = this->nameRect(option, index).adjusted(-1, 0, 0, 1); + *lineEditRect = *widgetRect; + *widgetRect = this->editorRect(*widgetRect); + break; + case ContactListModel::GroupType: + *widgetRect = this->groupNameRect(option, index).adjusted(-1, 0, 0, 0); + widgetRect->setRight(option.rect.right() - horizontalMargin() - 1); + *lineEditRect = *widgetRect; + widgetRect->adjust(0, -3, 0, 2); + break; + // case ContactListModel::AccountType: + // break; + default: + ; + } +} + +void ContactListViewDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QRect widgetRect; + QRect lineEditRect; + getEditorGeometry(editor, option, index, &widgetRect, &lineEditRect); + + if (!widgetRect.isEmpty()) { + editor->setGeometry(widgetRect); + } +} + +// we're preventing modifications of QLineEdit while it's still being displayed, +// and the contact we're editing emits dataChanged() +void ContactListViewDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const +{ + // same code is in YaContactListViewDelegate::setEditorData + QLineEdit* lineEdit = dynamic_cast(editor); + if (lineEdit) { + if (lineEdit->text().isEmpty()) { + lineEdit->setText(index.data(Qt::EditRole).toString()); + lineEdit->selectAll(); + } + return; + } + + ContactListViewDelegate::setEditorData(editor, index); +} + +void ContactListViewDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const +{ + // same code is in YaContactListViewDelegate::setModelData + QLineEdit* lineEdit = dynamic_cast(editor); + if (lineEdit) { + if (index.data(Qt::EditRole).toString() != lineEdit->text()) { + model->setData(index, lineEdit->text(), Qt::EditRole); + } + } + else { + QItemDelegate::setModelData(editor, model, index); + } +} + +void ContactListViewDelegate::setEditorCursorPosition(QWidget* editor, int cursorPosition) const +{ + QLineEdit* lineEdit = dynamic_cast(editor); + if (lineEdit) { + if (cursorPosition == -1) + cursorPosition = lineEdit->text().length(); + lineEdit->setCursorPosition(cursorPosition); + } +} + +// adapted from QItemDelegate::eventFilter() +bool ContactListViewDelegate::eventFilter(QObject* object, QEvent* event) +{ + QWidget* editor = ::qobject_cast(object); + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + if (keyEvent->key() == Qt::Key_Up || keyEvent->key() == Qt::Key_Home) { + setEditorCursorPosition(editor, 0); + return true; + } + else if (keyEvent->key() == Qt::Key_Down || keyEvent->key() == Qt::Key_End) { + setEditorCursorPosition(editor, -1); + return true; + } + else if (keyEvent->key() == Qt::Key_PageUp || + keyEvent->key() == Qt::Key_PageDown) + { + return true; + } + else if (keyEvent->key() == Qt::Key_Tab || + keyEvent->key() == Qt::Key_Backtab) + { + return true; + } + } + + return QItemDelegate::eventFilter(object, event); +} + +int ContactListViewDelegate::horizontalMargin() const +{ + return horizontalMargin_; +} + +int ContactListViewDelegate::verticalMargin() const +{ + return verticalMargin_; +} + +void ContactListViewDelegate::setHorizontalMargin(int margin) +{ + horizontalMargin_ = margin; +} + +void ContactListViewDelegate::setVerticalMargin(int margin) +{ + verticalMargin_ = margin; +} + +QString ContactListViewDelegate::nameText(const QStyleOptionViewItem& o, const QModelIndex& index) const +{ + Q_UNUSED(o); + return index.data(Qt::DisplayRole).toString(); +} + +QString ContactListViewDelegate::statusText(const QModelIndex& index) const +{ + return index.data(ContactListModel::StatusTextRole).toString(); +} + +XMPP::Status::Type ContactListViewDelegate::statusType(const QModelIndex& index) const +{ + return static_cast(index.data(ContactListModel::StatusTypeRole).toInt()); +} diff --git a/src/contactlistviewdelegate.h b/src/contactlistviewdelegate.h new file mode 100755 index 000000000..596f272a4 --- /dev/null +++ b/src/contactlistviewdelegate.h @@ -0,0 +1,102 @@ +/* + * contactlistviewdelegate.h - base class for painting contact list items + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTLISTVIEWDELEGATE_H +#define CONTACTLISTVIEWDELEGATE_H + +#include + +#include "contactlistmodel.h" + +#include "hoverabletreeview.h" +#include "xmpp_status.h" + +class ContactListView; +class ContactListItemProxy; +class PsiContact; +class ContactListGroup; +class PsiAccount; +class QStyleOptionViewItemV2; + +class ContactListViewDelegate : public QItemDelegate +{ +public: + ContactListViewDelegate(ContactListView* parent); + virtual ~ContactListViewDelegate(); + + virtual int avatarSize() const; + virtual const ContactListView* contactList() const; + + // reimplemented + void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual void getEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index, QRect* widgetRect, QRect* lineEditRect) const; + virtual void setEditorData(QWidget* editor, const QModelIndex& index) const; + virtual void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const; + + virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + + void doSetOptions(const QStyleOptionViewItem& option, const QModelIndex& index) const; + + virtual int horizontalMargin() const; + virtual int verticalMargin() const; + virtual void setHorizontalMargin(int margin); + virtual void setVerticalMargin(int margin); + +protected: + virtual void drawContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual void drawGroup(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual void drawAccount(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + virtual void defaultDraw(QPainter* painter, const QStyleOptionViewItem& option) const; + + virtual void drawText(QPainter* painter, const QStyleOptionViewItem& o, const QRect& rect, const QString& text, const QModelIndex& index) const; + virtual void drawBackground(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + virtual QRect nameRect(const QStyleOptionViewItem& option, const QModelIndex& index) const = 0; + virtual QRect groupNameRect(const QStyleOptionViewItem& option, const QModelIndex& index) const = 0; + virtual QRect editorRect(const QRect& nameRect) const = 0; + virtual void setEditorCursorPosition(QWidget* editor, int cursorPosition) const; + virtual QColor backgroundColor(const QStyleOptionViewItem& option, const QModelIndex& index) const; + + QIcon::Mode iconMode() const; + QIcon::State iconState() const; + const HoverableStyleOptionViewItem& opt() const; + + virtual QString nameText(const QStyleOptionViewItem& o, const QModelIndex& index) const; + virtual QString statusText(const QModelIndex& index) const; + virtual XMPP::Status::Type statusType(const QModelIndex& index) const; + + // reimplemented + bool eventFilter(QObject* object, QEvent* event); + + // these three are functional only inside paint() call + bool hovered() const; + QPoint hoveredPosition() const; + void setHovered(bool hovered) const; + +private: + class Private; + Private* d; + int horizontalMargin_; + int verticalMargin_; +}; + +#endif diff --git a/src/contactupdatesmanager.cpp b/src/contactupdatesmanager.cpp new file mode 100644 index 000000000..9d4a9fd6f --- /dev/null +++ b/src/contactupdatesmanager.cpp @@ -0,0 +1,167 @@ +/* + * contactupdatesmanager.cpp + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "contactupdatesmanager.h" + +#include + +#include "psiaccount.h" +#include "psicontact.h" +#include "psicon.h" +#include "psievent.h" +#include "userlist.h" + +#ifdef YAPSI_ACTIVEX_SERVER +#include "yaonline.h" +#endif + +ContactUpdatesManager::ContactUpdatesManager(PsiCon* parent) + : QObject(parent) + , controller_(parent) +{ + Q_ASSERT(controller_); + updateTimer_ = new QTimer(this); + updateTimer_->setSingleShot(false); + updateTimer_->setInterval(0); + connect(updateTimer_, SIGNAL(timeout()), SLOT(update())); +} + +ContactUpdatesManager::~ContactUpdatesManager() +{ +} + +void ContactUpdatesManager::contactBlocked(PsiAccount* account, const XMPP::Jid& jid) +{ + Q_ASSERT(account); + updates_ << ContactUpdateAction(ContactBlocked, account, jid); + updateTimer_->start(); +} + +void ContactUpdatesManager::contactDeauthorized(PsiAccount* account, const XMPP::Jid& jid) +{ + Q_ASSERT(account); + updates_ << ContactUpdateAction(ContactDeauthorized, account, jid); + updateTimer_->start(); +} + +void ContactUpdatesManager::contactAuthorized(PsiAccount* account, const XMPP::Jid& jid) +{ + Q_ASSERT(account); + updates_ << ContactUpdateAction(ContactAuthorized, account, jid); + updateTimer_->start(); +} + +void ContactUpdatesManager::contactRemoved(PsiAccount* account, const XMPP::Jid& jid) +{ + Q_ASSERT(account); + // we must act immediately, since otherwise all corresponding events + // will be simply deleted + removeAuthRequestEventsFor(account, jid, true); + removeToastersFor(account, jid); +} + +void ContactUpdatesManager::removeAuthRequestEventsFor(PsiAccount* account, const XMPP::Jid& jid, bool denyAuthRequests) +{ + Q_ASSERT(account); + if (!account || !controller_) + return; + + foreach(EventQueue::PsiEventId p, account->eventQueue()->eventsFor(jid, false)) { + PsiEvent* e = p.second; + if (e->type() == PsiEvent::Auth) { + AuthEvent* authEvent = static_cast(e); + if (authEvent->authType() == "subscribe") { + if (denyAuthRequests) { + account->dj_deny(jid); + } +#ifdef YAPSI_ACTIVEX_SERVER + controller_->yaOnline()->closeNotify(p.first, e); +#endif + account->eventQueue()->dequeue(e); + e->deleteLater(); + } + } + } +} + +void ContactUpdatesManager::removeToastersFor(PsiAccount* account, const XMPP::Jid& jid) +{ + Q_ASSERT(account); + if (!account || !controller_) + return; + + foreach(EventQueue::PsiEventId p, account->eventQueue()->eventsFor(jid, false)) { + PsiEvent* e = p.second; + if (e->type() == PsiEvent::Message +#ifdef YAPSI + || e->type() == PsiEvent::Mood +#endif + ) + { +#ifdef YAPSI_ACTIVEX_SERVER + controller_->yaOnline()->closeNotify(p.first, e); +#endif + account->eventQueue()->dequeue(e); + e->deleteLater(); + } + } +} + +void ContactUpdatesManager::removeNotInListContacts(PsiAccount* account, const XMPP::Jid& jid) +{ + Q_ASSERT(account); + if (!account) + return; + + foreach(UserListItem* u, account->findRelevant(jid)) { + if (u && !u->inList()) { + account->actionRemove(u->jid()); + } + } +} + +void ContactUpdatesManager::update() +{ + while (!updates_.isEmpty()) { + ContactUpdateAction action = updates_.takeFirst(); + if (!action.account) + continue; + + if (action.type == ContactBlocked) { + removeAuthRequestEventsFor(action.account, action.jid, true); + removeNotInListContacts(action.account, action.jid); + } + else if (action.type == ContactAuthorized) { + removeAuthRequestEventsFor(action.account, action.jid, false); + } + else if (action.type == ContactDeauthorized) { + removeAuthRequestEventsFor(action.account, action.jid, false); + removeNotInListContacts(action.account, action.jid); + } + else if (action.type == ContactRemoved) { + Q_ASSERT(false); + } + } + + if (updates_.isEmpty()) + updateTimer_->stop(); + else + updateTimer_->start(); +} diff --git a/src/contactupdatesmanager.h b/src/contactupdatesmanager.h new file mode 100644 index 000000000..6215d9b5f --- /dev/null +++ b/src/contactupdatesmanager.h @@ -0,0 +1,74 @@ +/* + * contactupdatesmanager.h + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef CONTACTUPDATESMANAGER_H +#define CONTACTUPDATESMANAGER_H + +#include +#include + +class PsiCon; +class QTimer; + +#include "psiaccount.h" +#include "xmpp_jid.h" + +class ContactUpdatesManager : public QObject +{ + Q_OBJECT +public: + ContactUpdatesManager(PsiCon* parent); + ~ContactUpdatesManager(); + + void contactBlocked(PsiAccount* account, const XMPP::Jid& jid); + void contactDeauthorized(PsiAccount* account, const XMPP::Jid& jid); + void contactAuthorized(PsiAccount* account, const XMPP::Jid& jid); + void contactRemoved(PsiAccount* account, const XMPP::Jid& jid); + +private slots: + void update(); + +private: + PsiCon* controller_; + enum ContactUpdateActionType { + ContactBlocked = 0, + ContactAuthorized, + ContactDeauthorized, + ContactRemoved + }; + struct ContactUpdateAction { + ContactUpdateAction(ContactUpdateActionType _type, PsiAccount* _account, const XMPP::Jid& _jid) + : type(_type) + , account(_account) + , jid(_jid) + {} + ContactUpdateActionType type; + QPointer account; + XMPP::Jid jid; + }; + QList updates_; + QTimer* updateTimer_; + + void removeAuthRequestEventsFor(PsiAccount* account, const XMPP::Jid& jid, bool denyAuthRequests); + void removeToastersFor(PsiAccount* account, const XMPP::Jid& jid); + void removeNotInListContacts(PsiAccount* account, const XMPP::Jid& jid); +}; + +#endif \ No newline at end of file diff --git a/src/contactview.cpp b/src/contactview.cpp index a66ef0efd..49acbda7b 100644 --- a/src/contactview.cpp +++ b/src/contactview.cpp @@ -52,7 +52,6 @@ #include "jidutil.h" #include "psioptions.h" #include "iconaction.h" -#include "pgputil.h" #include "alerticon.h" #include "avatars.h" #include "psiiconset.h" @@ -64,8 +63,10 @@ #include "shortcutmanager.h" #include "xmpp_message.h" #include "textutil.h" -#include "bookmarkmanagedlg.h" #include "bookmarkmanager.h" +#ifdef HAVE_PGPUTIL +#include "pgputil.h" +#endif static inline int rankStatus(int status) { @@ -1047,13 +1048,7 @@ void ContactProfile::doContextMenu(ContactViewItem *i, const QPoint &pos) d->pa->changeStatus(status); } else if(x == bookmarks_start) { - BookmarkManageDlg *dlg = d->pa->findDialog(); - if(dlg) { - bringToFront(dlg); - } else { - dlg = new BookmarkManageDlg(d->pa); - dlg->show(); - } + psiAccount()->actionManageBookmarks(); } else if (x > bookmarks_start) { ConferenceBookmark c = psiAccount()->bookmarkManager()->conferences()[x - bookmarks_start - 1]; @@ -1192,6 +1187,7 @@ void ContactProfile::doContextMenu(ContactViewItem *i, const QPoint &pos) if(!rl.isEmpty()) { for(UserResourceList::ConstIterator it = rl.begin(); it != rl.end(); ++it) { +#ifndef NEWCONTACTLIST const UserResource &r = *it; s2m->addResource(r, base_sendto+at_sendto++); c2m->addResource(r, base_sendto+at_sendto++); @@ -1199,6 +1195,7 @@ void ContactProfile::doContextMenu(ContactViewItem *i, const QPoint &pos) wb2m->addResource(r, base_sendto+at_sendto++); #endif rc2m->addResource(r, base_sendto+at_sendto++); +#endif } } @@ -1250,7 +1247,9 @@ void ContactProfile::doContextMenu(ContactViewItem *i, const QPoint &pos) status = makeSTATUS((*uit).status()); else status = STATUS_OFFLINE; +#ifndef NEWCONTACTLIST cm->addResource(status, *it, base_hidden+at_hidden++); +#endif } pm.insertItem(tr("Active Chats"), cm, 7); if(hc.isEmpty()) @@ -1401,12 +1400,14 @@ void ContactProfile::doContextMenu(ContactViewItem *i, const QPoint &pos) pm.insertItem(tr("&Picture"), avpm); } +#ifdef HAVE_PGPUTIL if(PGPUtil::instance().pgpAvailable() && PsiOptions::instance()->getOption("options.ui.menu.contact.custom-pgp-key").toBool()) { if(u->publicKeyID().isEmpty()) pm.insertItem(IconsetFactory::icon("psi/gpg-yes").icon(), tr("Assign Open&PGP Key"), 21); else pm.insertItem(IconsetFactory::icon("psi/gpg-no").icon(), tr("Unassign Open&PGP Key"), 22); } +#endif d->cv->qa_vcard->addTo( &pm ); diff --git a/src/dummystream.cpp b/src/dummystream.cpp new file mode 100644 index 000000000..0a2e3dab7 --- /dev/null +++ b/src/dummystream.cpp @@ -0,0 +1,23 @@ +/* + * dummystream.cpp - dummy Stream class for saving stanzas to strings + * Copyright (C) 2001-2010 Justin Karneges + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "dummystream.h" + +QDomDocument DummyStream::v_doc; diff --git a/src/dummystream.h b/src/dummystream.h new file mode 100644 index 000000000..e4b3691e0 --- /dev/null +++ b/src/dummystream.h @@ -0,0 +1,48 @@ +/* + * dummystream.h - dummy Stream class for saving stanzas to strings + * Copyright (C) 2001-2010 Justin Karneges + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef DUMMYSTREAM_H +#define DUMMYSTREAM_H + +#include "xmpp_stream.h" +#include "xmpp_stanza.h" + +class DummyStream : public XMPP::Stream +{ +public: + QDomDocument & doc() const { return v_doc; } + QString baseNS() const { return "jabber:client"; } + bool old() const { return false; } + + void close() { } + bool stanzaAvailable() const { return false; } + XMPP::Stanza read() { return XMPP::Stanza(); } + void write(const XMPP::Stanza &) { } + + int errorCondition() const { return 0; } + QString errorText() const { return QString::null; } + QDomElement errorAppSpec() const { return v_doc.documentElement(); } + +private: + static QDomDocument v_doc; +}; + + +#endif diff --git a/src/eventdlg.cpp b/src/eventdlg.cpp index ebb0ac02d..a0f02e8fb 100644 --- a/src/eventdlg.cpp +++ b/src/eventdlg.cpp @@ -55,7 +55,6 @@ #include "msgmle.h" #include "accountscombobox.h" #include "common.h" -#include "pgputil.h" #include "xmpp_htmlelement.h" #include "userlist.h" #include "iconwidget.h" @@ -73,6 +72,9 @@ #include "accountlabel.h" #include "xdata_widget.h" #include "desktoputil.h" +#ifdef HAVE_PGPUTIL +#include "pgputil.h" +#endif #include "psirichtext.h" static QString findJid(const QString &s, int x, int *p1, int *p2) @@ -1015,6 +1017,11 @@ void EventDlg::init() //ShortcutManager::connect("message.send", this, SLOT(doSend())); } +bool EventDlg::messagingEnabled() +{ + return PsiOptions::instance()->getOption("options.ui.message.enabled").toBool(); +} + void EventDlg::setAccount(PsiAccount *pa) { if(d->pa) @@ -1491,12 +1498,12 @@ void EventDlg::doSend() foreach(QString recipient, list) { m.addAddress(Address(XMPP::Address::To, Jid(recipient))); } - d->pa->dj_sendMessage(m); + d->pa->dj_sendMessage(m, false); } else { for(QStringList::ConstIterator it = list.begin(); it != list.end(); ++it) { m.setTo(Jid(*it)); - d->pa->dj_sendMessage(m); + d->pa->dj_sendMessage(m, false); } } doneSend(); @@ -1875,11 +1882,9 @@ void EventDlg::updateEvent(PsiEvent *e) txt = "

" + tr("Subject:") + " " + TextUtil::plain2rich(m.subject()) + "

" + (xhtml? "" : "
") + txt; if (!xhtml) { - if(PsiOptions::instance()->getOption("options.ui.emoticons.use-emoticons").toBool()) - txt = TextUtil::emoticonify(txt); - if( PsiOptions::instance()->getOption("options.ui.chat.legacy-formatting").toBool() ) - txt = TextUtil::legacyFormat(txt); txt = TextUtil::linkify(txt); + txt = TextUtil::emoticonify(txt); + txt = TextUtil::legacyFormat(txt); } if ( e->type() == PsiEvent::HttpAuth ) @@ -2111,6 +2116,7 @@ void EventDlg::trySendEncryptedNext() void EventDlg::encryptedMessageSent(int x, bool b, int e, const QString &dtext) { +#ifdef HAVE_PGPUTIL if(d->transid == -1) return; if(d->transid != x) @@ -2139,6 +2145,9 @@ void EventDlg::encryptedMessageSent(int x, bool b, int e, const QString &dtext) d->le_to->setEnabled(true); d->mle->setEnabled(true); d->mle->setFocus(); +#else + Q_ASSERT(false); +#endif } #include "eventdlg.moc" diff --git a/src/eventdlg.h b/src/eventdlg.h index 67156b4b4..ce1263ead 100644 --- a/src/eventdlg.h +++ b/src/eventdlg.h @@ -119,6 +119,8 @@ class EventDlg : public AdvancedWidget EventDlg(const Jid &, PsiAccount *, bool unique); ~EventDlg(); + static bool messagingEnabled(); + QString text() const; void setHtml(const QString &); void setSubject(const QString &); diff --git a/src/filetransdlg.cpp b/src/filetransdlg.cpp index 05b141225..86ce74377 100644 --- a/src/filetransdlg.cpp +++ b/src/filetransdlg.cpp @@ -1547,7 +1547,7 @@ class FileTransDlg::Private } } - pa->playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.completed-file-transfer").toString()); + pa->playSound(PsiAccount::eFTComplete); } } }; diff --git a/src/globaleventqueue.cpp b/src/globaleventqueue.cpp new file mode 100755 index 000000000..db0c47ee1 --- /dev/null +++ b/src/globaleventqueue.cpp @@ -0,0 +1,83 @@ +/* + * globaleventqueue.cpp - a list of all queued events from enabled accounts + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "globaleventqueue.h" + +#include + +GlobalEventQueue* GlobalEventQueue::instance() +{ + if (!instance_) + instance_ = new GlobalEventQueue(); + return instance_; +} + +int GlobalEventQueue::count() const +{ + return items_.count(); +} + +const QList& GlobalEventQueue::ids() const +{ + return ids_; +} + +PsiEvent *GlobalEventQueue::peek(int id) const +{ + Q_ASSERT(ids_.contains(id)); + foreach(EventItem* item, items_) + if (item->id() == id) + return item->event(); + return 0; +} + +void GlobalEventQueue::enqueue(EventItem* item) +{ + Q_ASSERT(item); + Q_ASSERT(!ids_.contains(item->id())); + if (!item || ids_.contains(item->id())) + return; + + ids_.append(item->id()); + items_.append(item); + Q_ASSERT(ids_.contains(item->id())); + + emit queueChanged(); +} + +void GlobalEventQueue::dequeue(EventItem* item) +{ + Q_ASSERT(item); + Q_ASSERT(ids_.contains(item->id())); + if (!item || !ids_.contains(item->id())) + return; + + ids_.removeAll(item->id()); + items_.removeAll(item); + Q_ASSERT(!ids_.contains(item->id())); + + emit queueChanged(); +} + +GlobalEventQueue::GlobalEventQueue() + : QObject(QCoreApplication::instance()) +{} + +GlobalEventQueue* GlobalEventQueue::instance_ = NULL; diff --git a/src/globaleventqueue.h b/src/globaleventqueue.h new file mode 100755 index 000000000..aac08d4ca --- /dev/null +++ b/src/globaleventqueue.h @@ -0,0 +1,56 @@ +/* + * globaleventqueue.h - a list of all queued events from enabled accounts + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef GLOBALEVENTQUEUE_H +#define GLOBALEVENTQUEUE_H + +#include + +#include "psievent.h" + +class GlobalEventQueue : public QObject +{ + Q_OBJECT + +public: + static GlobalEventQueue* instance(); + + int count() const; + + const QList& ids() const; + PsiEvent *peek(int id) const; + +protected: + void enqueue(EventItem* item); + void dequeue(EventItem* item); + +signals: + void queueChanged(); + +private: + GlobalEventQueue(); + + static GlobalEventQueue* instance_; + QList ids_; + QList items_; + friend class EventQueue; +}; + +#endif diff --git a/src/groupchatdlg.cpp b/src/groupchatdlg.cpp index f48c17e5d..74dd7c22c 100644 --- a/src/groupchatdlg.cpp +++ b/src/groupchatdlg.cpp @@ -1452,11 +1452,11 @@ void GCMainDlg::message(const Message &_m) // play sound? if(from == d->self) { if(!m.spooled()) - account()->playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.outgoing-chat").toString()); + account()->playSound(PsiAccount::eSend); } else { if(alert || (PsiOptions::instance()->getOption("options.ui.notifications.sounds.notify-every-muc-message").toBool() && !m.spooled() && !from.isEmpty()) ) - account()->playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.chat-message").toString()); + account()->playSound(PsiAccount::eChat2); } if(from.isEmpty()) diff --git a/src/hoverabletreeview.cpp b/src/hoverabletreeview.cpp new file mode 100644 index 000000000..7ff937f3a --- /dev/null +++ b/src/hoverabletreeview.cpp @@ -0,0 +1,154 @@ +/* + * hoverabletreeview.cpp - QTreeView that allows to show hovered items apart + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "hoverabletreeview.h" + +#include +#include + +#ifdef Q_WS_WIN +#include +#include + +// taken from qapplication_win.cpp +#if (_WIN32_WINNT < 0x0400) +// This struct is defined in winuser.h if the _WIN32_WINNT >= 0x0400 -- in the +// other cases we have to define it on our own. +typedef struct tagTRACKMOUSEEVENT { + DWORD cbSize; + DWORD dwFlags; + HWND hwndTrack; + DWORD dwHoverTime; +} TRACKMOUSEEVENT, *LPTRACKMOUSEEVENT; +#endif +#endif + +//---------------------------------------------------------------------------- +// HoverableStyleOptionViewItem +//---------------------------------------------------------------------------- + +HoverableStyleOptionViewItem::HoverableStyleOptionViewItem() + : HoverableStyleOptionViewItemBaseClass(Version) + , hovered(false) +{ +} + +HoverableStyleOptionViewItem::HoverableStyleOptionViewItem(const QStyleOptionViewItem& other) + : HoverableStyleOptionViewItemBaseClass(Version) +{ + (void)HoverableStyleOptionViewItem::operator=(other); +} + +HoverableStyleOptionViewItem &HoverableStyleOptionViewItem::operator=(const QStyleOptionViewItem& other) +{ + HoverableStyleOptionViewItemBaseClass::operator=(other); + const HoverableStyleOptionViewItem* hoverable = qstyleoption_cast(&other); + this->hovered = hoverable ? hoverable->hovered : false; + this->hoveredPosition = hoverable ? hoverable->hoveredPosition : QPoint(); + return *this; +} + +HoverableStyleOptionViewItem::HoverableStyleOptionViewItem(int version) + : HoverableStyleOptionViewItemBaseClass(version) +{ +} + +//---------------------------------------------------------------------------- +// HoverableTreeView +//---------------------------------------------------------------------------- + +HoverableTreeView::HoverableTreeView(QWidget* parent) + : QTreeView(parent) +{ +#if QT_VERSION >= 0x040400 + setAutoScrollMargin(50); +#endif + setMouseTracking(true); + viewport()->setMouseTracking(true); +} + +void HoverableTreeView::mouseMoveEvent(QMouseEvent* event) +{ + mousePosition_ = event->pos(); + QTreeView::mouseMoveEvent(event); + + // required for accurate hovered sub-regions painting + viewport()->update(); +} + +void HoverableTreeView::startDrag(Qt::DropActions supportedActions) +{ + mousePosition_ = QPoint(); + QTreeView::startDrag(supportedActions); +} + +void HoverableTreeView::drawRow(QPainter* painter, const QStyleOptionViewItem& options, const QModelIndex& index) const +{ + HoverableStyleOptionViewItem opt = options; + + QRect r = visualRect(index).adjusted(0, 1, 0, 1); + r.setLeft(0); + + opt.decorationSize = QSize(); + if (underMouse() && + !mousePosition_.isNull() && + r.contains(mousePosition_) && + !verticalScrollBar()->underMouse() && + !verticalScrollBar()->isSliderDown()) + { + // we're hacking our way through the QTreeView which casts HoverableStyleOptionViewItem + // down to HoverableStyleOptionViewItemBaseClass and loses the hovered flag + opt.displayAlignment = Qt::AlignLeft | Qt::AlignRight | Qt::AlignHCenter | Qt::AlignJustify; + + // decorationSize will hold the mouse coordinates starting from opt.rect.topLeft() + QPoint offset = mousePosition_ /*- QPoint(opt.rect.left(), 0)*/; + opt.decorationSize = QSize(offset.x(), offset.y()); + + opt.hovered = true; + opt.hoveredPosition = QPoint(offset.x(), offset.y()); + } + QTreeView::drawRow(painter, opt, index); +} + +void HoverableTreeView::repairMouseTracking() +{ +#ifdef Q_WS_WIN + // work-around for broken mouse event tracking after context menu gets hidden on Qt 4.3.5 + + // copied from qapplication_win.cpp + typedef BOOL (WINAPI *PtrTrackMouseEvent)(LPTRACKMOUSEEVENT); + static PtrTrackMouseEvent ptrTrackMouseEvent = 0; + ptrTrackMouseEvent = (PtrTrackMouseEvent)QLibrary::resolve(QLatin1String("comctl32"), "_TrackMouseEvent"); + + if (ptrTrackMouseEvent) { + TRACKMOUSEEVENT tme; + tme.cbSize = sizeof(TRACKMOUSEEVENT); + tme.dwFlags = 0x00000002; // TME_LEAVE + tme.hwndTrack = viewport()->winId(); // Track on window receiving msgs + tme.dwHoverTime = (DWORD)-1; // HOVER_DEFAULT + ptrTrackMouseEvent(&tme); + } +#endif +} + +QPoint HoverableTreeView::mousePosition() const +{ + return mousePosition_; +} diff --git a/src/hoverabletreeview.h b/src/hoverabletreeview.h new file mode 100644 index 000000000..bd33d5ba4 --- /dev/null +++ b/src/hoverabletreeview.h @@ -0,0 +1,79 @@ +/* + * hoverabletreeview.h - QTreeView that allows to show hovered items apart + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef HOVERABLETREEVIEW_H +#define HOVERABLETREEVIEW_H + +#include +#include + +#if QT_VERSION >= 0x040400 +typedef QStyleOptionViewItemV4 HoverableStyleOptionViewItemBaseClass; +#else +typedef QStyleOptionViewItemV3 HoverableStyleOptionViewItemBaseClass; +#endif + +class HoverableStyleOptionViewItem : public HoverableStyleOptionViewItemBaseClass +{ +public: + enum StyleOptionVersion { Version = HoverableStyleOptionViewItemBaseClass::Version+1 }; + + bool hovered; + QPoint hoveredPosition; + + HoverableStyleOptionViewItem(); + HoverableStyleOptionViewItem(const HoverableStyleOptionViewItem &other) + : HoverableStyleOptionViewItemBaseClass(Version) + { + *this = other; + } + HoverableStyleOptionViewItem(const QStyleOptionViewItem &other); + HoverableStyleOptionViewItem &operator = (const QStyleOptionViewItem &other); + +protected: + HoverableStyleOptionViewItem(int version); +}; + +class HoverableTreeView : public QTreeView +{ + Q_OBJECT + +public: + HoverableTreeView(QWidget* parent = 0); + + enum HoverableItemFeature { + Hovered = 0x8000 + }; + + void repairMouseTracking(); + +protected: + // reimplemented + void mouseMoveEvent(QMouseEvent* event); + void drawRow(QPainter* painter, const QStyleOptionViewItem& options, const QModelIndex& index) const; + void startDrag(Qt::DropActions supportedActions); + + QPoint mousePosition() const; + +private: + QPoint mousePosition_; +}; + +#endif diff --git a/src/legacypsiaccount.cpp b/src/legacypsiaccount.cpp new file mode 100644 index 000000000..3ce927228 --- /dev/null +++ b/src/legacypsiaccount.cpp @@ -0,0 +1,138 @@ +/* + * legacypsiaccount.h - PsiAccount with ContactProfile compatibility + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "legacypsiaccount.h" + +#include "psicon.h" +#include "contactview.h" + +LegacyPsiAccount::LegacyPsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegistry* capsRegistry, TabManager *tabManager) + : PsiAccount(acc, parent, capsRegistry, tabManager) + , cp_(0) +{ +} + +LegacyPsiAccount::~LegacyPsiAccount() +{ + delete cp_; +} + +void LegacyPsiAccount::init() +{ + cp_ = new ContactProfile(this, name(), psi()->contactView()); + connect(cp_, SIGNAL(actionDefault(const Jid &)),SLOT(actionDefault(const Jid &))); + connect(cp_, SIGNAL(actionRecvEvent(const Jid &)),SLOT(actionRecvEvent(const Jid &))); + connect(cp_, SIGNAL(actionSendMessage(const Jid &)),SLOT(actionSendMessage(const Jid &))); + connect(cp_, SIGNAL(actionSendMessage(const QList &)),SLOT(actionSendMessage(const QList &))); + connect(cp_, SIGNAL(actionSendUrl(const Jid &)),SLOT(actionSendUrl(const Jid &))); + connect(cp_, SIGNAL(actionRemove(const Jid &)),SLOT(actionRemove(const Jid &))); + connect(cp_, SIGNAL(actionRename(const Jid &, const QString &)),SLOT(actionRename(const Jid &, const QString &))); + connect(cp_, SIGNAL(actionGroupRename(const QString &, const QString &)),SLOT(actionGroupRename(const QString &, const QString &))); + connect(cp_, SIGNAL(actionHistory(const Jid &)),SLOT(actionHistory(const Jid &))); + connect(cp_, SIGNAL(actionOpenChat(const Jid &)),SLOT(actionOpenChat(const Jid &))); + connect(cp_, SIGNAL(actionOpenChatSpecific(const Jid &)),SLOT(actionOpenChatSpecific(const Jid &))); +#ifdef WHITEBOARDING + connect(cp_, SIGNAL(actionOpenWhiteboard(const Jid &)),SLOT(actionOpenWhiteboard(const Jid &))); + connect(cp_, SIGNAL(actionOpenWhiteboardSpecific(const Jid &)),SLOT(actionOpenWhiteboardSpecific(const Jid &))); +#endif + connect(cp_, SIGNAL(actionAgentSetStatus(const Jid &, Status &)),SLOT(actionAgentSetStatus(const Jid &, Status &))); + connect(cp_, SIGNAL(actionInfo(const Jid &)),SLOT(actionInfo(const Jid &))); + connect(cp_, SIGNAL(actionAuth(const Jid &)),SLOT(actionAuth(const Jid &))); + connect(cp_, SIGNAL(actionAuthRequest(const Jid &)),SLOT(actionAuthRequest(const Jid &))); + connect(cp_, SIGNAL(actionAuthRemove(const Jid &)),SLOT(actionAuthRemove(const Jid &))); + connect(cp_, SIGNAL(actionAdd(const Jid &)),SLOT(actionAdd(const Jid &))); + connect(cp_, SIGNAL(actionGroupAdd(const Jid &, const QString &)),SLOT(actionGroupAdd(const Jid &, const QString &))); + connect(cp_, SIGNAL(actionGroupRemove(const Jid &, const QString &)),SLOT(actionGroupRemove(const Jid &, const QString &))); + connect(cp_, SIGNAL(actionVoice(const Jid &)),SLOT(actionVoice(const Jid &))); + connect(cp_, SIGNAL(actionSendFile(const Jid &)),SLOT(actionSendFile(const Jid &))); + connect(cp_, SIGNAL(actionSendFiles(const Jid &, const QStringList&)),SLOT(actionSendFiles(const Jid &, const QStringList&))); + connect(cp_, SIGNAL(actionExecuteCommand(const Jid &, const QString&)),SLOT(actionExecuteCommand(const Jid &, const QString&))); + connect(cp_, SIGNAL(actionExecuteCommandSpecific(const Jid &, const QString&)),SLOT(actionExecuteCommandSpecific(const Jid &, const QString&))); + connect(cp_, SIGNAL(actionSetMood()),SLOT(actionSetMood())); + connect(cp_, SIGNAL(actionSetAvatar()),SLOT(actionSetAvatar())); + connect(cp_, SIGNAL(actionUnsetAvatar()),SLOT(actionUnsetAvatar())); + connect(cp_, SIGNAL(actionDisco(const Jid &, const QString &)),SLOT(actionDisco(const Jid &, const QString &))); + connect(cp_, SIGNAL(actionInvite(const Jid &, const QString &)),SLOT(actionInvite(const Jid &, const QString &))); + connect(cp_, SIGNAL(actionAssignKey(const Jid &)),SLOT(actionAssignKey(const Jid &))); + connect(cp_, SIGNAL(actionUnassignKey(const Jid &)),SLOT(actionUnassignKey(const Jid &))); +} + +ContactProfile* LegacyPsiAccount::contactProfile() const +{ + return cp_; +} + +void LegacyPsiAccount::setUserAccount(const UserAccount &acc) +{ + PsiAccount::setUserAccount(acc); + cp_->setName(name()); +} + +void LegacyPsiAccount::stateChanged() +{ + PsiAccount::stateChanged(); + + if(loggedIn()) { + cp_->setState(makeSTATUS(status())); + } + else { + if(isActive()) { + cp_->setState(-1); + if(usingSSL()) + cp_->setUsingSSL(true); + else + cp_->setUsingSSL(false); + } + else { + cp_->setState(STATUS_OFFLINE); + cp_->setUsingSSL(false); + } + } +} + +void LegacyPsiAccount::profileUpdateEntry(const UserListItem& u) +{ + PsiAccount::profileUpdateEntry(u); + cp_->updateEntry(u); +} + +void LegacyPsiAccount::profileRemoveEntry(const Jid& jid) +{ + PsiAccount::profileRemoveEntry(jid); + cp_->removeEntry(jid); +} + +void LegacyPsiAccount::profileAnimateNick(const Jid& jid) +{ + PsiAccount::profileAnimateNick(jid); + cp_->animateNick(jid); +} + +void LegacyPsiAccount::profileSetAlert(const Jid& jid, const PsiIcon* icon) +{ + PsiAccount::profileSetAlert(jid, icon); + cp_->setAlert(jid, icon); +} + +void LegacyPsiAccount::profileClearAlert(const Jid& jid) +{ + PsiAccount::profileClearAlert(jid); + cp_->clearAlert(jid); +} diff --git a/src/legacypsiaccount.h b/src/legacypsiaccount.h new file mode 100644 index 000000000..7686d67eb --- /dev/null +++ b/src/legacypsiaccount.h @@ -0,0 +1,50 @@ +/* + * legacypsiaccount.h - PsiAccount with ContactProfile compatibility + * Copyright (C) 2009-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef LEGACYPSIACCOUNT_H +#define LEGACYPSIACCOUNT_H + +#include "psiaccount.h" + +class LegacyPsiAccount : public PsiAccount +{ + Q_OBJECT +public: + LegacyPsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegistry* capsRegistry, TabManager *tabManager); + ~LegacyPsiAccount(); + virtual void init(); + + // reimplemented + virtual ContactProfile* contactProfile() const; + virtual void setUserAccount(const UserAccount &); + virtual void stateChanged(); + + // reimplemented + virtual void profileUpdateEntry(const UserListItem& u); + virtual void profileRemoveEntry(const Jid& jid); + virtual void profileAnimateNick(const Jid& jid); + virtual void profileSetAlert(const Jid& jid, const PsiIcon* icon); + virtual void profileClearAlert(const Jid& jid); + +private: + ContactProfile* cp_; +}; + +#endif diff --git a/src/mainwin.cpp b/src/mainwin.cpp index 676ea03dc..3d5d8afba 100644 --- a/src/mainwin.cpp +++ b/src/mainwin.cpp @@ -45,7 +45,9 @@ #include "common.h" #include "showtextdlg.h" #include "psicon.h" +#ifndef NEWCONTACTLIST #include "contactview.h" +#endif #include "psiiconset.h" #include "serverinfomanager.h" #include "applicationinfo.h" @@ -60,6 +62,18 @@ #include "mucjoindlg.h" #include "psicontactlist.h" #include "desktoputil.h" +#ifdef NEWCONTACTLIST +#include "psicontactlistmodel.h" +#include "psicontactlistview.h" +#include "contactlistproxymodel.h" +#include "psicontact.h" +#include "contactlistitemproxy.h" +#include "contactlistgroup.h" +#ifdef MODELTEST +#include "modeltest.h" +#endif +#endif +#include "contactlistutil.h" #include "mainwin_p.h" @@ -68,6 +82,14 @@ using namespace XMPP; +#ifdef NEWCONTACTLIST +static const QString showOfflineOptionPath = "options.ui.contactlist.show.offline-contacts"; +static const QString showHiddenOptionPath = "options.ui.contactlist.show.hidden-contacts-group"; +static const QString showAgentsOptionPath = "options.ui.contactlist.show.agent-contacts"; +static const QString showSelfOptionPath = "options.ui.contactlist.show.self-contact"; +#endif +static const QString showStatusMessagesOptionPath = "options.ui.contactlist.status-messages.show"; + // FIXME: this is a really corny way of getting the GStreamer version... QString extract_gst_version(const QString &in) { @@ -120,6 +142,11 @@ class MainWin::Private int lastStatus; bool filterActive, prefilterShowOffline, prefilterShowAway; +#ifdef NEWCONTACTLIST + PsiContactListView* contactListView_; + PsiContactListModel* contactListModel_; +#endif + void registerActions(); IconAction* getAction( QString name ); void updateMenu(QStringList actions, QMenu* menu); @@ -271,12 +298,49 @@ MainWin::MainWin(bool _onTop, bool _asTool, PsiCon* psi) setCentralWidget ( center ); d->vb_main = new QVBoxLayout(center); +#ifndef NEWCONTACTLIST cvlist = new ContactView(center); +#else + connect(PsiOptions::instance(), SIGNAL(optionChanged(const QString&)), SLOT(optionChanged(const QString&))); + + connect(psiCon()->contactList(), SIGNAL(showAgentsChanged(bool)), SLOT(showAgentsChanged(bool))); + connect(psiCon()->contactList(), SIGNAL(showHiddenChanged(bool)), SLOT(showHiddenChanged(bool))); + connect(psiCon()->contactList(), SIGNAL(showSelfChanged(bool)), SLOT(showSelfChanged(bool))); + connect(psiCon()->contactList(), SIGNAL(showOfflineChanged(bool)), SLOT(showOfflineChanged(bool))); + optionChanged(showAgentsOptionPath); + optionChanged(showHiddenOptionPath); + optionChanged(showSelfOptionPath); + optionChanged(showOfflineOptionPath); + + d->contactListModel_ = new PsiContactListModel(psiCon()->contactList()); + d->contactListModel_->invalidateLayout(); + d->contactListModel_->setGroupsEnabled(true); + d->contactListModel_->setAccountsEnabled(true); + d->contactListModel_->storeGroupState("contacts"); +#ifdef MODELTEST + new ModelTest(d->contactListModel_, this); +#endif + + ContactListProxyModel* contactListProxyModel = new ContactListProxyModel(this); + contactListProxyModel->setSourceModel(d->contactListModel_); +#ifdef MODELTEST + new ModelTest(contactListProxyModel, this); +#endif + + d->contactListView_ = new PsiContactListView(center); + d->contactListView_->setModel(contactListProxyModel); + connect(d->contactListView_, SIGNAL(removeSelection(QMimeData*)), SLOT(removeSelection(QMimeData*))); + connect(d->contactListView_, SIGNAL(removeGroupWithoutContacts(QMimeData*)), SLOT(removeGroupWithoutContacts(QMimeData*))); +#endif int layoutMargin = 2; #ifdef Q_WS_MAC layoutMargin = 0; +#ifndef NEWCONTACTLIST cvlist->setFrameShape(QFrame::NoFrame); +#else + d->contactListView_->setFrameShape(QFrame::NoFrame); +#endif #endif d->vb_main->setMargin(layoutMargin); d->vb_main->setSpacing(layoutMargin); @@ -293,11 +357,17 @@ MainWin::MainWin(bool _onTop, bool _asTool, PsiCon* psi) d->searchPb = new QToolButton(d->searchWidget); d->searchPb->setText("X"); connect(d->searchPb,SIGNAL(clicked()),SLOT(searchClearClicked())); +#ifndef NEWCONTACTLIST connect(cvlist, SIGNAL(searchInput(const QString&)), SLOT(searchTextStarted(const QString&))); +#endif searchLayout->addWidget(d->searchPb); d->searchWidget->setVisible(false); //add contact view +#ifndef NEWCONTACTLIST d->vb_main->addWidget(cvlist); +#else + d->vb_main->addWidget(d->contactListView_); +#endif #ifdef Q_WS_MAC // Disable the empty vertical scrollbar: @@ -357,9 +427,9 @@ MainWin::MainWin(bool _onTop, bool _asTool, PsiCon* psi) QMenu* viewMenu = new QMenu(tr("View"), this); mainMenuBar()->addMenu(viewMenu); d->getAction("show_offline")->addTo(viewMenu); - if (PsiOptions::instance()->getOption("options.ui.menu.view.show-away").toBool()) { - d->getAction("show_away")->addTo(viewMenu); - } + // if (PsiOptions::instance()->getOption("options.ui.menu.view.show-away").toBool()) { + // d->getAction("show_away")->addTo(viewMenu); + // } d->getAction("show_hidden")->addTo(viewMenu); d->getAction("show_agents")->addTo(viewMenu); d->getAction("show_self")->addTo(viewMenu); @@ -432,18 +502,29 @@ void MainWin::registerAction( IconAction* action ) const char *toggled = SIGNAL( toggled(bool) ); const char *setChecked = SLOT( setChecked(bool) ); + PsiContactList* contactList = psiCon()->contactList(); + struct { const char* name; const char* signal; QObject* receiver; const char* slot; } actionlist[] = { +#ifndef NEWCONTACTLIST { "show_offline", toggled, cvlist, SLOT( setShowOffline(bool) ) }, { "show_away", toggled, cvlist, SLOT( setShowAway(bool) ) }, { "show_hidden", toggled, cvlist, SLOT( setShowHidden(bool) ) }, { "show_agents", toggled, cvlist, SLOT( setShowAgents(bool) ) }, { "show_self", toggled, cvlist, SLOT( setShowSelf(bool) ) }, { "show_statusmsg", toggled, cvlist, SLOT( setShowStatusMsg(bool) ) }, +#else + { "show_offline", toggled, contactList, SLOT( setShowOffline(bool) ) }, + // { "show_away", toggled, contactList, SLOT( setShowAway(bool) ) }, + { "show_hidden", toggled, contactList, SLOT( setShowHidden(bool) ) }, + { "show_agents", toggled, contactList, SLOT( setShowAgents(bool) ) }, + { "show_self", toggled, contactList, SLOT( setShowSelf(bool) ) }, + { "show_statusmsg", toggled, this, SLOT( setShowStatusMsg(bool) ) }, +#endif { "button_options", activated, this, SIGNAL( doOptions() ) }, @@ -452,7 +533,9 @@ void MainWin::registerAction( IconAction* action ) { "menu_xml_console", SIGNAL( activated(PsiAccount *, int) ), this, SLOT( activatedAccOption(PsiAccount*, int) ) }, { "menu_new_message", activated, this, SIGNAL( blankMessage() ) }, +#ifdef GROUPCHAT { "menu_join_groupchat", activated, this, SIGNAL( doGroupChat() ) }, +#endif { "menu_account_setup", activated, this, SIGNAL( doManageAccounts() ) }, { "menu_options", activated, this, SIGNAL( doOptions() ) }, { "menu_file_transfer", activated, this, SIGNAL( doFileTransDlg() ) }, @@ -514,12 +597,21 @@ void MainWin::registerAction( IconAction* action ) const char* slot; bool checked; } reverseactionlist[] = { +#ifndef NEWCONTACTLIST { "show_away", cvlist, SIGNAL( showAway(bool) ), setChecked, cvlist->isShowAway()}, { "show_hidden", cvlist, SIGNAL( showHidden(bool) ), setChecked, cvlist->isShowHidden()}, { "show_offline", cvlist, SIGNAL( showOffline(bool) ), setChecked, cvlist->isShowOffline()}, { "show_self", cvlist, SIGNAL( showSelf(bool) ), setChecked, cvlist->isShowSelf()}, { "show_agents", cvlist, SIGNAL( showAgents(bool) ), setChecked, cvlist->isShowAgents()}, { "show_statusmsg", cvlist, SIGNAL( showStatusMsg(bool) ), setChecked, cvlist->isShowStatusMsg()}, +#else + // { "show_away", contactList, SIGNAL(showAwayChanged(bool)), setChecked, contactList->showAway()}, + { "show_hidden", contactList, SIGNAL(showHiddenChanged(bool)), setChecked, contactList->showHidden()}, + { "show_offline", contactList, SIGNAL(showOfflineChanged(bool)), setChecked, contactList->showOffline()}, + { "show_self", contactList, SIGNAL(showSelfChanged(bool)), setChecked, contactList->showSelf()}, + { "show_agents", contactList, SIGNAL(showAgentsChanged(bool)), setChecked, contactList->showAgents()}, + // { "show_statusmsg", contactList, SIGNAL(showStatusMsgChanged(bool)), setChecked, contactList->showStatusMsg()}, +#endif { "", 0, 0, 0, false } }; @@ -529,7 +621,7 @@ void MainWin::registerAction( IconAction* action ) connect( reverseactionlist[i].sender, reverseactionlist[i].signal, action, reverseactionlist[i].slot ); if (aName == "show_statusmsg") { - action->setChecked( PsiOptions::instance()->getOption("options.ui.contactlist.status-messages.show").toBool() ); + action->setChecked( PsiOptions::instance()->getOption(showStatusMessagesOptionPath).toBool() ); } else action->setChecked( reverseactionlist[i].checked ); @@ -727,7 +819,9 @@ void MainWin::buildMainMenu() actions << "menu_new_message"; } actions << "menu_disco" +#ifdef GROUPCHAT << "menu_join_groupchat" +#endif << "separator" << "menu_account_setup"; if (PsiOptions::instance()->getOption("options.ui.menu.main.change-profile").toBool()) { @@ -757,7 +851,9 @@ void MainWin::buildGeneralMenu(QMenu* menu) actions << "menu_new_message"; } actions << "menu_disco" +#ifdef GROUPCHAT << "menu_join_groupchat" +#endif << "menu_account_setup" << "menu_options" << "menu_file_transfer"; @@ -1126,7 +1222,7 @@ void MainWin::toggleVisible() } } -void MainWin::setTrayToolTip(const Status& status, bool) +void MainWin::setTrayToolTip(const Status& status, bool, bool) { if (!d->tray) { return; @@ -1304,7 +1400,9 @@ void MainWin::searchClearClicked() { d->searchWidget->setVisible(false); d->searchText->clear(); +#ifndef NEWCONTACTLIST cvlist->clearFilter(); +#endif if (d->filterActive) { d->getAction("show_offline")->setChecked(d->prefilterShowOffline); @@ -1342,8 +1440,9 @@ void MainWin::searchTextEntered(QString const& text) if (text.isEmpty()) { searchClearClicked(); } else { - +#ifndef NEWCONTACTLIST cvlist->setFilter(text); +#endif } } @@ -1358,6 +1457,86 @@ void MainWin::setWindowIcon(const QPixmap& p) } #endif +#ifdef NEWCONTACTLIST +void MainWin::removeSelection(QMimeData* selection) +{ + ContactListUtil::removeContact(0, selection, d->contactListModel_, this, this); +} + +void MainWin::removeContactConfirmation(const QString& id, bool confirmed) +{ + ContactListUtil::removeContactConfirmation(id, confirmed, d->contactListModel_, d->contactListView_); +} + +void MainWin::removeGroupWithoutContacts(QMimeData* selection) +{ + int n = QMessageBox::information(d->contactListView_, tr("Remove Group"), + tr("This will cause all contacts in this group to be disassociated with it.\n" + "\n" + "Proceed?"), + tr("&Yes"), tr("&No")); + + if (n == 0) { + QModelIndexList indexes = d->contactListModel_->indexesFor(0, selection); + Q_ASSERT(indexes.count() == 1); + Q_ASSERT(d->contactListModel_->indexType(indexes.first()) == ContactListModel::GroupType); + if (indexes.count() != 1) + return; + ContactListItemProxy* proxy = d->contactListModel_->modelIndexToItemProxy(indexes.first()); + ContactListGroup* group = proxy ? dynamic_cast(proxy->item()) : 0; + if (!group) + return; + + QList contacts = group->contacts(); + foreach(PsiContact* c, group->contacts()) { + c->account()->actionGroupRemove(c->jid(), group->name()); + } + } +} + +void MainWin::optionChanged(const QString& option) +{ + PsiContactList* contactList = psiCon()->contactList(); + if (option == showAgentsOptionPath && contactList) { + contactList->setShowAgents(PsiOptions::instance()->getOption(showAgentsOptionPath).toBool()); + } + else if (option == showHiddenOptionPath && contactList) { + contactList->setShowHidden(PsiOptions::instance()->getOption(showHiddenOptionPath).toBool()); + } + else if (option == showSelfOptionPath && contactList) { + contactList->setShowSelf(PsiOptions::instance()->getOption(showSelfOptionPath).toBool()); + } + else if (option == showOfflineOptionPath && contactList) { + contactList->setShowOffline(PsiOptions::instance()->getOption(showOfflineOptionPath).toBool()); + } +} + +void MainWin::showAgentsChanged(bool enabled) +{ + PsiOptions::instance()->setOption(showAgentsOptionPath, enabled); +} + +void MainWin::showHiddenChanged(bool enabled) +{ + PsiOptions::instance()->setOption(showHiddenOptionPath, enabled); +} + +void MainWin::showSelfChanged(bool enabled) +{ + PsiOptions::instance()->setOption(showSelfOptionPath, enabled); +} + +void MainWin::showOfflineChanged(bool enabled) +{ + PsiOptions::instance()->setOption(showOfflineOptionPath, enabled); +} + +void MainWin::setShowStatusMsg(bool enabled) +{ + PsiOptions::instance()->setOption(showStatusMessagesOptionPath, enabled); +} +#endif + #if 0 #if defined(Q_WS_WIN) #include diff --git a/src/mainwin.h b/src/mainwin.h index d2adbaa11..9a98f81bb 100644 --- a/src/mainwin.h +++ b/src/mainwin.h @@ -34,6 +34,7 @@ class QAction; class QPixmap; class QPoint; class QMenu; +class QMimeData; class PsiCon; class PsiToolBar; @@ -61,7 +62,9 @@ class MainWin : public AdvancedWidget QStringList actionList; QMap actions; +#ifndef NEWCONTACTLIST ContactView *cvlist; +#endif PsiCon *psiCon() const; @@ -137,6 +140,19 @@ private slots: void registerAction( IconAction * ); +#ifdef NEWCONTACTLIST + void removeSelection(QMimeData* selection); + void removeGroupWithoutContacts(QMimeData* selection); + void optionChanged(const QString& option); + void showAgentsChanged(bool); + void showHiddenChanged(bool); + void showSelfChanged(bool); + void showOfflineChanged(bool); + void setShowStatusMsg(bool); + + void removeContactConfirmation(const QString& id, bool confirmed); +#endif + public slots: void setWindowIcon(const QPixmap&); void showNoFocus(); @@ -145,7 +161,7 @@ public slots: void updateReadNext(PsiIcon *nextAnim, int nextAmount); void optionsUpdate(); - void setTrayToolTip(const XMPP::Status &, bool usePriority = false); + void setTrayToolTip(const XMPP::Status &, bool usePriority = false, bool isManualStatus = false); void toggleVisible(); diff --git a/src/miniclient.cpp b/src/miniclient.cpp index d5a80b892..0bbf2ec91 100644 --- a/src/miniclient.cpp +++ b/src/miniclient.cpp @@ -240,8 +240,11 @@ void MiniClient::cs_error(int err) { QString str; bool reconn; + bool disableAutoConnect; + bool isAuthError; + bool isTemporaryAuthFailure; - PsiAccount::getErrorInfo(err, conn, stream, tlsHandler, &str, &reconn); + PsiAccount::getErrorInfo(err, conn, stream, tlsHandler, &str, &reconn, &disableAutoConnect, &isAuthError, &isTemporaryAuthFailure); close(); QMessageBox::critical(0, tr("Server Error"), tr("There was an error communicating with the Jabber server.\nDetails: %1").arg(str)); diff --git a/src/profiles.h b/src/profiles.h index 83bd3e4d4..576e9ba69 100644 --- a/src/profiles.h +++ b/src/profiles.h @@ -49,6 +49,7 @@ class UserAccount void fromOptions(OptionsTree *o, QString base); void toOptions(OptionsTree *o, QString base=QString()); + QString id; QString name; QString jid, pass, host, resource, authid, realm; bool customAuth; diff --git a/src/psi_profiles.cpp b/src/psi_profiles.cpp index 2773b1bdc..8bca61a02 100644 --- a/src/psi_profiles.cpp +++ b/src/psi_profiles.cpp @@ -30,10 +30,11 @@ #include #include #include +#include +#include #include "eventdlg.h" #include "chatdlg.h" -#include "pgputil.h" #include "xmpp_xmlcommon.h" #include "fancylabel.h" #include "advwidget.h" @@ -42,6 +43,9 @@ #include "atomicxmlfile.h" #include "psitoolbar.h" #include "optionstree.h" +#ifdef HAVE_PGPUTIL +#include "pgputil.h" +#endif using namespace XMPP; using namespace XMLHelper; @@ -107,6 +111,7 @@ UserAccount::UserAccount() void UserAccount::reset() { + id = QUuid::createUuid().toString(); name = "Default"; opt_enabled = true; opt_auto = false; @@ -185,7 +190,11 @@ void UserAccount::fromOptions(OptionsTree *o, QString base) else { o->setOption(base + ".connect-after-sleep", opt_connectAfterSleep); } - + + QString tmpId = o->getOption(base + ".id").toString(); + if (!tmpId.isEmpty()) { + id = tmpId; + } name = o->getOption(base + ".name").toString(); jid = o->getOption(base + ".jid").toString(); @@ -221,14 +230,16 @@ void UserAccount::fromOptions(OptionsTree *o, QString base) resource = o->getOption(base + ".resource").toString(); priority = o->getOption(base + ".priority").toInt(); - + +#ifdef HAVE_PGPUTIL QString pgpSecretKeyID = o->getOption(base + ".pgp-secret-key-id").toString(); if (!pgpSecretKeyID.isEmpty()) { QCA::KeyStoreEntry e = PGPUtil::instance().getSecretKeyStoreEntry(pgpSecretKeyID); if (!e.isNull()) pgpSecretKey = e.pgpSecretKey(); } - +#endif + tmp = o->getOption(base + ".allow-plain").toString(); if (tmp == "never") { allow_plain = XMPP::ClientStream::NoAllowPlain; @@ -429,6 +440,7 @@ void UserAccount::fromXml(const QDomElement &a) bool found; + readEntry(a, "id", &id); readEntry(a, "name", &name); readBoolAttribute(a, "enabled", &opt_enabled); readBoolAttribute(a, "auto", &opt_auto); @@ -517,11 +529,13 @@ void UserAccount::fromXml(const QDomElement &a) readNumEntry(a, "priority", &priority); QString pgpSecretKeyID; readEntry(a, "pgpSecretKeyID", &pgpSecretKeyID); +#ifdef HAVE_PGPUTIL if (!pgpSecretKeyID.isEmpty()) { QCA::KeyStoreEntry e = PGPUtil::instance().getSecretKeyStoreEntry(pgpSecretKeyID); if (!e.isNull()) pgpSecretKey = e.pgpSecretKey(); } +#endif QDomElement r = findSubTag(a, "roster", &found); if(found) { @@ -876,7 +890,7 @@ bool OptionsMigration::fromFile(const QString &fname) migrateBoolEntry(tag, "noAwayPopup", "options.ui.notifications.popup-dialogs.suppress-while-away"); migrateBoolEntry(tag, "noUnlistedPopup", "options.ui.notifications.popup-dialogs.suppress-when-not-on-roster"); migrateBoolEntry(tag, "raise", "options.ui.contactlist.raise-on-new-event"); - int force; + int force = 0; readNumEntry(tag, "incomingAs", &force); QString fe[4] = {"no", "message", "chat", "current-open"}; PsiOptions::instance()->setOption("options.messages.force-incoming-message-type", fe[force]); diff --git a/src/psiaccount.cpp b/src/psiaccount.cpp index aca92637c..7d98fc159 100644 --- a/src/psiaccount.cpp +++ b/src/psiaccount.cpp @@ -23,6 +23,7 @@ * */ + #include #include #include @@ -50,12 +51,16 @@ #include "xmpp_xmlcommon.h" #include "pongserver.h" #include "s5b.h" +#ifdef FILETRANSFER #include "filetransfer.h" -#include "pgpkeydlg.h" +#endif #include "psioptions.h" #include "textutil.h" #include "httpauthmanager.h" +#ifdef HAVE_PGPUTIL +#include "pgpkeydlg.h" #include "pgputil.h" +#endif #include "applicationinfo.h" #include "pgptransaction.h" #include "accountmanagedlg.h" @@ -65,7 +70,12 @@ #include "psievent.h" #include "jidutil.h" #include "eventdlg.h" +#ifdef YAPSI +#include "yapsi_revision.h" +#include "yaprivacymanager.h" +#else #include "psiprivacymanager.h" +#endif #include "rosteritemexchangetask.h" #include "chatdlg.h" #include "contactview.h" @@ -74,7 +84,9 @@ #ifdef USE_PEP #include "tunecontroller.h" #endif +#ifdef GROUPCHAT #include "groupchatdlg.h" +#endif #include "statusdlg.h" #include "infodlg.h" #include "adduserdlg.h" @@ -108,11 +120,12 @@ #include "geolocation.h" #include "physicallocation.h" #include "psipopup.h" -#include "pgputil.h" #include "translationmanager.h" #include "irisprotocol/iris_discoinfoquerier.h" #include "iconwidget.h" +#ifdef FILETRANSFER #include "filetransdlg.h" +#endif #include "systeminfo.h" #include "avatars.h" #include "ahcommanddlg.h" @@ -122,11 +135,17 @@ #include "tabdlg.h" #include "proxy.h" #include "psicontactlist.h" +#include "psicontact.h" +#include "psiselfcontact.h" +#include "alertable.h" #include "tabmanager.h" +#include "contactupdatesmanager.h" #include "fileutil.h" #include "Certificates/CertificateHelpers.h" #include "Certificates/CertificateErrorDialog.h" #include "Certificates/CertificateDisplayDialog.h" +#include "legacypsiaccount.h" +#include "bookmarkmanagedlg.h" #include "accountloginpassword.h" #include "alertmanager.h" @@ -292,18 +311,49 @@ bool BlockTransportPopupList::find(const Jid &j, bool online) // PsiAccount //---------------------------------------------------------------------------- -class PsiAccount::Private : public QObject +struct ReconnectData +{ + ReconnectData(int _delay, int _timeout) + : delay(_delay), timeout(_timeout) {} + int delay; + int timeout; +}; + +static const int RECONNECT_TIMEOUT_ERROR = -10; + +static QList reconnectData() +{ + static QList data; + if (data.isEmpty()) { + data << ReconnectData(15, 10); + data << ReconnectData(15, 11); + data << ReconnectData(15, 20); + data << ReconnectData(15, 30); + + data << ReconnectData(15, 30); + data << ReconnectData(15, 30); + data << ReconnectData(15, 30); + data << ReconnectData(15, 30); + + data << ReconnectData(2*60, 30); + } + + return data; +} + + +class PsiAccount::Private : public Alertable { Q_OBJECT public: Private(PsiAccount *parent) - : QObject(parent) + : Alertable(parent) , contactList(0) + , selfContact(0) , psi(0) , account(parent) , options(0) , client(0) - , cp(0) , eventQueue(0) , xmlConsole(0) , blockTransportPopupList(0) @@ -335,15 +385,21 @@ class PsiAccount::Private : public QObject , xmlRingbuf(1000) , xmlRingbufWrite(0) , doPopups_(true) + , reconnectTimeoutTimer_(0) + , reconnectData_(-1) + , reconnectInfrequently_(false) { + reconnectTimeoutTimer_ = new QTimer(this); + reconnectTimeoutTimer_->setSingleShot(true); + connect(reconnectTimeoutTimer_, SIGNAL(timeout()), SLOT(reconnectTimerTimeout())); } PsiContactList* contactList; + PsiSelfContact* selfContact; PsiCon *psi; PsiAccount *account; PsiOptions *options; Client *client; - ContactProfile *cp; UserAccount acc; Jid jid, nextJid; Status loginStatus; @@ -362,6 +418,8 @@ class PsiAccount::Private : public QObject CapsManager* capsManager; RosterItemExchangeTask* rosterItemExchangeTask; bool pepAvailable; + QString currentConnectionError; + int currentConnectionErrorCondition; // Tune Tune lastTune; @@ -419,10 +477,7 @@ class PsiAccount::Private : public QObject QHostAddress localAddress; - QString pathToProfileEvents() - { - return pathToProfile(activeProfile) + "/events-" + acc.name + ".xml"; - } + QList contacts; private: bool doPopups_; @@ -433,10 +488,12 @@ class PsiAccount::Private : public QObject if (activationType == FromXml || !doPopups_) return true; - if (lastManualStatus_.isAvailable()) { - if (lastManualStatus_.type() == XMPP::Status::DND) + if (lastManualStatus().isAvailable()) { + if (lastManualStatus().type() == XMPP::Status::DND) return true; - if ((lastManualStatus_.type() == XMPP::Status::Away || lastManualStatus_.type() == XMPP::Status::XA) && PsiOptions::instance()->getOption("options.ui.notifications.popup-dialogs.suppress-while-away").toBool()) { + if ((lastManualStatus().type() == XMPP::Status::Away || lastManualStatus().type() == XMPP::Status::XA) && + PsiOptions::instance()->getOption("options.ui.notifications.popup-dialogs.suppress-while-away").toBool()) + { return true; } } @@ -444,6 +501,128 @@ class PsiAccount::Private : public QObject return false; } +private slots: + void removeContact(PsiContact* contact) + { + Q_ASSERT(contacts.contains(contact)); + emit account->removedContact(contact); + contacts.removeAll(contact); + } + + /** + * TODO: make it work with multiple groups per contact. + * And with metacontacts too. + */ + PsiContact* addContact(const UserListItem& u) + { + Q_ASSERT(!findContact(u.jid())); + // PsiContactGroup* parent = groupsForUserListItem(u).first(); + PsiContact* contact = new PsiContact(u, account); + contacts.append(contact); + connect(contact, SIGNAL(destroyed(PsiContact*)), SLOT(removeContact(PsiContact*))); + emit account->addedContact(contact); + return contact; + } + +public: + PsiContact* findContact(const Jid& jid) const + { + foreach(PsiContact* contact, contacts) + if (contact->find(jid)) + return contact; + + return 0; + } + + PsiContact* findContactOrSelf(const Jid& jid) const + { + PsiContact* contact = findContact(jid); + if (!contact) { + if (selfContact->find(jid)) { + contact = selfContact; + } + } + return contact; + } + + QStringList groupList() const + { + QStringList groupList; + + foreach(PsiContact* contact, contacts) + foreach(QString group, contact->userListItem().groups()) + if (!groupList.contains(group)) + groupList.append(group); + + groupList.sort(); + return groupList; + } + + QString pathToProfileEvents() const + { + return pathToProfile(activeProfile) + "/events-" + JIDUtil::encode(acc.id).toLower() + ".xml"; + } + + // FIXME: Rename updateEntry -> updateContact + void updateEntry(const UserListItem& u) + { + if (u.isSelf()) { + selfContact->update(u); + } + else { + PsiContact* contact = findContact(u.jid()); + if (!contact) { + contact = addContact(u); + } + else { + contact->update(u); + } + } + } + + // FIXME: Rename removeEntry -> removeContact + void removeEntry(const Jid& jid) + { + PsiContact* contact = findContact(jid); + Q_ASSERT(contact); + delete contact; + emit account->removeContact(jid); + } + + void setState(int state) + { + const PsiIcon* alert = 0; + if (state == -1) + alert = IconsetFactory::iconPtr("psi/connect"); + Alertable::setAlert(alert); + } + + void setAlert(const Jid& jid, const PsiIcon* icon) + { + PsiContact* contact = findContactOrSelf(jid); + if (contact) + contact->setAlert(icon); + } + + void clearAlert(const Jid& jid) + { + PsiContact* contact = findContactOrSelf(jid); + if (contact) + contact->setAlert(0); + } + + void animateNick(const Jid& jid) + { + // TODO + Q_UNUSED(jid); + } + +private: + void alertFrameUpdated() + { + // account->emitDataUpdated(); + } + public slots: void queueChanged() { @@ -466,17 +645,15 @@ public slots: void setEnabled( bool e ) { - psi->contactList()->beginBulkOperation(); - acc.opt_enabled = e; - cp->setEnabled(e); account->cpUpdate(self); + // account->updateParent(); + + eventQueue->setEnabled(e); // signals account->enabledChanged(); account->updatedAccount(); - - psi->contactList()->endBulkOperation(); } void client_xmlIncoming(const QString &s) @@ -518,7 +695,9 @@ public slots: } if(newHash != photoHash) { photoHash = newHash; - account->setStatusDirect(loginStatus); + if (account->presenceSent) { + account->setStatusDirect(loginStatus); + } } } @@ -569,6 +748,14 @@ public slots: } } + void findDialogs(const QMetaObject& mo, QList* list) const + { + foreach(item_dialog2* i, dialogList) { + if (mo.cast(i->widget)) + list->append(i->widget); + } + } + void dialogRegister(QWidget* w, const Jid& jid) { connect(w, SIGNAL(destroyed(QObject*)), SLOT(forceDialogUnregister(QObject*))); @@ -605,6 +792,18 @@ private slots: dialogUnregister(static_cast(obj)); } +public slots: + void doModify() + { + AccountModifyDlg *w = account->findDialog(); + if(!w) { + w = new AccountModifyDlg(account, 0); + w->show(); + } + + bringToFront(w); + } + void incoming_call() { AvCall *sess = avCallManager->takeIncoming(); @@ -639,12 +838,17 @@ private slots: lastManualStatus_ = status; } + const Status& lastManualStatus() const + { + return lastManualStatus_; + } + private: Status lastManualStatus_; XMPP::Status autoAwayStatus(AutoAway autoAway) { - if (!lastManualStatus_.isAway() && !lastManualStatus_.isInvisible()) { + if (!lastManualStatus().isAway() && !lastManualStatus().isInvisible()) { int priority; if (PsiOptions::instance()->getOption("options.status.auto-away.force-priority").toBool()) { priority = PsiOptions::instance()->getOption("options.status.auto-away.priority").toInt(); @@ -663,20 +867,84 @@ private slots: ; } } - return lastManualStatus_; + return lastManualStatus(); + } + +public slots: + void startReconnect() + { + reconnectData_++; + Q_ASSERT(::reconnectData().count()); + Q_ASSERT(reconnectData >= 0); + reconnectData_ = qMax(0, qMin(reconnectData_, ::reconnectData().count() - 1)); + ReconnectData data = ::reconnectData()[reconnectData_]; + + int delay = data.delay * 1000; + reconnectTimeoutTimer_->stop(); + reconnectTimeoutTimer_->setInterval(data.timeout * 1000); + account->doReconnect = true; + + reconnectScheduledAt_ = QDateTime::currentDateTime(); + reconnectScheduledAt_.addSecs(data.delay); + QTimer::singleShot(delay, account, SLOT(reconnect())); + emit account->stateChanged(); + } + + void startReconnectTimeout() + { + reconnectScheduledAt_ = QDateTime(); + account->doReconnect = false; + reconnectTimeoutTimer_->start(); } + + void stopReconnect() + { + reconnectScheduledAt_ = QDateTime(); + reconnectData_ = -1; + account->doReconnect = false; + reconnectTimeoutTimer_->stop(); + } + +private slots: + void reconnectTimerTimeout() + { + reconnectScheduledAt_ = QDateTime(); + account->v_isActive = true; + account->cs_error(RECONNECT_TIMEOUT_ERROR); + } + +public: + QDateTime reconnectScheduledAt_; + QTimer* reconnectTimeoutTimer_; + int reconnectData_; + bool reconnectInfrequently_; }; +PsiAccount* PsiAccount::create(const UserAccount &acc, PsiContactList *parent, CapsRegistry* capsRegistry, TabManager *tabManager) +{ +#ifdef NEWCONTACTLIST + PsiAccount* account = new PsiAccount(acc, parent, capsRegistry, tabManager); +#else + PsiAccount* account = new LegacyPsiAccount(acc, parent, capsRegistry, tabManager); +#endif + account->init(); + return account; +} + +void PsiAccount::init() +{ +} + PsiAccount::PsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegistry* capsRegistry, TabManager *tabManager) -:QObject(parent) + : QObject(parent) { d = new Private( this ); + QPointer boom(d); d->contactList = parent; d->tabManager = tabManager; d->psi = parent->psi(); d->options = PsiOptions::instance(); d->client = 0; - d->cp = 0; d->userCounter = 0; d->avatarFactory = 0; d->voiceCaller = 0; @@ -728,11 +996,17 @@ PsiAccount::PsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegis QStringList features; features << "http://jabber.org/protocol/commands"; features << "http://jabber.org/protocol/rosterx"; +#ifdef GROUPCHAT features << "http://jabber.org/protocol/muc"; +#endif features << "jabber:x:data"; d->client->setFeatures(Features(features)); +#ifdef FILETRANSFER d->client->setFileTransferEnabled(true); +#else + d->client->setFileTransferEnabled(false); +#endif setSendChatState(PsiOptions::instance()->getOption("options.messages.send-composing-events").toBool()); @@ -757,7 +1031,11 @@ PsiAccount::PsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegis connect(d->client, SIGNAL(groupChatLeft(const Jid &)), SLOT(client_groupChatLeft(const Jid &))); connect(d->client, SIGNAL(groupChatPresence(const Jid &, const Status &)), SLOT(client_groupChatPresence(const Jid &, const Status &))); connect(d->client, SIGNAL(groupChatError(const Jid &, int, const QString &)), SLOT(client_groupChatError(const Jid &, int, const QString &))); +#ifdef FILETRANSFER connect(d->client->fileTransferManager(), SIGNAL(incomingReady()), SLOT(client_incomingFileTransfer())); +#endif + connect(d->client, SIGNAL(beginImportRoster()), SIGNAL(beginBulkContactUpdate())); + connect(d->client, SIGNAL(endImportRoster()), SIGNAL(endBulkContactUpdate())); connect(d->client, SIGNAL(xmlIncoming(const QString &)), d, SLOT(client_xmlIncoming(const QString &))); connect(d->client, SIGNAL(xmlOutgoing(const QString &)), d, SLOT(client_xmlOutgoing(const QString &))); @@ -772,44 +1050,6 @@ PsiAccount::PsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegis d->rosterItemExchangeTask = new RosterItemExchangeTask(d->client->rootTask()); connect(d->rosterItemExchangeTask,SIGNAL(rosterItemExchange(const Jid&, const RosterExchangeItems&)),SLOT(actionRecvRosterExchange(const Jid&,const RosterExchangeItems&))); - // contactprofile context - d->cp = new ContactProfile(this, acc.name, d->psi->contactView()); - connect(d->cp, SIGNAL(actionDefault(const Jid &)),SLOT(actionDefault(const Jid &))); - connect(d->cp, SIGNAL(actionRecvEvent(const Jid &)),SLOT(actionRecvEvent(const Jid &))); - connect(d->cp, SIGNAL(actionSendMessage(const Jid &)),SLOT(actionSendMessage(const Jid &))); - connect(d->cp, SIGNAL(actionSendMessage(const QList &)),SLOT(actionSendMessage(const QList &))); - connect(d->cp, SIGNAL(actionSendUrl(const Jid &)),SLOT(actionSendUrl(const Jid &))); - connect(d->cp, SIGNAL(actionRemove(const Jid &)),SLOT(actionRemove(const Jid &))); - connect(d->cp, SIGNAL(actionRename(const Jid &, const QString &)),SLOT(actionRename(const Jid &, const QString &))); - connect(d->cp, SIGNAL(actionGroupRename(const QString &, const QString &)),SLOT(actionGroupRename(const QString &, const QString &))); - connect(d->cp, SIGNAL(actionHistory(const Jid &)),SLOT(actionHistory(const Jid &))); - connect(d->cp, SIGNAL(actionOpenChat(const Jid &)),SLOT(actionOpenChat(const Jid &))); - connect(d->cp, SIGNAL(actionOpenChatSpecific(const Jid &)),SLOT(actionOpenChatSpecific(const Jid &))); -#ifdef WHITEBOARDING - connect(d->cp, SIGNAL(actionOpenWhiteboard(const Jid &)),SLOT(actionOpenWhiteboard(const Jid &))); - connect(d->cp, SIGNAL(actionOpenWhiteboardSpecific(const Jid &)),SLOT(actionOpenWhiteboardSpecific(const Jid &))); -#endif - connect(d->cp, SIGNAL(actionAgentSetStatus(const Jid &, Status &)),SLOT(actionAgentSetStatus(const Jid &, Status &))); - connect(d->cp, SIGNAL(actionInfo(const Jid &)),SLOT(actionInfo(const Jid &))); - connect(d->cp, SIGNAL(actionAuth(const Jid &)),SLOT(actionAuth(const Jid &))); - connect(d->cp, SIGNAL(actionAuthRequest(const Jid &)),SLOT(actionAuthRequest(const Jid &))); - connect(d->cp, SIGNAL(actionAuthRemove(const Jid &)),SLOT(actionAuthRemove(const Jid &))); - connect(d->cp, SIGNAL(actionAdd(const Jid &)),SLOT(actionAdd(const Jid &))); - connect(d->cp, SIGNAL(actionGroupAdd(const Jid &, const QString &)),SLOT(actionGroupAdd(const Jid &, const QString &))); - connect(d->cp, SIGNAL(actionGroupRemove(const Jid &, const QString &)),SLOT(actionGroupRemove(const Jid &, const QString &))); - connect(d->cp, SIGNAL(actionVoice(const Jid &)),SLOT(actionVoice(const Jid &))); - connect(d->cp, SIGNAL(actionSendFile(const Jid &)),SLOT(actionSendFile(const Jid &))); - connect(d->cp, SIGNAL(actionSendFiles(const Jid &, const QStringList&)),SLOT(actionSendFiles(const Jid &, const QStringList&))); - connect(d->cp, SIGNAL(actionExecuteCommand(const Jid &, const QString&)),SLOT(actionExecuteCommand(const Jid &, const QString&))); - connect(d->cp, SIGNAL(actionExecuteCommandSpecific(const Jid &, const QString&)),SLOT(actionExecuteCommandSpecific(const Jid &, const QString&))); - connect(d->cp, SIGNAL(actionSetMood()),SLOT(actionSetMood())); - connect(d->cp, SIGNAL(actionSetAvatar()),SLOT(actionSetAvatar())); - connect(d->cp, SIGNAL(actionUnsetAvatar()),SLOT(actionUnsetAvatar())); - connect(d->cp, SIGNAL(actionDisco(const Jid &, const QString &)),SLOT(actionDisco(const Jid &, const QString &))); - connect(d->cp, SIGNAL(actionInvite(const Jid &, const QString &)),SLOT(actionInvite(const Jid &, const QString &))); - connect(d->cp, SIGNAL(actionAssignKey(const Jid &)),SLOT(actionAssignKey(const Jid &))); - connect(d->cp, SIGNAL(actionUnassignKey(const Jid &)),SLOT(actionUnassignKey(const Jid &))); - // Initialize server info stuff d->serverInfoManager = new ServerInfoManager(d->client); connect(d->serverInfoManager,SIGNAL(featuresChanged()),SLOT(serverFeaturesChanged())); @@ -866,6 +1106,8 @@ PsiAccount::PsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegis if(PsiOptions::instance()->getOption("options.html.chat.render").toBool()) d->client->addExtension("html",Features("http://jabber.org/protocol/xhtml-im")); + d->selfContact = new PsiSelfContact(d->self, this); + // restore cached roster for(Roster::ConstIterator it = acc.roster.begin(); it != acc.roster.end(); ++it) client_rosterItemUpdated(*it); @@ -883,19 +1125,20 @@ PsiAccount::PsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegis setUserAccount(acc); connect(d->psi->proxy(), SIGNAL(proxyRemoved(QString)), d, SLOT(pm_proxyRemoved(QString))); - d->contactList->link(this); connect(d->psi, SIGNAL(emitOptionsUpdate()), SLOT(optionsUpdate())); //connect(d->psi, SIGNAL(pgpToggled(bool)), SLOT(pgpToggled(bool))); +#ifdef HAVE_PGPUTIL connect(&PGPUtil::instance(), SIGNAL(pgpKeysUpdated()), SLOT(pgpKeysUpdated())); +#endif - d->setEnabled(d->acc.opt_enabled); + d->setEnabled(enabled()); // Listen to the capabilities manager connect(capsManager(),SIGNAL(capsChanged(const Jid&)),SLOT(capsChanged(const Jid&))); //printf("PsiAccount: [%s] loaded\n", name().latin1()); d->xmlConsole = new XmlConsole(this); - if(PsiOptions::instance()->getOption("options.xml-console.enable-at-login").toBool() && d->acc.opt_enabled) { + if(PsiOptions::instance()->getOption("options.xml-console.enable-at-login").toBool() && enabled()) { this->showXmlConsole(); d->xmlConsole->enable(); } @@ -954,20 +1197,25 @@ PsiAccount::PsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegis // load event queue from disk QTimer::singleShot(0, d, SLOT(loadQueue())); + + d->contactList->link(this); } PsiAccount::~PsiAccount() { + emit accountDestroyed(); // nuke all related dialogs deleteAllDialogs(); - logout(true); + logout(true, loggedOutStatus()); QString str = name(); while (!d->messageQueue.isEmpty()) delete d->messageQueue.takeFirst(); +#ifdef FILETRANSFER d->psi->ftdlg()->killTransfers(this); +#endif delete d->avCallManager; @@ -975,7 +1223,6 @@ PsiAccount::~PsiAccount() delete d->voiceCaller; delete d->ahcManager; - delete d->cp; delete d->privacyManager; delete d->capsManager; delete d->pepManager; @@ -1033,7 +1280,7 @@ void PsiAccount::setEnabled(bool e) } if (isActive()) { if (QMessageBox::information(0, tr("Disable Account"), tr("The account is currently active.\nDo you want to log out ?"),QMessageBox::Yes,QMessageBox::No | QMessageBox::Default | QMessageBox::Escape, QMessageBox::NoButton) == QMessageBox::Yes) { - logout(); + logout(false, loggedOutStatus()); } else { return; @@ -1060,7 +1307,7 @@ bool PsiAccount::isConnected() const */ bool PsiAccount::isAvailable() const { - return isConnected() && isActive() && loggedIn(); + return isConnected() && isActive() && loggedIn() && !isDisconnecting; } const QString & PsiAccount::name() const @@ -1068,6 +1315,15 @@ const QString & PsiAccount::name() const return d->acc.name; } +const QString &PsiAccount::id() const +{ + return d->acc.id; +} + +/** + * TODO: FIXME: Has side-effects of updating toggle's status, clearing + * the roster and keybindings. + */ // FIXME: we should move all PsiAccount::userAccount() users to PsiAccount::accountOptions() const UserAccount & PsiAccount::userAccount() const { @@ -1100,11 +1356,6 @@ Client *PsiAccount::client() const return d->client; } -ContactProfile *PsiAccount::contactProfile() const -{ - return d->cp; -} - EventQueue *PsiAccount::eventQueue() const { return d->eventQueue; @@ -1153,8 +1404,10 @@ QHostAddress *PsiAccount::localAddress() const return &d->localAddress; } -void PsiAccount::setUserAccount(const UserAccount &acc) +void PsiAccount::setUserAccount(const UserAccount &_acc) { + UserAccount acc = _acc; + bool renamed = false; QString oldfname; if(d->acc.name != acc.name) { @@ -1163,6 +1416,7 @@ void PsiAccount::setUserAccount(const UserAccount &acc) } d->acc = acc; + d->setEnabled(enabled()); // rename queue file? if(renamed) { @@ -1181,20 +1435,21 @@ void PsiAccount::setUserAccount(const UserAccount &acc) d->stream->setNoopTime(0); } - d->cp->setName(d->acc.name); + // d->cp->setName(d->acc.name); Jid j = acc.jid; d->nextJid = j; if(!isActive()) { d->jid = j; d->self.setJid(j); - d->cp->updateEntry(d->self); + profileUpdateEntry(d->self); } if(!d->nickFromVCard) setNick(j.node()); QString pgpSecretKeyID = (d->acc.pgpSecretKey.isNull() ? "" : d->acc.pgpSecretKey.keyId()); d->self.setPublicKeyID(pgpSecretKeyID); +#ifdef HAVE_PGPUTIL if(PGPUtil::instance().pgpAvailable()) { bool updateStatus = !PGPUtil::instance().equals(d->acc.pgpSecretKey, d->cur_pgpSecretKey) && loggedIn(); d->cur_pgpSecretKey = d->acc.pgpSecretKey; @@ -1204,6 +1459,7 @@ void PsiAccount::setUserAccount(const UserAccount &acc) setStatusDirect(d->loginStatus); } } +#endif if(d->avCallManager) { @@ -1235,15 +1491,18 @@ QString PsiAccount::nameWithJid() const return (name() + " (" + JIDUtil::toString(jid(),true) + ')'); } -// Moved out of constructor to have all accounts loaded -// when we ask for passwords. void PsiAccount::autoLogin() { // auto-login ? - if (d->acc.opt_enabled) { + if (enabled()) { bool autoLogin = d->acc.opt_auto; if (autoLogin) { - setStatus(Status(Status::Online, "", d->acc.priority)); +#ifndef YAPSI + // FIXME: we should remember last used status + setStatus(Status(Status::Online, "", d->acc.priority), false, true); +#else + setStatus(Status(d->psi->lastLoggedInStatusType(), d->psi->currentStatusMessage(), d->acc.priority), false, true); +#endif } } } @@ -1339,8 +1598,17 @@ void PsiAccount::logout(bool fast, const Status &s) if(!isActive()) return; - // cancel reconnect - doReconnect = false; + clearCurrentConnectionError(); + + d->stopReconnect(); + + forceDisconnect(fast, s); +} + +void PsiAccount::forceDisconnect(bool fast, const XMPP::Status &s) +{ + if(!isActive()) + return; if(loggedIn()) { // Extended Presence @@ -1363,8 +1631,12 @@ void PsiAccount::logout(bool fast, const Status &s) d->loginStatus = Status(Status::Offline); stateChanged(); +#ifdef YAPSI + disconnect(); +#else // Using 100msecs; See note on disconnect() QTimer::singleShot(100, this, SLOT(disconnect())); +#endif } // skz note: I had to split logout() because server seem to need some time to store status @@ -1376,10 +1648,12 @@ void PsiAccount::disconnect() d->client->close(); cleanupStream(); - disconnected(); + emit disconnected(); } } +// TODO: search through the Psi and replace most of loggedIn() calls with isAvailable() +// because it's safer bool PsiAccount::loggedIn() const { return (v_isActive && presenceSent); @@ -1387,12 +1661,13 @@ bool PsiAccount::loggedIn() const void PsiAccount::tls_handshaken() { - if (CertificateHelpers::checkCertificate(d->tls, d->tlsHandler, d->acc.tlsOverrideDomain, d->acc.tlsOverrideCert, this, - (d->psi->contactList()->enabledAccounts().count() > 1 ? QString("%1: ").arg(name()) : "") + tr("Server Authentication"), - d->jid.domain())) { + bool certificateOk = CertificateHelpers::checkCertificate(d->tls, d->tlsHandler, d->acc.tlsOverrideDomain, d->acc.tlsOverrideCert, this, + (d->psi->contactList()->enabledAccounts().count() > 1 ? QString("%1: ").arg(name()) : "") + tr("Server Authentication"), + d->jid.domain()); + if (certificateOk) { d->tlsHandler->continueAfterHandshake(); } else { - logout(); + logout(false, loggedOutStatus()); } } @@ -1465,6 +1740,11 @@ void PsiAccount::cs_needAuthParams(bool user, bool pass, bool realm) void PsiAccount::cs_authenticated() { + if (d->conn.isNull() || d->stream.isNull()) { + cs_error(RECONNECT_TIMEOUT_ERROR); + return; + } + //printf("PsiAccount: [%s] authenticated\n", name().latin1()); d->conn->changePollInterval(10); // for http poll, slow down after login @@ -1554,10 +1834,13 @@ void PsiAccount::cs_warning(int w) } } -void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QCATLSHandler *tlsHandler, QString *_str, bool *_reconn) +void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QCATLSHandler *tlsHandler, QString *_str, bool *_reconn, bool *_disableAutoConnect, bool *_isAuthError, bool *_isTemporaryAuthFailure) { QString str; bool reconn = false; + bool disableAutoConnect = false; + bool isAuthError = false; + bool isTemporaryAuthFailure = false; if(err == -1) { str = tr("Disconnected"); @@ -1580,7 +1863,9 @@ void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, detail = stream->errorText(); } else { x = XMPP::Stream::GenericStreamError; +#ifndef YAPSI reconn = false; +#endif } if(x == XMPP::Stream::GenericStreamError) @@ -1588,6 +1873,7 @@ void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, else if(x == XMPP::ClientStream::Conflict) { s = tr("Conflict (remote login replacing this one)"); reconn = false; + disableAutoConnect = true; } else if(x == XMPP::ClientStream::ConnectionTimeout) s = tr("Timed out from inactivity"); @@ -1609,7 +1895,7 @@ void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, str = tr("XMPP Stream Error: %1").arg(s) + "\n" + detail; } else if(err == XMPP::ClientStream::ErrConnection) { - int x = conn->errorCode(); + int x = conn ? conn->errorCode() : XMPP::AdvancedConnector::ErrStream; QString s; reconn = true; if(x == XMPP::AdvancedConnector::ErrConnectionRefused) @@ -1623,6 +1909,7 @@ void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, else if(x == XMPP::AdvancedConnector::ErrProxyAuth) { s = tr("Proxy authentication failed"); reconn = false; + isAuthError = true; } else if(x == XMPP::AdvancedConnector::ErrStream) s = tr("Socket/stream error"); @@ -1665,9 +1952,10 @@ void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QString s; if(x == XMPP::ClientStream::GenericAuthError) { s = tr("Unable to login"); + isAuthError = true; } else if(x == XMPP::ClientStream::NoMech) { s = tr("No appropriate mechanism available for given security settings (e.g. SASL library too weak, or plaintext authentication not enabled)"); - s += "\n" + stream->errorText(); + s += "\n" + stream->errorText(); } else if(x == XMPP::ClientStream::BadProto) { s = tr("Bad server response"); } else if(x == XMPP::ClientStream::BadServ) { @@ -1676,6 +1964,7 @@ void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, s = tr("Encryption required for chosen SASL mechanism"); } else if(x == XMPP::ClientStream::InvalidAuthzid) { s = tr("Invalid account information"); + isAuthError = true; } else if(x == XMPP::ClientStream::InvalidMech) { s = tr("Invalid SASL mechanism"); } else if(x == XMPP::ClientStream::InvalidRealm) { @@ -1684,34 +1973,72 @@ void PsiAccount::getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, s = tr("SASL mechanism too weak for this account"); } else if(x == XMPP::ClientStream::NotAuthorized) { s = tr("Not authorized"); + isAuthError = true; } else if(x == XMPP::ClientStream::TemporaryAuthFailure) { s = tr("Temporary auth failure"); + isAuthError = true; + isTemporaryAuthFailure = true; } str = tr("Authentication error: %1").arg(s); } - else if(err == XMPP::ClientStream::ErrSecurityLayer) + else if(err == XMPP::ClientStream::ErrSecurityLayer) { str = tr("Broken security layer (SASL)"); - else + } + else { str = tr("None"); + reconn = true; + } //printf("str[%s], reconn=%d\n", str.latin1(), reconn); *_str = str; *_reconn = reconn; + *_disableAutoConnect = disableAutoConnect; + *_isAuthError = isAuthError; + *_isTemporaryAuthFailure = isTemporaryAuthFailure; } void PsiAccount::cs_error(int err) { QString str; bool reconn; + bool isAuthError; + bool isTemporaryAuthFailure; if (!isActive()) return; // all cleaned up already - getErrorInfo(err, d->conn, d->stream, d->tlsHandler, &str, &reconn); + bool disableAutoConnect; + getErrorInfo(err, d->conn, d->stream, d->tlsHandler, &str, &reconn, &disableAutoConnect, &isAuthError, &isTemporaryAuthFailure); + if (err != RECONNECT_TIMEOUT_ERROR) { + d->currentConnectionError = str; + d->currentConnectionErrorCondition = -1; + } + if(err == XMPP::ClientStream::ErrAuth && d && d->stream) { + d->currentConnectionErrorCondition = d->stream->errorCondition(); + } d->client->close(); cleanupStream(); +#ifdef YAPSI + // we could disable an account while 'emit connectionError()' + QString bakError = d->currentConnectionError; + int bakErrorCond = d->currentConnectionErrorCondition; +#endif + + emit connectionError(d->currentConnectionError); //printf("Error: [%s]\n", str.latin1()); +#ifdef YAPSI + d->currentConnectionError = bakError; + d->currentConnectionErrorCondition = bakErrorCond; +#endif + +#ifdef YAPSI + d->reconnectInfrequently_ = !reconn; + if (!d->disableAutoConnect) { + reconn = true; + } +#endif + isDisconnecting = true; if ( loggedIn() ) { // FIXME: is this condition okay? @@ -1722,10 +2049,7 @@ void PsiAccount::cs_error(int err) // Auto-Reconnect? if(d->acc.opt_reconn && reconn) { - int delay = 5000; // reconnect in 5 seconds - doReconnect = true; - stateChanged(); - QTimer::singleShot(delay, this, SLOT(reconnect())); + d->startReconnect(); return; } @@ -1743,11 +2067,27 @@ void PsiAccount::cs_error(int err) psi()->alertManager()->raiseMessageBox(AlertManager::ConnectionError, QMessageBox::Critical, title, message); } +void PsiAccount::clearCurrentConnectionError() +{ + d->currentConnectionError = QString(); + d->currentConnectionErrorCondition = -1; + emit connectionError(d->currentConnectionError); +} + +QString PsiAccount::currentConnectionError() const +{ + return d->currentConnectionError; +} + +int PsiAccount::currentConnectionErrorCondition() const +{ + return d->currentConnectionErrorCondition; +} + void PsiAccount::client_rosterRequestFinished(bool success, int, const QString &) { if(success) { //printf("PsiAccount: [%s] roster retrieved ok. %d entries.\n", name().latin1(), d->client->roster().count()); - psi()->contactList()->beginBulkOperation(); // delete flagged items foreach(UserListItem* u, d->userList) { @@ -1757,13 +2097,13 @@ void PsiAccount::client_rosterRequestFinished(bool success, int, const QString & d->eventQueue->clear(u->jid()); updateReadNext(u->jid()); - d->cp->removeEntry(u->jid()); + profileRemoveEntry(u->jid()); d->userList.removeAll(u); delete u; } } - psi()->contactList()->endBulkOperation(); + d->stopReconnect(); } else { //printf("PsiAccount: [%s] error retrieving roster: [%d, %s]\n", name().latin1(), code, str.latin1()); @@ -1776,7 +2116,11 @@ void PsiAccount::client_rosterRequestFinished(bool success, int, const QString & //if (PsiOptions::instance()->getOption("options.options-storage.load").toBool()) // PsiOptions::instance()->load(d->client); + // we need to have up-to-date photoHash for initial presence + d->vcardChanged(jid()); setStatusDirect(d->loginStatus, d->loginWithPriority); + + emit rosterRequestFinished(); } void PsiAccount::resolveContactName() @@ -1840,11 +2184,13 @@ void PsiAccount::bookmarksAvailabilityChanged() return; } +#ifdef GROUPCHAT foreach(ConferenceBookmark c, d->bookmarkManager->conferences()) { if (!findDialog(Jid(c.jid().bare())) && c.autoJoin()) { actionJoin(c, true); } } +#endif } void PsiAccount::incomingHttpAuthRequest(const PsiHttpAuthRequest &req) @@ -1878,7 +2224,7 @@ void PsiAccount::client_rosterItemUpdated(const RosterItem &r) } u->setInList(true); - d->cp->updateEntry(*u); + profileUpdateEntry(*u); } void PsiAccount::client_rosterItemRemoved(const RosterItem &r) @@ -1887,16 +2233,17 @@ void PsiAccount::client_rosterItemRemoved(const RosterItem &r) if(!u) return; + u->setInList(false); simulateContactOffline(u); // if the item has messages queued, then move them to 'not in list' if(d->eventQueue->count(r.jid()) > 0) { u->setInList(false); - d->cp->updateEntry(*u); + profileUpdateEntry(*u); } // else remove them for good! else { - d->cp->removeEntry(u->jid()); + profileRemoveEntry(u->jid()); d->userList.removeAll(u); delete u; } @@ -1904,8 +2251,10 @@ void PsiAccount::client_rosterItemRemoved(const RosterItem &r) void PsiAccount::tryVerify(UserListItem *u, UserResource *ur) { +#ifdef HAVE_PGPUTIL if(PGPUtil::instance().pgpAvailable()) verifyStatus(u->jid().withResource(ur->name()), ur->status()); +#endif } void PsiAccount::incomingVoiceCall(const Jid& j) @@ -1981,11 +2330,12 @@ void PsiAccount::client_resourceAvailable(const Jid &j, const Resource &r) cpUpdate(*u, r.name(), true); if(doAnim && PsiOptions::instance()->getOption("options.ui.contactlist.use-status-change-animation").toBool()) - d->cp->animateNick(u->jid()); + profileAnimateNick(u->jid()); + } if(doSound) - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.contact-online").toString()); + playSound(eOnline); #if !defined(Q_WS_MAC) || !defined(HAVE_GROWL) // Do the popup test earlier (to avoid needless JID lookups) @@ -2083,7 +2433,7 @@ void PsiAccount::client_resourceUnavailable(const Jid &j, const Resource &r) cpUpdate(*u, r.name(), true); } if(doSound) - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.contact-offline").toString()); + playSound(eOffline); #if !defined(Q_WS_MAC) || !defined(HAVE_GROWL) // Do the popup test earlier (to avoid needless JID lookups) @@ -2142,6 +2492,7 @@ void PsiAccount::client_messageReceived(const Message &m) } } +#ifdef HAVE_PGPUTIL // encrypted message? if(PGPUtil::instance().pgpAvailable() && !_m.xencrypted().isEmpty()) { Message *m = new Message(_m); @@ -2149,10 +2500,19 @@ void PsiAccount::client_messageReceived(const Message &m) processMessageQueue(); return; } +#endif processIncomingMessage(_m); } +/** + * Handles the passed Message \param m. Also message's type could be modified + * here, if certain options are set. + * + * TODO: Generalize option.incomingAs and EventDlg::messagingEnabled() + * processing in one common function that could be applied to the messages + * loaded from the event queue, and settings were changed prior to that. + */ void PsiAccount::processIncomingMessage(const Message &_m) { // skip empty messages, but not if the message contains a data form @@ -2164,10 +2524,12 @@ void PsiAccount::processIncomingMessage(const Message &_m) return; if(_m.type() == "groupchat") { +#ifdef GROUPCHAT GCMainDlg *w = findDialog(Jid(_m.from().bare())); if(w) w->message(_m); return; +#endif } // only toggle if not an invite or body is not empty @@ -2193,12 +2555,13 @@ void PsiAccount::processIncomingMessage(const Message &_m) { if (PsiOptions::instance()->getOption("options.messages.exclude-muc-from-ignore").toBool()) { +#ifdef GROUPCHAT GCMainDlg *w = findDialog(Jid(_m.from().bare())); if(!w) { return; } - +#endif } else { @@ -2233,7 +2596,10 @@ void PsiAccount::processIncomingMessage(const Message &_m) } // change the type? - if (m.type() != "headline" && m.invite().isEmpty() && m.mucInvites().isEmpty()) { + if (!EventDlg::messagingEnabled()) { + m.setType("chat"); + } + else if (m.type() != "headline" && m.invite().isEmpty() && m.mucInvites().isEmpty()) { if (PsiOptions::instance()->getOption("options.messages.force-incoming-message-type").toString() == "message") m.setType(""); else if (PsiOptions::instance()->getOption("options.messages.force-incoming-message-type").toString() == "chat") @@ -2250,14 +2616,6 @@ void PsiAccount::processIncomingMessage(const Message &_m) //if(m.type() == "chat" && (!m.urlList().isEmpty() || !m.subject().isEmpty())) // m.setType(""); - //We must not respond to recepit request automatically - KIS - /*if(m.messageReceipt() == ReceiptRequest && !m.id().isEmpty()) { - Message tm(m.from()); - tm.setId(m.id()); - tm.setMessageReceipt(ReceiptReceived); - dj_sendMessage(tm, false); - }*/ - MessageEvent *me = new MessageEvent(m, this); me->setOriginLocal(false); handleEvent(me, IncomingStanza); @@ -2299,6 +2657,7 @@ void PsiAccount::incomingGoogleFileTransfer(GoogleFileTransfer* ft) void PsiAccount::client_incomingFileTransfer() { +#ifdef FILETRANSFER FileTransfer *ft = d->client->fileTransferManager()->takeIncoming(); if(!ft) return; @@ -2311,15 +2670,16 @@ void PsiAccount::client_incomingFileTransfer() FileEvent *fe = new FileEvent(ft->peer().full(), ft, this); fe->setTimeStamp(QDateTime::currentDateTime()); handleEvent(fe, IncomingStanza); +#endif } void PsiAccount::reconnect() { - if(doReconnect) { + if(doReconnect && !isConnected()) { //printf("PsiAccount: [%s] reconnecting...\n", name().latin1()); emit reconnecting(); v_isActive = false; - doReconnect = false; + d->startReconnectTimeout(); login(); } } @@ -2329,9 +2689,24 @@ Status PsiAccount::status() const return d->loginStatus; } -void PsiAccount::setStatus(const Status &_s, bool withPriority) +Status PsiAccount::loggedOutStatus() +{ +#ifdef YAPSI + return Status(Status::Offline); +#else + return Status(Status::Offline, "Logged out", 0); +#endif +} + +void PsiAccount::setStatus(const Status &_s, bool withPriority, bool isManualStatus) { - d->setManualStatus(_s); + Status s = _s; + if (!withPriority) + s.setPriority(d->acc.priority); + + if (isManualStatus) { + d->setManualStatus(s); + } // Block all transports' contacts' status change popups from popping { @@ -2343,13 +2718,6 @@ void PsiAccount::setStatus(const Status &_s, bool withPriority) } } - // cancel auto-status and reconnect - doReconnect = false; - - Status s = _s; - if (!withPriority) - s.setPriority(d->acc.priority); - d->loginStatus = s; d->loginWithPriority = withPriority; @@ -2363,6 +2731,7 @@ void PsiAccount::setStatus(const Status &_s, bool withPriority) modify(); return; } + if(!d->acc.opt_pass) { // will call back to us later new AccountLoginPassword(this); @@ -2372,8 +2741,9 @@ void PsiAccount::setStatus(const Status &_s, bool withPriority) } // change status else { - if(rosterDone) + if(rosterDone) { setStatusDirect(s, withPriority); + } if(s.isInvisible()) {//&&Pass invis to transports KEVIN //this is a nasty hack to let the transports know we're invisible, since they get an offline packet when we go invisible @@ -2435,6 +2805,10 @@ void PsiAccount::setStatusActual(const Status &_s) s.setCapsExt(d->client->capsExt()); } + if (!presenceSent) { + simulateRosterOffline(); + } + // Add vcard photo hash if available if(!d->photoHash.isEmpty()) { s.setPhotoHash(d->photoHash); @@ -2449,20 +2823,27 @@ void PsiAccount::setStatusActual(const Status &_s) else { presenceSent = true; stateChanged(); - QTimer::singleShot(15000, this, SLOT(enableNotifyOnline())); + sentInitialPresence(); - // Get the vcard - const VCard *vcard = VCardFactory::instance()->vcard(d->jid); - if (PsiOptions::instance()->getOption("options.vcard.query-own-vcard-on-login").toBool() || !vcard || vcard->isEmpty() || (vcard->nickName().isEmpty() && vcard->fullName().isEmpty())) - VCardFactory::instance()->getVCard(d->jid, d->client->rootTask(), this, SLOT(slotCheckVCard())); - else { - d->nickFromVCard = true; - // if we get here, one of these fields is non-empty - if (!vcard->nickName().isEmpty()) { - setNick(vcard->nickName()); - } else { - setNick(vcard->fullName()); - } + clearCurrentConnectionError(); + } +} + +void PsiAccount::sentInitialPresence() +{ + QTimer::singleShot(15000, this, SLOT(enableNotifyOnline())); + + // Get the vcard + const VCard *vcard = VCardFactory::instance()->vcard(d->jid); + if (PsiOptions::instance()->getOption("options.vcard.query-own-vcard-on-login").toBool() || !vcard || vcard->isEmpty() || (vcard->nickName().isEmpty() && vcard->fullName().isEmpty())) + VCardFactory::instance()->getVCard(d->jid, d->client->rootTask(), this, SLOT(slotCheckVCard())); + else { + d->nickFromVCard = true; + // if we get here, one of these fields is non-empty + if (!vcard->nickName().isEmpty()) { + setNick(vcard->nickName()); + } else { + setNick(vcard->fullName()); } } } @@ -2534,16 +2915,57 @@ void PsiAccount::secondsIdle(int seconds) d->setAutoAway(Private::AutoAway_None); } -void PsiAccount::playSound(const QString &str) +void PsiAccount::playSound(PsiAccount::SoundType _onevent) { + int onevent = static_cast(_onevent); + if (onevent < 0) { + return; + } + + QString str; + switch (onevent) { + case eMessage: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.incoming-message").toString(); + break; + case eChat1: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.new-chat").toString(); + break; + case eChat2: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.chat-message").toString(); + break; + case eHeadline: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.incoming-headline").toString(); + break; + case eSystem: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.system-message").toString(); + break; + case eOnline: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.contact-online").toString(); + break; + case eOffline: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.contact-offline").toString(); + break; + case eSend: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.outgoing-chat").toString(); + break; + case eIncomingFT: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.incoming-file-transfer").toString(); + break; + case eFTComplete: + str = PsiOptions::instance()->getOption("options.ui.notifications.sounds.completed-file-transfer").toString(); + break; + default: + Q_ASSERT(false); + } + if(str.isEmpty()) return; - int s = STATUS_OFFLINE; - if(loggedIn()) - s = makeSTATUS(status()); + XMPP::Status::Type s = XMPP::Status::Offline; + if (loggedIn()) + s = d->lastManualStatus().type(); - if(s == STATUS_DND) + if (s == XMPP::Status::DND) return; // no away sounds? @@ -2588,7 +3010,9 @@ bool PsiAccount::validRosterExchangeItem(const RosterExchangeItem& item) ChatDlg* PsiAccount::findChatDialog(const Jid& jid) const { - return findDialog(jid, true); + return findDialog(jid, + true + ); } QWidget* PsiAccount::findDialog(const QMetaObject& mo, const Jid& jid, bool compareResource) const @@ -2601,6 +3025,11 @@ void PsiAccount::findDialogs(const QMetaObject& mo, const Jid& jid, bool compare d->findDialogs(mo, jid, compareResource, list); } +void PsiAccount::findDialogs(const QMetaObject& mo, QList* list) const +{ + d->findDialogs(mo, list); +} + void PsiAccount::dialogRegister(QWidget *w, const Jid &j) { d->dialogRegister(w, j); @@ -2614,6 +3043,7 @@ void PsiAccount::dialogUnregister(QWidget *w) void PsiAccount::deleteAllDialogs() { delete d->xmlConsole; + d->xmlConsole = 0; d->deleteDialogList(); } @@ -2630,14 +3060,7 @@ bool PsiAccount::checkConnected(QWidget *par) void PsiAccount::modify() { - AccountModifyDlg *w = findDialog(); - if (w) { - bringToFront(w); - } - else { - w = new AccountModifyDlg(this, 0); - w->show(); - } + d->doModify(); } void PsiAccount::changeVCard() @@ -2732,6 +3155,17 @@ void PsiAccount::featureActivated(QString feature, Jid jid, QString node) } } +void PsiAccount::actionManageBookmarks() +{ + BookmarkManageDlg *dlg = findDialog(); + if(dlg) { + bringToFront(dlg); + } else { + dlg = new BookmarkManageDlg(this); + dlg->show(); + } +} + void PsiAccount::actionJoin(const Jid& mucJid, const QString& password) { actionJoin(ConferenceBookmark(QString(), mucJid, false, QString(), password), @@ -2740,6 +3174,7 @@ void PsiAccount::actionJoin(const Jid& mucJid, const QString& password) void PsiAccount::actionJoin(const ConferenceBookmark& bookmark, bool connectImmediately) { +#ifdef GROUPCHAT MUCJoinDlg* w = new MUCJoinDlg(psi(), this); w->setJid(bookmark.jid()); @@ -2750,30 +3185,36 @@ void PsiAccount::actionJoin(const ConferenceBookmark& bookmark, bool connectImme if (connectImmediately) { w->doJoin(); } +#else + Q_UNUSED(bookmark); + Q_UNUSED(connectImmediately); +#endif } void PsiAccount::stateChanged() { if(loggedIn()) { - d->cp->setState(makeSTATUS(status())); + d->setState(makeSTATUS(status())); } else { if(isActive()) { - d->cp->setState(-1); - if(d->usingSSL) - d->cp->setUsingSSL(true); - else - d->cp->setUsingSSL(false); + d->setState(-1); } else { - d->cp->setState(STATUS_OFFLINE); - d->cp->setUsingSSL(false); + d->setState(STATUS_OFFLINE); } } emit updatedActivity(); } +void PsiAccount::simulateContactOffline(const XMPP::Jid& contact) +{ + foreach(UserListItem* u, findRelevant(contact)) { + simulateContactOffline(u); + } +} + void PsiAccount::simulateContactOffline(UserListItem *u) { UserResourceList rl = u->userResourceList(); @@ -2795,6 +3236,8 @@ void PsiAccount::simulateContactOffline(UserListItem *u) void PsiAccount::simulateRosterOffline() { + emit beginBulkContactUpdate(); + foreach(UserListItem* u, d->userList) simulateContactOffline(u); @@ -2814,6 +3257,13 @@ void PsiAccount::simulateRosterOffline() while (!d->gcbank.isEmpty()) delete d->gcbank.takeFirst(); + + emit endBulkContactUpdate(); +} + +bool PsiAccount::notifyOnline() const +{ + return notifyOnlineOk; } void PsiAccount::enableNotifyOnline() @@ -2826,7 +3276,6 @@ void PsiAccount::enableNotifyOnline() notifyOnlineOk = true; } - void PsiAccount::itemRetracted(const Jid& j, const QString& n, const PubSubRetraction& item) { Q_UNUSED(item); @@ -2958,6 +3407,21 @@ UserListItem *PsiAccount::findFirstRelevant(const Jid &j) const return list.first(); } +PsiContact* PsiAccount::selfContact() const +{ + return d->selfContact; +} + +const QList& PsiAccount::contactList() const +{ + return d->contacts; +} + +PsiContact* PsiAccount::findContact(const Jid& jid) const +{ + return d->findContact(jid); +} + UserListItem *PsiAccount::find(const Jid &j) const { UserListItem *u; @@ -2973,29 +3437,30 @@ void PsiAccount::cpUpdate(const UserListItem &u, const QString &rname, bool from { PsiEvent *e = d->eventQueue->peek(u.jid()); - d->cp->updateEntry(u); - if(e) { - d->cp->setAlert(u.jid(), PsiIconset::instance()->event2icon(e)); + profileSetAlert(u.jid(), PsiIconset::instance()->event2icon(e)); + } + else { + profileClearAlert(u.jid()); } - else - d->cp->clearAlert(u.jid()); - updateContact(u); + profileUpdateEntry(u); + emit updateContact(u); Jid j = u.jid(); - if(!rname.isEmpty()) { + if(!rname.isEmpty()) j = j.withResource(rname); - } - updateContact(j); - updateContact(j, fromPresence); + emit updateContact(j); + emit updateContact(j, fromPresence); d->psi->updateContactGlobal(this, j); } EventDlg *PsiAccount::ensureEventDlg(const Jid &j) { EventDlg *w = findDialog(j); - if(!w) { + if (!w && EventDlg::messagingEnabled()) w = new EventDlg(j, this, true); + + if (w) { connect(w, SIGNAL(aReadNext(const Jid &)), SLOT(processReadNext(const Jid &))); connect(w, SIGNAL(aChat(const Jid &)), SLOT(actionOpenChat2(const Jid&))); connect(w, SIGNAL(aReply(const Jid &, const QString &, const QString &, const QString &)), SLOT(dj_replyMessage(const Jid &, const QString &, const QString &, const QString &))); @@ -3027,6 +3492,10 @@ ChatDlg *PsiAccount::ensureChatDlg(const Jid &j) connect(c, SIGNAL(aVoice(const Jid &)), SLOT(actionVoice(const Jid &))); connect(d->psi, SIGNAL(emitOptionsUpdate()), c, SLOT(optionsUpdate())); connect(this, SIGNAL(updateContact(const Jid &, bool)), c, SLOT(updateContact(const Jid &, bool))); + +#ifdef YAPSI + processChatsHelper(j, false); +#endif } else { // on X11, do a special reparent to open on the right desktop @@ -3061,20 +3530,20 @@ ChatDlg *PsiAccount::ensureChatDlg(const Jid &j) void PsiAccount::changeStatus(int x) { if(x == STATUS_OFFLINE && !PsiOptions::instance()->getOption("options.status.ask-for-message-on-offline").toBool()) { - setStatus(Status(Status::Offline, "Logged out", 0)); + setStatus(loggedOutStatus(), false, true); } else { if(x == STATUS_ONLINE && !PsiOptions::instance()->getOption("options.status.ask-for-message-on-online").toBool()) { - setStatus(Status()); + setStatus(Status(), false, true); } else if(x == STATUS_INVISIBLE){ Status s("","",0,true); s.setIsInvisible(true); - setStatus(s); + setStatus(s, false, true); } else { StatusSetDlg *w = new StatusSetDlg(this, makeStatus(x, "")); - connect(w, SIGNAL(set(const XMPP::Status &, bool)), SLOT(setStatus(const XMPP::Status &, bool))); + connect(w, SIGNAL(set(const XMPP::Status &, bool, bool)), SLOT(setStatus(const XMPP::Status &, bool, bool))); w->show(); } } @@ -3125,6 +3594,7 @@ void PsiAccount::actionVoice(const Jid &j) void PsiAccount::sendFiles(const Jid& j, const QStringList& l, bool direct) { +#ifdef FILETRANSFER Jid j2 = j; if(j.resource().isEmpty()) { UserListItem *u = find(j); @@ -3145,6 +3615,11 @@ void PsiAccount::sendFiles(const Jid& j, const QStringList& l, bool direct) FileRequestDlg *w = new FileRequestDlg(j2, d->psi, this, l, direct); w->show(); } +#else + Q_UNUSED(j); + Q_UNUSED(l); + Q_UNUSED(direct); +#endif } void PsiAccount::actionSendFile(const Jid &j) @@ -3233,6 +3708,8 @@ void PsiAccount::actionRecvRosterExchange(const Jid& j, const RosterExchangeItem void PsiAccount::actionSendMessage(const Jid &j) { EventDlg *w = d->psi->createEventDlg(j.full(), this); + if (!w) + return; w->show(); } @@ -3249,12 +3726,16 @@ void PsiAccount::actionSendMessage(const QList &j) } EventDlg *w = d->psi->createEventDlg(str, this); + if (!w) + return; w->show(); } void PsiAccount::actionSendUrl(const Jid &j) { EventDlg *w = d->psi->createEventDlg(j.full(), this); + if (!w) + return; w->setUrlOnShow(); w->show(); } @@ -3307,6 +3788,9 @@ void PsiAccount::actionHistory(const Jid &j) void PsiAccount::actionHistoryBox(PsiEvent *e) { + if (!EventDlg::messagingEnabled()) + return; + EventDlg *w = new EventDlg(e->from(), this, false); connect(w, SIGNAL(aChat(const Jid &)), SLOT(actionOpenChat2(const Jid&))); connect(w, SIGNAL(aReply(const Jid &, const QString &, const QString &, const QString &)), SLOT(dj_replyMessage(const Jid &, const QString &, const QString &, const QString &))); @@ -3322,15 +3806,14 @@ void PsiAccount::actionHistoryBox(PsiEvent *e) void PsiAccount::actionOpenChat(const Jid &j) { UserListItem *u = find(j); - if(!u) - { - printf("[%s] not in userlist\n", qPrintable(j.full())); + if(!u) { + qWarning("[%s] not in userlist\n", qPrintable(j.full())); return; } // if 'j' is bare, we might want to switch to a specific resource QString res; - if(j.resource().isEmpty()) { + if(u && j.resource().isEmpty()) { // first, are there any queued chats? /*PsiEvent *e = d->eventQueue->peekFirstChat(j, false); if(e) { @@ -3392,11 +3875,21 @@ void PsiAccount::actionOpenChat2(const Jid &_j) actionOpenChat(j); } +void PsiAccount::actionOpenSavedChat(const Jid& j) +{ + openChat(j, FromXml); +} + void PsiAccount::actionOpenChatSpecific(const Jid &j) { openChat(j, UserAction); } +void PsiAccount::actionOpenPassiveChatSpecific(const Jid &j) +{ + openChat(j, UserPassiveAction); +} + #ifdef WHITEBOARDING void PsiAccount::actionOpenWhiteboard(const Jid &j) { @@ -3437,7 +3930,7 @@ void PsiAccount::actionOpenWhiteboardSpecific(const Jid &target, Jid ownJid, boo } #endif -void PsiAccount::actionAgentSetStatus(const Jid &j, Status &s) +void PsiAccount::actionAgentSetStatus(const Jid &j, const Status &s) { if ( j.node().isEmpty() ) // add all transport popups to block list new BlockTransportPopup(d->blockTransportPopupList, j); @@ -3532,6 +4025,27 @@ void PsiAccount::actionGroupRemove(const Jid &j, const QString &g) r->go(true); } +void PsiAccount::actionGroupsSet(const Jid &j, const QStringList &g) +{ + UserListItem *u = d->userList.find(j); + if(!u) + return; + + QStringList g1 = g; + g1.sort(); + QStringList g2 = u->groups(); + g2.sort(); + if (g1 == g2) + return; + + u->setGroups(g); + cpUpdate(*u); + + JT_Roster *r = new JT_Roster(d->client->rootTask()); + r->set(u->jid(), u->name(), u->groups()); + r->go(true); +} + void PsiAccount::actionRegister(const Jid &j) { if(!checkConnected()) @@ -3580,8 +4094,9 @@ void PsiAccount::actionAssignKey(const Jid &j) { if(ensureKey(j)) { UserListItem *u = findFirstRelevant(j); - if(u) + if(u) { cpUpdate(*u); + } } } @@ -3689,7 +4204,7 @@ void PsiAccount::dj_sendMessage(const Message &m, bool log) toggleSecurity(m.to(), m.wasEncrypted()); // don't log groupchat, private messages, or encrypted messages - if(d->acc.opt_log && log) { + if(log) { if(m.type() != "groupchat" && m.xencrypted().isEmpty() && !findGCContact(m.to())) { MessageEvent *me = new MessageEvent(m, this); me->setOriginLocal(true); @@ -3700,8 +4215,8 @@ void PsiAccount::dj_sendMessage(const Message &m, bool log) } // don't sound when sending groupchat messages or message events - if(m.type() != "groupchat" && !m.body().isEmpty()) - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.outgoing-chat").toString()); + if(m.type() != "groupchat" && !m.body().isEmpty() && log) + playSound(eSend); // auto close an open messagebox (if non-chat) if(m.type() != "chat" && !m.body().isEmpty()) { @@ -3717,6 +4232,9 @@ void PsiAccount::dj_sendMessage(const Message &m, bool log) void PsiAccount::dj_newMessage(const Jid &jid, const QString &body, const QString &subject, const QString &thread) { EventDlg *w = d->psi->createEventDlg(jid.full(), this); + if (!w) + return; + if (!body.isEmpty()) { w->setHtml(TextUtil::plain2rich(body)); } @@ -3810,13 +4328,38 @@ void PsiAccount::dj_authReq(const Jid &j) d->client->sendSubscription(j, "subscribe", nick()); } +#ifdef YAPSI +void PsiAccount::dj_authInternal(const Jid &j, bool doAuthReq) +{ + psi()->contactUpdatesManager()->contactAuthorized(this, j); + + d->client->sendSubscription(j, "subscribed"); + if (doAuthReq) { + dj_authReq(j); + } +} +#endif + void PsiAccount::dj_auth(const Jid &j) { +#ifdef YAPSI + UserListItem* u = d->userList.find(j); + if (!u || !u->inList()) { + // TODO: need some sort of central dispatcher for this kind of stuff + emit YaRosterToolTip::instance()->addContact(j, this, QStringList(), QString()); + return; + } + + dj_authInternal(j); +#else + psi()->contactUpdatesManager()->contactAuthorized(this, j); d->client->sendSubscription(j, "subscribed"); +#endif } void PsiAccount::dj_deny(const Jid &j) { + psi()->contactUpdatesManager()->contactDeauthorized(this, j); d->client->sendSubscription(j, "unsubscribed"); } @@ -3854,6 +4397,8 @@ void PsiAccount::dj_rename(const Jid &j, const QString &name) void PsiAccount::dj_remove(const Jid &j) { + psi()->contactUpdatesManager()->contactRemoved(this, j); + UserListItem *u = d->userList.find(j); if(!u) return; @@ -3863,8 +4408,10 @@ void PsiAccount::dj_remove(const Jid &j) updateReadNext(j); // TODO: delete the item immediately (to simulate local change) + // should be done with care though, as the server could refuse + // to delete a contact if(!u->inList()) { - //simulateContactOffline(u); + profileRemoveEntry(u->jid()); d->userList.removeAll(u); delete u; } @@ -3907,16 +4454,43 @@ void PsiAccount::eventFromXml(PsiEvent* e) handleEvent(e, FromXml); } +static bool messageListContainsEvent(const QList& messageList, const MessageEvent* me) +{ + Q_ASSERT(me); +#ifdef YAPSI + YaDateTime timeStamp = getYaDateTime(me); + foreach(const PsiEvent* event, messageList) { + const MessageEvent* me2 = dynamic_cast(event); + if (!me2) + continue; + + YaDateTime timeStamp2 = getYaDateTime(me2); + if (timeStamp2.isNull()) + continue; + + if (timeStamp == timeStamp2) { + return true; + } + } +#else + // FIXME + Q_UNUSED(messageList); +#endif + + return false; +} + // handle an incoming event void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) { - if ( e ) { + if (e && activationType != FromXml) { setEnabled(); } bool doPopup = false; bool putToQueue = true; PsiPopup::PopupType popupType = PsiPopup::AlertNone; + SoundType soundType = eNone; // find someone to accept the event Jid j; @@ -3957,12 +4531,27 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) } //FIXME(KIS): must now cause the event to be recreated from this xml or such. Horrid. #endif - - if(d->acc.opt_log && activationType != FromXml) { - if(e->type() == PsiEvent::Message || e->type() == PsiEvent::Auth) { + + if (d->psi->filterEvent(this, e)) { + delete e; + return; + } + + if (activationType != FromXml) { + if (e->type() == PsiEvent::Message || + e->type() == PsiEvent::Auth + ) + { + bool found = false; + // don't log private messages - if(!findGCContact(e->from()) && !(e->type() == PsiEvent::Message && ((MessageEvent *)e)->message().body().isEmpty())) + if (!found && + !findGCContact(e->from()) && + !(e->type() == PsiEvent::Message && + ((MessageEvent *)e)->message().body().isEmpty())) + { logEvent(e->from(), e); + } } } @@ -3982,7 +4571,12 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) //PluginManager::instance()->message(this,e->from(),ulItem,((MessageEvent*)e)->message().body()); #endif - + QList chatList; + d->eventQueue->extractChats(&chatList, me->from(), false, false); + if (messageListContainsEvent(chatList, me)) { + delete e; + return; + } // Pass message events to chat window if ((m.containsEvents() || m.chatState() != StateNone) && m.body().isEmpty()) { @@ -4009,19 +4603,31 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) //if the chat exists, and is either open in a tab, //or in a window +#ifdef YAPSI_ACTIVEX_SERVER + // since the event could be deleted later in YaOnline::doToasterIgnored() + // we must add the message to the dialog so it will appear in timeline + if( c ) { +#else if( c && ( d->tabManager->isChatTabbed(c) || !c->isHidden() ) ) { +#endif c->incomingMessage(m); - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.chat-message").toString()); + soundType = eChat2; if(PsiOptions::instance()->getOption("options.ui.chat.alert-for-already-open-chats").toBool() && !c->isActiveTab()) { // to alert the chat also, we put it in the queue me->setSentToChatWindow(true); } - else + else { putToQueue = false; +#ifdef YAPSI + if (!d->noPopup(activationType) && PsiOptions::instance()->getOption("options.ui.chat.auto-popup").toBool()) { + openChat(e->from(), activationType); + } +#endif + } } else { bool firstChat = !d->eventQueue->hasChats(e->from()); - playSound(PsiOptions::instance()->getOption(firstChat ? "options.ui.notifications.sounds.new-chat": "options.ui.notifications.sounds.chat-message").toString()); + soundType = firstChat ? eChat1: eChat2; } if (putToQueue) { @@ -4030,17 +4636,18 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) } } // /chat else if (m.type() == "headline") { - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.incoming-headline").toString()); + soundType = eHeadline; doPopup = true; popupType = PsiPopup::AlertHeadline; } // /headline else if (m.type().isEmpty()) { - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.incoming-message").toString()); + soundType = eMessage; doPopup = true; popupType = PsiPopup::AlertMessage; } // /"" - else - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.system-message").toString()); + else { + soundType = eSystem; + } if(m.type() == "error") { // FIXME: handle message errors @@ -4048,15 +4655,15 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) } } else if(e->type() == PsiEvent::HttpAuth) { - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.system-message").toString()); + soundType = eSystem; } else if(e->type() == PsiEvent::File) { - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.incoming-file-transfer").toString()); + soundType = eIncomingFT; doPopup = true; popupType = PsiPopup::AlertFile; } else if(e->type() == PsiEvent::AvCallType) { - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.incoming-file-transfer").toString()); + soundType = eIncomingFT; doPopup = true; popupType = PsiPopup::AlertAvCall; } @@ -4072,14 +4679,29 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) return; } re->setRosterExchangeItems(items); - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.system-message").toString()); + soundType = eSystem; } else if (e->type() == PsiEvent::Auth) { - playSound(PsiOptions::instance()->getOption("options.ui.notifications.sounds.system-message").toString()); + soundType = eSystem; AuthEvent *ae = (AuthEvent *)e; if(ae->authType() == "subscribe") { +#ifdef YAPSI + UserListItem *userListItem = d->userList.find(ae->from()); + Q_ASSERT(dynamic_cast(privacyManager())); + bool isContactBlocked = dynamic_cast(privacyManager())->isContactBlocked(ae->from()); + if (!isContactBlocked) { + soundType = eSubscribe; + } + + if (isContactBlocked) { + dj_deny(ae->from()); + putToQueue = false; + } + else if (userListItem && userListItem->inList()) { +#else if(PsiOptions::instance()->getOption("options.subscriptions.automatically-allow-authorization").toBool()) { +#endif // Check if we want to request auth as well UserListItem *u = d->userList.find(ae->from()); if (!u || (u->subscription().type() != Subscription::Both && u->subscription().type() != Subscription::To)) { @@ -4090,7 +4712,7 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) } putToQueue = false; } - } + } else if(ae->authType() == "subscribed") { if(!PsiOptions::instance()->getOption("options.ui.notifications.successful-subscription").toBool()) putToQueue = false; @@ -4098,6 +4720,13 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) else if(ae->authType() == "unsubscribe") { putToQueue = false; } +#ifdef YAPSI + if (!putToQueue) { + playSound(soundType); + delete e; + return; + } +#endif } else { putToQueue = false; @@ -4126,10 +4755,50 @@ void PsiAccount::handleEvent(PsiEvent* e, ActivationType activationType) emit startBounce(); } +#ifdef YAPSI + int id = -1; + PsiEvent* backupEvent = e->copy(); // FIXME: temporary workaround for braindead queueEvent + if (putToQueue) { + QList ids = GlobalEventQueue::instance()->ids(); + int lastId = ids.isEmpty() ? -1 : ids.last(); + + queueEvent(e, activationType); + + // insurance + ids = GlobalEventQueue::instance()->ids(); + int lastId2 = ids.isEmpty() ? -1 : ids.last(); + if ((lastId2 != lastId) && (lastId2 != -1)) + id = lastId2; + } + else { + psi()->yaUnreadMessagesManager()->eventRead(e); + } + + if (activationType != FromXml) { + PsiEvent* notificationEvent = id == -1 ? backupEvent : e; + bool shouldPlaySound = true; + if (notificationEvent) { + shouldPlaySound = !YaPopupNotification::notify(id, notificationEvent, soundType); + } + + if (shouldPlaySound) { + playSound(soundType); + } + } + delete backupEvent; + + if (!putToQueue) + delete e; +#else + if (soundType >= 0 && activationType != FromXml) { + playSound(soundType); + } + if ( putToQueue ) queueEvent(e, activationType); else delete e; +#endif } UserListItem* PsiAccount::addUserListItem(const Jid& jid, const QString& nick) @@ -4199,13 +4868,18 @@ void PsiAccount::queueEvent(PsiEvent* e, ActivationType activationType) // update the roster cpUpdate(*u); +#ifndef YAPSI + // if we have both (option.popupMsgs || option.popupChats) && option.alertOpenChats, + // then option.alertOpenChats will have NO USER-VISIBLE EFFECT as the + // events will be immediately deleted from the event queue + // FIXME: We shouldn't be doing this kind of stuff here, because this // function is named *queue*Event() not deleteThisMessageSometimes() if (!d->noPopup(activationType)) { bool doPopup = false; // Check to see if we need to popup - if(e->type() == PsiEvent::Message) { + if (e->type() == PsiEvent::Message) { MessageEvent *me = (MessageEvent *)e; const Message &m = me->message(); if (m.type() == "chat") @@ -4218,6 +4892,8 @@ void PsiAccount::queueEvent(PsiEvent* e, ActivationType activationType) else if (e->type() == PsiEvent::File) { doPopup = PsiOptions::instance()->getOption("options.ui.file-transfer.auto-popup").toBool(); } + else if (e->type() == PsiEvent::Auth && !EventDlg::messagingEnabled()) + doPopup = false; else { doPopup = PsiOptions::instance()->getOption("options.ui.message.auto-popup").toBool(); } @@ -4228,8 +4904,10 @@ void PsiAccount::queueEvent(PsiEvent* e, ActivationType activationType) if (u && (!PsiOptions::instance()->getOption("options.ui.notifications.popup-dialogs.suppress-when-not-on-roster").toBool() || u->inList())) openNextEvent(*u, activationType); } - } +#else + Q_UNUSED(activationType); +#endif } // take the next event from the queue and display it @@ -4286,8 +4964,9 @@ int PsiAccount::forwardPendingEvents(const Jid &jid) delete e; // update the contact - if(u) + if(u) { cpUpdate(*u); + } updateReadNext(u->jid()); } @@ -4358,7 +5037,7 @@ void PsiAccount::processReadNext(const UserListItem &u) updateReadNext(u.jid()); } -void PsiAccount::processChats(const Jid &j) +void PsiAccount::processChatsHelper(const Jid& j, bool removeEvents) { //printf("processing chats for [%s]\n", j.full().latin1()); ChatDlg *c = findChatDialog(j); @@ -4367,40 +5046,74 @@ void PsiAccount::processChats(const Jid &j) // extract the chats QList chatList; - d->eventQueue->extractChats(&chatList, j); + bool compareResources = true; +#ifdef YAPSI + compareResources = false; +#endif + d->eventQueue->extractChats(&chatList, j, compareResources, removeEvents); if(!chatList.isEmpty()) { // dump the chats into the chat window, and remove the related cvlist alerts + + // TODO FIXME: Contact's status changes should also be cached in order to + // insert them to chatdlg correctly. + // + // 15:07 *mblsha is Online + // 15:15 *mblsha is Offline + // 15:10 hello! + foreach(PsiEvent *e, chatList) { - MessageEvent *me = (MessageEvent *)e; - const Message &m = me->message(); + if (e->type() == PsiEvent::Message) { + MessageEvent* me = static_cast(e); + const Message &m = me->message(); - // process the message - if(!me->sentToChatWindow()) - c->incomingMessage(m); + // process the message + if(!me->sentToChatWindow()) { + c->incomingMessage(m); + me->setSentToChatWindow(true); + } + } } - while (!chatList.isEmpty()) - delete chatList.takeFirst(); + if (removeEvents) { + while (!chatList.isEmpty()) { + PsiEvent* e = chatList.takeFirst(); +#ifdef YAPSI + psi()->yaUnreadMessagesManager()->eventRead(e); +#endif + delete e; + } - QList ul = findRelevant(j); - if(!ul.isEmpty()) { - UserListItem *u = ul.first(); - cpUpdate(*u); - updateReadNext(u->jid()); + QList ul = findRelevant(j); + if(!ul.isEmpty()) { + UserListItem *u = ul.first(); + cpUpdate(*u); + updateReadNext(u->jid()); + } } } } +void PsiAccount::processChats(const Jid &j) +{ + processChatsHelper(j, true); +} + void PsiAccount::openChat(const Jid& j, ActivationType activationType) { ChatDlg* chat = ensureChatDlg(j); chat->ensureTabbedCorrectly(); - processChats(j); - if (activationType == UserAction) { - chat->bringToFront(); + processChats(j); + chat->bringToFront(true); + } + else if (activationType == UserPassiveAction) { + chat->bringToFront(false); + TabDlg* dlg = chat->getManagingTabDlg(); + if (dlg) { + QTimer::singleShot(1000, dlg, SLOT(showWithoutActivation())); + } } } @@ -4413,6 +5126,9 @@ void PsiAccount::chatMessagesRead(const Jid &j) void PsiAccount::logEvent(const Jid &j, PsiEvent *e) { + if (!d->acc.opt_log) + return; + EDBHandle *h = new EDBHandle(d->psi->edb()); connect(h, SIGNAL(finished()), SLOT(edb_finished())); h->append(j, e); @@ -4426,6 +5142,7 @@ void PsiAccount::edb_finished() void PsiAccount::openGroupChat(const Jid &j, ActivationType activationType) { +#ifdef GROUPCHAT QString str = j.bare(); bool found = false; for(QStringList::ConstIterator it = d->groupchats.begin(); it != d->groupchats.end(); ++it) { @@ -4444,6 +5161,7 @@ void PsiAccount::openGroupChat(const Jid &j, ActivationType activationType) w->ensureTabbedCorrectly(); if (activationType == UserAction) w->bringToFront(); +#endif } bool PsiAccount::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString& pass, bool nohistory) @@ -4473,7 +5191,7 @@ void PsiAccount::groupChatLeave(const QString &host, const QString &room) d->client->groupChatLeave(host, room); } -GCContact *PsiAccount::findGCContact(const Jid &j) +GCContact *PsiAccount::findGCContact(const Jid &j) const { foreach(GCContact *c, d->gcbank) { if(c->jid.compare(j)) @@ -4499,11 +5217,12 @@ QStringList PsiAccount::groupchats() const void PsiAccount::client_groupChatJoined(const Jid &j) { - //d->client->groupChatSetStatus(j.domain(), j.node(), d->loginStatus); +#ifdef GROUPCHAT + //d->client->groupChatSetStatus(j.host(), j.user(), d->loginStatus); GCMainDlg *m = findDialog(Jid(j.bare())); if(m) { - m->setPassword(d->client->groupChatPassword(j.node(),j.domain())); + m->setPassword(d->client->groupChatPassword(j.node(), j.domain())); m->joined(); return; } @@ -4514,6 +5233,7 @@ void PsiAccount::client_groupChatJoined(const Jid &j) // TODO: Correctly handle auto-join groupchats openGroupChat(j, UserAction); +#endif } void PsiAccount::client_groupChatLeft(const Jid &j) @@ -4541,6 +5261,7 @@ void PsiAccount::client_groupChatLeft(const Jid &j) void PsiAccount::client_groupChatPresence(const Jid &j, const Status &s) { +#ifdef GROUPCHAT GCMainDlg *w = findDialog(Jid(j.bare())); if(!w) return; @@ -4564,10 +5285,12 @@ void PsiAccount::client_groupChatPresence(const Jid &j, const Status &s) client_resourceAvailable(j, r); else client_resourceUnavailable(j, j.resource()); +#endif } void PsiAccount::client_groupChatError(const Jid &j, int code, const QString &str) { +#ifdef GROUPCHAT GCMainDlg *w = findDialog(Jid(j.bare())); if(w) { w->error(code, str); @@ -4578,6 +5301,7 @@ void PsiAccount::client_groupChatError(const Jid &j, int code, const QString &st w->error(code, str); } } +#endif } QStringList PsiAccount::hiddenChats(const Jid &j) const @@ -4605,12 +5329,20 @@ void PsiAccount::slotCheckVCard() d->nickFromVCard = true; nick = j->vcard().fullName(); } +#ifdef YAPSI + else if (!j->vcard().fullName().isEmpty()) { + d->nickFromVCard = true; + nick = j->vcard().fullName(); + } +#endif } +#ifndef YAPSI if (j->vcard().isEmpty()) { changeVCard(); return; } +#endif if (!j->vcard().photo().isEmpty()) { d->vcardPhotoUpdate(j->vcard().photo()); @@ -4653,6 +5385,7 @@ QString PsiAccount::nick() const void PsiAccount::pgpKeysUpdated() { +#ifdef HAVE_PGPUTIL // are there any sigs that need verifying? foreach(UserListItem* u, d->userList) { UserResourceList &rl = u->userResourceList(); @@ -4665,6 +5398,9 @@ void PsiAccount::pgpKeysUpdated() } } } +#else + Q_ASSERT(false); +#endif } void PsiAccount::trySignPresence() @@ -4684,6 +5420,7 @@ void PsiAccount::trySignPresence() void PsiAccount::pgp_signFinished() { +#ifdef HAVE_PGPUTIL PGPTransaction *t = (PGPTransaction*) sender(); if (t->success()) { Status s = d->loginStatus; @@ -4702,21 +5439,26 @@ void PsiAccount::pgp_signFinished() .arg(PGPUtil::instance().messageErrorString(t->errorCode())), t->diagnosticText()); - logout(); + logout(false, loggedOutStatus()); return; } t->deleteLater(); +#else + Q_ASSERT(false); +#endif } void PsiAccount::verifyStatus(const Jid &j, const Status &s) { +#ifdef HAVE_PGPUTIL PGPTransaction *t = new PGPTransaction(new QCA::OpenPGP()); t->setJid(j); connect(t, SIGNAL(finished()), SLOT(pgp_verifyFinished())); t->startVerify(PGPUtil::instance().addHeaderFooter(s.xsigned(),1).toUtf8()); t->update(s.status().toUtf8()); t->end(); +#endif } @@ -4754,6 +5496,7 @@ void PsiAccount::pgp_verifyFinished() int PsiAccount::sendMessageEncrypted(const Message &_m) { +#ifdef HAVE_PGPUTIL if(!ensureKey(_m.to())) return -1; @@ -4775,17 +5518,22 @@ int PsiAccount::sendMessageEncrypted(const Message &_m) t->end(); return t->id(); +#else + Q_ASSERT(false); + return -1; +#endif } void PsiAccount::pgp_encryptFinished() { +#ifdef HAVE_PGPUTIL PGPTransaction *pt = (PGPTransaction *)sender(); int x = pt->id(); if(pt->success()) { Message m = pt->message(); // log the message here, before we encrypt it - if(d->acc.opt_log) { + { MessageEvent *me = new MessageEvent(m, this); me->setOriginLocal(true); me->setTimeStamp(QDateTime::currentDateTime()); @@ -4811,11 +5559,13 @@ void PsiAccount::pgp_encryptFinished() } emit encryptedMessageSent(x, pt->success(), pt->errorCode(), pt->diagnosticText()); pt->deleteLater(); +#endif } void PsiAccount::processEncryptedMessage(const Message &m) { +#ifdef HAVE_PGPUTIL PGPTransaction *t = new PGPTransaction(new QCA::OpenPGP()); t->setMessage(m); connect(t, SIGNAL(finished()), SLOT(pgp_decryptFinished())); @@ -4823,6 +5573,7 @@ void PsiAccount::processEncryptedMessage(const Message &m) t->startDecrypt(); t->update(PGPUtil::instance().addHeaderFooter(m.xencrypted(),0).toUtf8()); t->end(); +#endif } @@ -4868,11 +5619,13 @@ void PsiAccount::processMessageQueue() while(!d->messageQueue.isEmpty()) { Message *mp = d->messageQueue.first(); +#ifdef HAVE_PGPUTIL // encrypted? if(PGPUtil::instance().pgpAvailable() && !mp->xencrypted().isEmpty()) { processEncryptedMessageNext(); break; } +#endif processIncomingMessage(*mp); d->messageQueue.removeAll(mp); @@ -4899,7 +5652,7 @@ void PsiAccount::processEncryptedMessageDone() void PsiAccount::optionsUpdate() { - d->cp->updateEntry(d->self); + profileUpdateEntry(d->self); // Tune #ifdef USE_PEP @@ -4949,13 +5702,15 @@ void PsiAccount::setSendChatState(bool b) { if (b && !d->client->extensions().contains("cs")) { d->client->addExtension("cs",Features("http://jabber.org/protocol/chatstates")); - if (isConnected()) + if (isConnected()) { setStatusActual(d->loginStatus); + } } else if (!b && d->client->extensions().contains("cs")) { d->client->removeExtension("cs"); - if (isConnected()) + if (isConnected()) { setStatusActual(d->loginStatus); + } } } @@ -5062,12 +5817,14 @@ void PsiAccount::toggleSecurity(const Jid &j, bool b) } } - if(needUpdate) + if(needUpdate) { cpUpdate(*u); + } } bool PsiAccount::ensureKey(const Jid &j) { +#ifdef HAVE_PGPUTIL if(!PGPUtil::instance().pgpAvailable()) return false; @@ -5114,6 +5871,9 @@ bool PsiAccount::ensureKey(const Jid &j) } return true; +#else + return false; +#endif } ServerInfoManager* PsiAccount::serverInfoManager() @@ -5131,6 +5891,19 @@ BookmarkManager* PsiAccount::bookmarkManager() return d->bookmarkManager; } +QStringList PsiAccount::groupList() const +{ + return d->groupList(); +} + +/** + * FIXME: Make InfoDlg use some other way of updating items. + */ +void PsiAccount::updateEntry(const UserListItem& u) +{ + profileUpdateEntry(u); +} + AvCallManager *PsiAccount::avCallManager() { return d->avCallManager; @@ -5146,5 +5919,70 @@ QList PsiAccount::dumpRingbuf() return d->dumpRingbuf(); } +/** + * Frees ringbuffer memory and makes it compact. + */ +void PsiAccount::clearRingbuf() +{ + d->xmlRingbuf.clear(); + d->xmlRingbufWrite = 0; + d->xmlRingbuf.resize(10); +} + +/** + * Helper to prevent automated outgoing presences from happening + */ +void PsiAccount::resetLastManualStatusSafeGuard() +{ + d->setManualStatus(XMPP::Status()); +} + +ContactProfile *PsiAccount::contactProfile() const +{ + return 0; +} + +bool PsiAccount::usingSSL() const +{ + return d->usingSSL; +} + +void PsiAccount::profileUpdateEntry(const UserListItem& u) +{ + d->updateEntry(u); +} + +void PsiAccount::profileRemoveEntry(const Jid& jid) +{ + d->removeEntry(jid); +} + +void PsiAccount::profileAnimateNick(const Jid& jid) +{ + d->animateNick(jid); +} + +void PsiAccount::profileSetAlert(const Jid& jid, const PsiIcon* icon) +{ + d->setAlert(jid, icon); +} + +void PsiAccount::profileClearAlert(const Jid& jid) +{ + d->clearAlert(jid); +} + +bool PsiAccount::alerting() const +{ + return d->alerting(); +} + +QIcon PsiAccount::alertPicture() const +{ + Q_ASSERT(alerting()); + if (!alerting()) + return QIcon(); + return d->currentAlertFrame(); +} #include "psiaccount.moc" diff --git a/src/psiaccount.h b/src/psiaccount.h index 244b0dff3..cc43f9777 100644 --- a/src/psiaccount.h +++ b/src/psiaccount.h @@ -52,6 +52,7 @@ namespace XMPP using namespace XMPP; class PsiCon; +class PsiContact; class PsiContactList; class PsiAccount; class PsiEvent; @@ -84,6 +85,8 @@ class TabManager; #ifdef GOOGLE_FT class GoogleFileTransfer; #endif +class PsiIcon; +class QIcon; // sick sick remove this someday please! struct GCContact; @@ -93,9 +96,13 @@ class AvCallManager; class PsiAccount : public QObject { Q_OBJECT -public: +protected: PsiAccount(const UserAccount &acc, PsiContactList *parent, CapsRegistry* capsRegistry, TabManager *tabManager); - ~PsiAccount(); + virtual void init(); + +public: + static PsiAccount* create(const UserAccount &acc, PsiContactList *parent, CapsRegistry* capsRegistry, TabManager *tabManager); + virtual ~PsiAccount(); bool enabled() const; void setEnabled(bool e = TRUE); @@ -104,15 +111,19 @@ class PsiAccount : public QObject bool isActive() const; bool isConnected() const; const QString &name() const; + const QString &id() const; + + bool alerting() const; + QIcon alertPicture() const; const UserAccount & userAccount() const; UserAccount accountOptions() const; - void setUserAccount(const UserAccount &); + virtual void setUserAccount(const UserAccount &); const Jid & jid() const; QString nameWithJid() const; XMPP::Client *client() const; - ContactProfile *contactProfile() const; + virtual ContactProfile *contactProfile() const; EventQueue *eventQueue() const; EDB *edb() const; PsiCon *psi() const; @@ -121,13 +132,13 @@ class PsiAccount : public QObject CapsManager* capsManager() const; VoiceCaller* voiceCaller() const; Status status() const; - void setStatusDirect(const Status &, bool withPriority = false); - void setStatusActual(const Status &); - void login(); - void logout(bool fast=false, const Status &s = Status("", "Logged out", 0, false)); + static Status loggedOutStatus(); + void setStatusDirect(const XMPP::Status &, bool withPriority = false); + void setStatusActual(const XMPP::Status &); bool loggedIn() const; void setNick(const QString &); QString nick() const; + void forceDisconnect(bool fast, const XMPP::Status &s); bool hasPGP() const; QHostAddress *localAddress() const; void passwordReady(QString password); @@ -146,10 +157,19 @@ class PsiAccount : public QObject reinterpret_cast*>(&list)); return list; } + template + inline QList findAllDialogs() const { + QList list; + findDialogs(((T)0)->staticMetaObject, + reinterpret_cast*>(&list)); + return list; + } void dialogRegister(QWidget* w, const Jid& jid = Jid()); void dialogUnregister(QWidget* w); + bool notifyOnline() const; + void modify(); void changeVCard(); void changePW(); @@ -165,20 +185,44 @@ class PsiAccount : public QObject void groupChatChangeNick(const QString &host, const QString &room, const QString& nick, const Status &); void groupChatLeave(const QString &host, const QString &room); + PsiContact* selfContact() const; + const QList& contactList() const; + PsiContact* findContact(const Jid& jid) const; UserListItem *find(const Jid &) const; QList findRelevant(const Jid &) const; UserListItem *findFirstRelevant(const Jid &) const; UserList *userList() const; + bool usingSSL() const; bool checkConnected(QWidget *parent=0); - void playSound(const QString &); + + enum SoundType { + eNone = -1, + + eMessage = 0, + eChat1, + eChat2, + eHeadline, + eSystem, + eOnline, + eOffline, + eSend, + eIncomingFT, + eFTComplete, +#ifdef YAPSI + eSubscribe, + eUnsubscribe, +#endif + eSoundLast + }; + void playSound(SoundType onevent); QStringList hiddenChats(const Jid &) const; int sendMessageEncrypted(const Message &); // sucks sucks sucks sucks sucks sucks sucks - GCContact *findGCContact(const Jid &j); + GCContact *findGCContact(const Jid &j) const; XMPP::Status gcContactStatus(const Jid &j); QStringList groupchats() const; @@ -186,7 +230,7 @@ class PsiAccount : public QObject bool ensureKey(const Jid &); void tryVerify(UserListItem *, UserResource *); - static void getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QCATLSHandler *tlsHandler, QString *_str, bool *_reconn); + static void getErrorInfo(int err, AdvancedConnector *conn, Stream *stream, QCATLSHandler *tlsHandler, QString *_str, bool *_reconn, bool *_disableAutoConnect, bool *_isAuthError, bool *_isTemporaryAuthFailure); void deleteQueueFile(); void sendFiles(const Jid&, const QStringList&, bool direct = false); @@ -196,11 +240,18 @@ class PsiAccount : public QObject BookmarkManager* bookmarkManager(); AvCallManager *avCallManager(); + void clearCurrentConnectionError(); + QString currentConnectionError() const; + int currentConnectionErrorCondition() const; + enum xmlRingType {RingXmlIn, RingXmlOut, RingSysMsg}; class xmlRingElem { public: int type; QDateTime time; QString xml; }; QList< xmlRingElem > dumpRingbuf(); + void clearRingbuf(); signals: + void accountDestroyed(); + void connectionError(const QString& errorInfo); void disconnected(); void reconnecting(); void updatedActivity(); @@ -209,6 +260,7 @@ class PsiAccount : public QObject void updateContact(const UserListItem &); void updateContact(const Jid &); void updateContact(const Jid &, bool); + void removeContact(const Jid &); void nickChanged(); void pgpKeyChanged(); void encryptedMessageSent(int, bool, int, const QString &); @@ -216,7 +268,7 @@ class PsiAccount : public QObject void startBounce(); public slots: - void setStatus(const XMPP::Status &, bool withStatus = false); + void setStatus(const XMPP::Status &, bool withPriority = false, bool isManualStatus = false); void capsChanged(const Jid&); void tuneStopped(); @@ -263,13 +315,15 @@ public slots: void actionGroupRename(const QString &, const QString &); void actionHistory(const Jid &); void actionOpenChat(const Jid &); + void actionOpenSavedChat(const Jid &); void actionOpenChat2(const Jid &); void actionOpenChatSpecific(const Jid &); + void actionOpenPassiveChatSpecific(const Jid &); #ifdef WHITEBOARDING void actionOpenWhiteboard(const Jid &); void actionOpenWhiteboardSpecific(const Jid &, Jid = Jid(), bool = false); #endif - void actionAgentSetStatus(const Jid &, Status &s); + void actionAgentSetStatus(const Jid &, const Status &s); void actionInfo(const Jid &, bool showStatusInfo=true); void actionAuth(const Jid &); void actionAuthRequest(const Jid &); @@ -277,9 +331,11 @@ public slots: void actionAdd(const Jid &); void actionGroupAdd(const Jid &, const QString &); void actionGroupRemove(const Jid &, const QString &); + void actionGroupsSet(const Jid &, const QStringList &); void actionHistoryBox(PsiEvent *); void actionRegister(const Jid &); void actionSearch(const Jid &); + void actionManageBookmarks(); void actionJoin(const Jid& mucJid, const QString& password = QString()); void actionJoin(const ConferenceBookmark& bookmark, bool connectImmediately); void actionDisco(const Jid &, const QString &); @@ -374,19 +430,43 @@ private slots: void setRCEnabled(bool); void sessionStarted(); + virtual void stateChanged(); + virtual void profileUpdateEntry(const UserListItem& u); + virtual void profileRemoveEntry(const Jid& jid); + virtual void profileAnimateNick(const Jid& jid); + virtual void profileSetAlert(const Jid& jid, const PsiIcon* icon); + virtual void profileClearAlert(const Jid& jid); + private slots: void eventFromXml(PsiEvent* e); + void simulateContactOffline(const XMPP::Jid& contact); private: void handleEvent(PsiEvent* e, ActivationType activationType); +public: + QStringList groupList() const; + void updateEntry(const UserListItem& u); + + void resetLastManualStatusSafeGuard(); + +signals: + void addedContact(PsiContact*); + void removedContact(PsiContact*); + + void beginBulkContactUpdate(); + void endBulkContactUpdate(); + void rosterRequestFinished(); + public: class Private; private: Private *d; + void login(); + void logout(bool fast, const Status &s); + void deleteAllDialogs(); - void stateChanged(); void simulateContactOffline(UserListItem *); void simulateRosterOffline(); void cpUpdate(const UserListItem &, const QString &rname="", bool fromPresence=false); @@ -403,7 +483,10 @@ private slots: void processEncryptedMessageNext(); void processEncryptedMessageDone(); void verifyStatus(const Jid &j, const Status &s); + void sentInitialPresence(); + void requestAvatarsForAllContacts(); + void processChatsHelper(const Jid& jid, bool removeEvents); void processChats(const Jid &); void openChat(const Jid &, ActivationType activationType); EventDlg *ensureEventDlg(const Jid &); @@ -414,6 +497,7 @@ private slots: QWidget* findDialog(const QMetaObject& mo, const Jid& jid, bool compareResource) const; void findDialogs(const QMetaObject& mo, const Jid& jid, bool compareResource, QList* list) const; + void findDialogs(const QMetaObject& mo, QList* list) const; friend class Private; }; diff --git a/src/psiactions.h b/src/psiactions.h index 53e094fd0..56baa5344 100644 --- a/src/psiactions.h +++ b/src/psiactions.h @@ -22,9 +22,10 @@ #define PSIACTIONS_H enum ActivationType { - UserAction = 0, - IncomingStanza, - FromXml + UserAction = 0, // action is triggered by user + UserPassiveAction, // action is triggered by user, but should be performed in background + IncomingStanza, // action is triggered by incoming stanza + FromXml // action is triggered by restoring saved events from XML }; #endif diff --git a/src/psicon.cpp b/src/psicon.cpp index da0db7f17..8830e4ff0 100644 --- a/src/psicon.cpp +++ b/src/psicon.cpp @@ -55,7 +55,9 @@ #include "mucjoindlg.h" #include "userlist.h" #include "eventdlg.h" +#ifdef HAVE_PGPUTIL #include "pgputil.h" +#endif #include "eventdb.h" #include "proxy.h" #ifdef PSIMNG @@ -64,8 +66,10 @@ #include "alerticon.h" #include "iconselect.h" #include "psitoolbar.h" +#ifdef FILETRANSFER #include "filetransfer.h" #include "filetransdlg.h" +#endif #include "accountmodifydlg.h" #include "psiactionlist.h" #include "applicationinfo.h" @@ -88,12 +92,14 @@ #include "globalshortcutmanager.h" #include "desktoputil.h" #include "tabmanager.h" +#include "xmpp_xmlcommon.h" +#include "psicontact.h" +#include "contactupdatesmanager.h" #include "capsmanager.h" #include "avcall/avcall.h" #include "avcall/calldlg.h" #include "alertmanager.h" - #include "AutoUpdater/AutoUpdater.h" #ifdef HAVE_SPARKLE #include "AutoUpdater/SparkleAutoUpdater.h" @@ -191,7 +197,11 @@ class PsiCon::Private : public QObject Q_OBJECT public: Private(PsiCon *parent) - : contactList(0), iconSelect(0), alertManager(parent) + : QObject(parent) + , contactList(0) + , iconSelect(0) + , quitting(false) + , alertManager(parent) { psi = parent; } @@ -257,13 +267,17 @@ private slots: S5BServer *s5bServer; ProxyManager *proxy; IconSelectPopup *iconSelect; +#ifdef FILETRANSFER FileTransDlg *ftwin; +#endif PsiActionList *actionList; //GlobalAccelManager *globalAccelManager; TuneController* tuneController; QMenuBar* defaultMenuBar; CapsRegistry* capsRegistry; TabManager *tabManager; + bool quitting; + QTimer* updatedAccountTimer_; AutoUpdater *autoUpdater; AlertManager alertManager; }; @@ -273,17 +287,19 @@ private slots: //---------------------------------------------------------------------------- PsiCon::PsiCon() -:QObject(0) + : QObject(0) { //pdb(DEBUG_JABCON, QString("%1 v%2\n By Justin Karneges\n infiniti@affinix.com\n\n").arg(PROG_NAME).arg(PROG_VERSION)); - d = new Private(this); d->tabManager = new TabManager(this); + connect(QCoreApplication::instance(), SIGNAL(aboutToQuit()), SLOT(aboutToQuit())); + d->mainwin = 0; +#ifdef FILETRANSFER d->ftwin = 0; +#endif - d->eventId = 0; d->edb = new EDBFlatFile; d->s5bServer = 0; @@ -293,6 +309,7 @@ PsiCon::PsiCon() d->actionList = 0; d->defaultMenuBar = new QMenuBar(0); + d->capsRegistry = new CapsRegistry(); connect(d->capsRegistry, SIGNAL(registered(const CapsSpec&)), SLOT(saveCapabilities())); } @@ -320,8 +337,10 @@ bool PsiCon::init() connect(qApp, SIGNAL(forceSavePreferences()), SLOT(forceSavePreferences())); +#ifdef HAVE_PGPUTIL // PGP initialization (needs to be before any gpg usage!) PGPUtil::instance(); +#endif d->contactList = new PsiContactList(this); @@ -330,6 +349,12 @@ bool PsiCon::init() connect(d->contactList, SIGNAL(accountCountChanged()), SIGNAL(accountCountChanged())); connect(d->contactList, SIGNAL(accountActivityChanged()), SIGNAL(accountActivityChanged())); connect(d->contactList, SIGNAL(saveAccounts()), SLOT(saveAccounts())); + connect(d->contactList, SIGNAL(queueChanged()), SLOT(queueChanged())); + + d->updatedAccountTimer_ = new QTimer(this); + d->updatedAccountTimer_->setSingleShot(true); + d->updatedAccountTimer_->setInterval(1000); + connect(d->updatedAccountTimer_, SIGNAL(timeout()), SLOT(saveAccounts())); // do some backuping in case we are about to start migration from config.xml+options.xml // to options.xml only. @@ -386,8 +411,8 @@ bool PsiCon::init() d->optionsMigration.fromFile(pathToProfileConfig(activeProfile)); //load the new profile - //Save every time an option is changed options->load(optionsFile()); + //Save every time an option is changed options->autoSave(true, optionsFile()); //just set a dummy option to trigger saving @@ -417,6 +442,9 @@ bool PsiCon::init() connect(options, SIGNAL(optionChanged(const QString&)), SLOT(optionChanged(const QString&))); + + contactUpdatesManager_ = new ContactUpdatesManager(this); + QDir profileDir( pathToProfile( activeProfile ) ); profileDir.rmdir( "info" ); // remove unused dir @@ -435,14 +463,16 @@ bool PsiCon::init() if ( !d->actionList ) d->actionList = new PsiActionList( this ); - new PsiConObject(this); + PsiConObject* psiConObject = new PsiConObject(this); Anim::setMainThread(QThread::currentThread()); // setup the main window - d->mainwin = new MainWin(PsiOptions::instance()->getOption("options.ui.contactlist.always-on-top").toBool(), (PsiOptions::instance()->getOption("options.ui.systemtray.enable").toBool() && PsiOptions::instance()->getOption("options.contactlist.use-toolwindow").toBool()), this); + d->mainwin = new MainWin(PsiOptions::instance()->getOption("options.ui.contactlist.always-on-top").toBool(), (PsiOptions::instance()->getOption("options.ui.systemtray.enable").toBool() && PsiOptions::instance()->getOption("options.contactlist.use-toolwindow").toBool()), this); d->mainwin->setUseDock(PsiOptions::instance()->getOption("options.ui.systemtray.enable").toBool()); + Q_UNUSED(psiConObject); + connect(d->mainwin, SIGNAL(closeProgram()), SLOT(closeProgram())); connect(d->mainwin, SIGNAL(changeProfile()), SLOT(changeProfile())); connect(d->mainwin, SIGNAL(doManageAccounts()), SLOT(doManageAccounts())); @@ -455,14 +485,19 @@ bool PsiCon::init() connect(d->mainwin, SIGNAL(recvNextEvent()), SLOT(recvNextEvent())); connect(this, SIGNAL(emitOptionsUpdate()), d->mainwin, SLOT(optionsUpdate())); +#ifndef NEWCONTACTLIST connect(this, SIGNAL(emitOptionsUpdate()), d->mainwin->cvlist, SLOT(optionsUpdate())); +#endif + d->mainwin->setGeometryOptionPath("options.ui.contactlist.saved-window-geometry"); if(!(PsiOptions::instance()->getOption("options.ui.systemtray.enable").toBool() && PsiOptions::instance()->getOption("options.contactlist.hide-on-start").toBool())) d->mainwin->show(); +#ifdef FILETRANSFER d->ftwin = new FileTransDlg(this); +#endif d->idle.start(); @@ -482,7 +517,7 @@ bool PsiCon::init() // Global shortcuts setShortcuts(); - + // FIXME #ifdef __GNUC__ #warning "Temporary hard-coding caps registration of own version" @@ -490,13 +525,17 @@ bool PsiCon::init() // client()->identity() registerCaps(ApplicationInfo::capsVersion(), QStringList() +#ifdef FILETRANSFER << "http://jabber.org/protocol/bytestreams" << "http://jabber.org/protocol/si" << "http://jabber.org/protocol/si/profile/file-transfer" +#endif << "http://jabber.org/protocol/disco#info" << "http://jabber.org/protocol/commands" << "http://jabber.org/protocol/rosterx" +#ifdef GROUPCHAT << "http://jabber.org/protocol/muc" +#endif << "jabber:x:data" ); @@ -505,8 +544,8 @@ bool PsiCon::init() << "http://jabber.org/protocol/tune" << "http://jabber.org/protocol/physloc" << "http://jabber.org/protocol/geoloc" - << "urn:xmpp:avatar:data" - << "urn:xmpp:avatar:metadata" + << "urn:xmpp:avatar:data" + << "urn:xmpp:avatar:metadata" ); registerCaps("ep-notify-2", QStringList() @@ -514,13 +553,14 @@ bool PsiCon::init() << "http://jabber.org/protocol/tune+notify" << "http://jabber.org/protocol/physloc+notify" << "http://jabber.org/protocol/geoloc+notify" - << "urn:xmpp:avatar:metadata+notify" + << "urn:xmpp:avatar:metadata+notify" ); registerCaps("html", QStringList("http://jabber.org/protocol/xhtml-im")); registerCaps("cs", QStringList("http://jabber.org/protocol/chatstates")); - //I've commented out the automatic replies, so commenting out support as well - KIS +#ifdef YAPSI registerCaps("mr", QStringList("urn:xmpp:receipts")); +#endif // load accounts { @@ -561,12 +601,11 @@ bool PsiCon::init() checkAccountsEmpty(); - // try autologin if needed foreach(PsiAccount* account, d->contactList->accounts()) { account->autoLogin(); } - + // show tip of the day if ( PsiOptions::instance()->getOption("options.ui.tip.show").toBool() ) { TipDlg::show(this); @@ -630,7 +669,9 @@ void PsiCon::deinit() // delete s5b server delete d->s5bServer; +#ifdef FILETRANSFER delete d->ftwin; +#endif if(d->mainwin) { delete d->mainwin; @@ -659,15 +700,9 @@ void PsiCon::setShortcuts() ShortcutManager::connect("global.toggle-visibility", d->mainwin, SLOT(toggleVisible())); ShortcutManager::connect("global.bring-to-front", d->mainwin, SLOT(trayShow())); ShortcutManager::connect("global.new-blank-message", this, SLOT(doNewBlankMessage())); - -} - -ContactView *PsiCon::contactView() const -{ - if(d->mainwin) - return d->mainwin->cvlist; - else - return 0; +#ifdef YAPSI + ShortcutManager::connect("global.filter-contacts", d->mainwin, SLOT(filterContacts())); +#endif } PsiContactList* PsiCon::contactList() const @@ -687,7 +722,16 @@ ProxyManager *PsiCon::proxy() const FileTransDlg *PsiCon::ftdlg() const { +#ifdef FILETRANSFER return d->ftwin; +#else + return 0; +#endif +} + +TabManager *PsiCon::tabManager() const +{ + return d->tabManager; } TuneController *PsiCon::tuneController() const @@ -702,7 +746,7 @@ AlertManager *PsiCon::alertManager() const { void PsiCon::closeProgram() { - quit(QuitProgram); + doQuit(QuitProgram); } void PsiCon::changeProfile() @@ -717,10 +761,10 @@ void PsiCon::changeProfile() if (messageBox.clickedButton() == cancel) return; - setStatusFromDialog(XMPP::Status::Offline, false); + setStatusFromDialog(XMPP::Status::Offline, false, true); } - quit(QuitProfile); + doQuit(QuitProfile); } void PsiCon::doManageAccounts() @@ -747,12 +791,14 @@ void PsiCon::doManageAccounts() void PsiCon::doGroupChat() { +#ifdef GROUPCHAT PsiAccount *account = d->contactList->defaultAccount(); if(!account) return; MUCJoinDlg *w = new MUCJoinDlg(this, account); w->show(); +#endif } void PsiCon::doNewBlankMessage() @@ -762,6 +808,9 @@ void PsiCon::doNewBlankMessage() return; EventDlg *w = createEventDlg("", account); + if (!w) + return; + w->show(); } @@ -769,6 +818,9 @@ void PsiCon::doNewBlankMessage() // call optionsUpdate() automatically. EventDlg *PsiCon::createEventDlg(const QString &to, PsiAccount *pa) { + if (!EventDlg::messagingEnabled()) + return 0; + EventDlg *w = new EventDlg(to, this, pa); connect(this, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate())); return w; @@ -803,7 +855,6 @@ QMenuBar* PsiCon::defaultMenuBar() const return d->defaultMenuBar; } - void PsiCon::dialogRegister(QWidget *w) { item_dialog *i = new item_dialog; @@ -843,13 +894,14 @@ AccountsComboBox *PsiCon::accountsComboBox(QWidget *parent, bool online_only) return acb; } -void PsiCon::createAccount(const QString &name, const Jid &j, const QString &pass, bool opt_host, const QString &host, int port, bool legacy_ssl_probe, UserAccount::SSLFlag ssl, QString proxy, const QString &tlsOverrideDomain, const QByteArray &tlsOverrideCert) +PsiAccount* PsiCon::createAccount(const QString &name, const Jid &j, const QString &pass, bool opt_host, const QString &host, int port, bool legacy_ssl_probe, UserAccount::SSLFlag ssl, QString proxy, const QString &tlsOverrideDomain, const QByteArray &tlsOverrideCert) { - d->contactList->createAccount(name, j, pass, opt_host, host, port, legacy_ssl_probe, ssl, proxy, tlsOverrideDomain, tlsOverrideCert); + return d->contactList->createAccount(name, j, pass, opt_host, host, port, legacy_ssl_probe, ssl, proxy, tlsOverrideDomain, tlsOverrideCert); } -PsiAccount *PsiCon::createAccount(const UserAccount& acc) +PsiAccount *PsiCon::createAccount(const UserAccount& _acc) { + UserAccount acc = _acc; PsiAccount *pa = new PsiAccount(acc, d->contactList, d->capsRegistry, d->tabManager); connect(&d->idle, SIGNAL(secondsIdle(int)), pa, SLOT(secondsIdle(int))); connect(pa, SIGNAL(updatedActivity()), SLOT(pa_updatedActivity())); @@ -869,66 +921,125 @@ void PsiCon::removeAccount(PsiAccount *pa) void PsiCon::statusMenuChanged(int x) { +#ifndef YAPSI if(x == STATUS_OFFLINE && !PsiOptions::instance()->getOption("options.status.ask-for-message-on-offline").toBool()) { - setGlobalStatus(Status(Status::Offline, "Logged out", 0)); + setGlobalStatus(PsiAccount::loggedOutStatus(), false, true); if(PsiOptions::instance()->getOption("options.ui.systemtray.enable").toBool() == true) d->mainwin->setTrayToolTip(Status(Status::Offline, "", 0)); } else { if(x == STATUS_ONLINE && !PsiOptions::instance()->getOption("options.status.ask-for-message-on-online").toBool()) { - setGlobalStatus(Status()); + setGlobalStatus(Status(), false, true); if(PsiOptions::instance()->getOption("options.ui.systemtray.enable").toBool() == true) d->mainwin->setTrayToolTip(Status()); } else if(x == STATUS_INVISIBLE){ Status s("","",0,true); s.setIsInvisible(true); - setGlobalStatus(s); + setGlobalStatus(s, false, true); if(PsiOptions::instance()->getOption("options.ui.systemtray.enable").toBool() == true) d->mainwin->setTrayToolTip(s); } else { // Create a dialog with the last status message - StatusSetDlg *w = new StatusSetDlg(this, makeStatus(x, PsiOptions::instance()->getOption("options.status.last-message").toString())); - connect(w, SIGNAL(set(const XMPP::Status &, bool)), SLOT(setStatusFromDialog(const XMPP::Status &, bool))); + StatusSetDlg *w = new StatusSetDlg(this, makeStatus(x, currentStatusMessage())); + connect(w, SIGNAL(set(const XMPP::Status &, bool, bool)), SLOT(setStatusFromDialog(const XMPP::Status &, bool, bool))); connect(w, SIGNAL(cancelled()), SLOT(updateMainwinStatus())); if(PsiOptions::instance()->getOption("options.ui.systemtray.enable").toBool() == true) - connect(w, SIGNAL(set(const XMPP::Status &, bool)), d->mainwin, SLOT(setTrayToolTip(const XMPP::Status &, bool))); + connect(w, SIGNAL(set(const XMPP::Status &, bool, bool)), d->mainwin, SLOT(setTrayToolTip(const XMPP::Status &, bool, bool))); w->show(); } } +#else + setGlobalStatus(makeStatus(x, currentStatusMessage()), false, true); +#endif +} + +XMPP::Status::Type PsiCon::currentStatusType() const +{ +#ifdef YAPSI + if (!d->mainwin) + return XMPP::Status::Offline; + return d->mainwin->statusType(); +#else + bool active = false; + bool loggedIn = false; + XMPP::Status::Type state = XMPP::Status::Online; + foreach(PsiAccount* account, d->contactList->enabledAccounts()) { + if(account->isActive()) + active = true; + if(account->loggedIn()) { + loggedIn = true; + state = account->status().type(); + } + } + + if (!loggedIn) { + state = XMPP::Status::Offline; + } + + return state; +#endif } -void PsiCon::setStatusFromDialog(const Status &s, bool withPriority) +XMPP::Status::Type PsiCon::lastLoggedInStatusType() const { - PsiOptions::instance()->setOption("options.status.last-message", s.status()); - setGlobalStatus(s, withPriority); +#ifdef YAPSI + return d->mainwin->lastLoggedInStatusType(); +#else +// FIXME: Has to use status type from global status type selector + return XMPP::Status::Online; +#endif +} + +QString PsiCon::currentStatusMessage() const +{ +#ifdef YAPSI + if (!d->mainwin) + return QString(); + return d->mainwin->statusMessage(); +#else + return PsiOptions::instance()->getOption("options.status.last-message").toString(); +#endif +} + +void PsiCon::setStatusFromDialog(const XMPP::Status &s, bool withPriority, bool isManualStatus) +{ + if (isManualStatus) { + PsiOptions::instance()->setOption("options.status.last-message", s.status()); + } + setGlobalStatus(s, withPriority, isManualStatus); } void PsiCon::setStatusFromCommandline(const QString &status, const QString &message) { - PsiOptions::instance()->setOption("options.status.last-message", message); + bool isManualStatus = true; // presume that this will always be a manual user action + if (isManualStatus) { + PsiOptions::instance()->setOption("options.status.last-message", message); + } XMPP::Status s; s.setType(status); s.setStatus(message); // yes, a bit different naming convention.. - setGlobalStatus(s, false); + setGlobalStatus(s, false, isManualStatus); } -void PsiCon::setGlobalStatus(const Status &s, bool withPriority) +void PsiCon::setGlobalStatus(const Status &s, bool withPriority, bool isManualStatus) { // Check whether all accounts are logged off bool allOffline = true; +#ifndef YAPSI foreach(PsiAccount* account, d->contactList->enabledAccounts()) { if ( account->isActive() ) { allOffline = false; break; } } +#endif // globally set each account which is logged in foreach(PsiAccount* account, d->contactList->enabledAccounts()) if (allOffline || account->isActive()) - account->setStatus(s, withPriority); + account->setStatus(s, withPriority, isManualStatus); } void PsiCon::pa_updatedActivity() @@ -947,11 +1058,13 @@ void PsiCon::pa_updatedAccount() PsiAccount *pa = (PsiAccount *)sender(); emit accountUpdated(pa); - saveAccounts(); + d->updatedAccountTimer_->start(); } void PsiCon::saveAccounts() { + d->updatedAccountTimer_->stop(); + UserAccountList acc = d->contactList->getUserAccountList(); d->saveProfile(acc); } @@ -999,13 +1112,17 @@ void PsiCon::doOptions() void PsiCon::doFileTransDlg() { +#ifdef FILETRANSFER bringToFront(d->ftwin); +#endif } void PsiCon::checkAccountsEmpty() { if (d->contactList->accounts().count() == 0) { +#ifndef YAPSI promptUserToCreateAccount(); +#endif } } @@ -1159,11 +1276,6 @@ void PsiCon::slotApplyOptions() emit emitOptionsUpdate(); } -int PsiCon::getId() -{ - return d->eventId++; -} - void PsiCon::queueChanged() { PsiIcon *nextAnim = 0; @@ -1224,6 +1336,11 @@ void PsiCon::raiseMainwin() d->mainwin->showNoFocus(); } +bool PsiCon::mainWinVisible() const +{ + return d->mainwin->isVisible(); +} + QStringList PsiCon::recentGCList() const { return PsiOptions::instance()->getOption("options.muc.recent-joins.jids").toStringList(); @@ -1311,6 +1428,13 @@ IconSelectPopup *PsiCon::iconSelectPopup() const return d->iconSelect; } +bool PsiCon::filterEvent(const PsiAccount* acc, const PsiEvent* e) const +{ + Q_UNUSED(acc); + Q_UNUSED(e); + return false; +} + void PsiCon::processEvent(PsiEvent *e, ActivationType activationType) { if ( e->type() == PsiEvent::PGP ) { @@ -1330,6 +1454,7 @@ void PsiCon::processEvent(PsiEvent *e, ActivationType activationType) return; } +#ifdef FILETRANSFER if( e->type() == PsiEvent::File ) { FileEvent *fe = (FileEvent *)e; FileTransfer *ft = fe->takeFileTransfer(); @@ -1342,6 +1467,7 @@ void PsiCon::processEvent(PsiEvent *e, ActivationType activationType) } return; } +#endif if(e->type() == PsiEvent::AvCallType) { AvCallEvent *ae = (AvCallEvent *)e; @@ -1370,12 +1496,28 @@ void PsiCon::processEvent(PsiEvent *e, ActivationType activationType) MessageEvent *me = (MessageEvent *)e; const Message &m = me->message(); bool emptyForm = m.getForm().fields().empty(); - if ( m.type() == "chat" && emptyForm ) { + // FIXME: Refactor this, PsiAccount and PsiEvent out + if ((m.type() == "chat" && emptyForm) + || !EventDlg::messagingEnabled()) { isChat = true; sentToChatWindow = me->sentToChatWindow(); } } + if (e->type() == PsiEvent::Auth && !EventDlg::messagingEnabled()) { + if (dynamic_cast(e)->authType() == "subscribe") { +#ifdef YAPSI + bringToFront(d->mainwin); + return; +#else + e->account()->dj_addAuth(e->jid()); +#endif + } + e->account()->eventQueue()->dequeue(e); + delete e; + return; + } + if ( isChat ) { PsiAccount* account = e->account(); XMPP::Jid from = e->from(); @@ -1407,7 +1549,8 @@ void PsiCon::processEvent(PsiEvent *e, ActivationType activationType) e->account()->processReadNext(*u); } - bringToFront(w); + if (w) + bringToFront(w); } } @@ -1513,7 +1656,7 @@ void PsiCon::promptUserToCreateAccount() AccountRegDlg w(proxy()); int n = w.exec(); if (n == QDialog::Accepted) { - contactList()->createAccount(w.jid().node(),w.jid(),w.pass(),w.useHost(),w.host(),w.port(),w.legacySSLProbe(),w.ssl(),w.proxy(),w.tlsOverrideDomain(), w.tlsOverrideCert(), false); + contactList()->createAccount(w.jid().node(),w.jid(),w.pass(),w.useHost(),w.host(),w.port(),w.legacySSLProbe(),w.ssl(),w.proxy(),w.tlsOverrideDomain(), w.tlsOverrideCert()); } } } @@ -1527,5 +1670,31 @@ void PsiCon::forceSavePreferences() { PsiOptions::instance()->save(optionsFile()); } - + +void PsiCon::doQuit(int quitCode) +{ + d->quitting = true; + emit quit(quitCode); +} + +void PsiCon::aboutToQuit() +{ + doQuit(QuitProgram); +} + +ContactUpdatesManager* PsiCon::contactUpdatesManager() const +{ + return contactUpdatesManager_; +} + +#ifndef NEWCONTACTLIST +ContactView* PsiCon::contactView() const +{ + if(d->mainwin) + return d->mainwin->cvlist; + else + return 0; +} +#endif + #include "psicon.moc" diff --git a/src/psicon.h b/src/psicon.h index 9bfc8eaaf..5227ac2c1 100644 --- a/src/psicon.h +++ b/src/psicon.h @@ -25,10 +25,9 @@ #include "profiles.h" #include "psiactions.h" - +#include "psievent.h" #include "tabbablewidget.h" - using namespace XMPP; class PsiCon; @@ -53,6 +52,9 @@ class ChatDlg; class AlertManager; class TuneController; class PsiContactList; +class TabManager; +class ContactUpdatesManager; + namespace OpenPGP { class Engine; } @@ -72,11 +74,14 @@ class PsiCon : public QObject void deinit(); PsiContactList* contactList() const; - ContactView *contactView() const; +#ifndef NEWCONTACTLIST + ContactView* contactView() const; +#endif EDB *edb() const; TuneController* tuneController() const; ProxyManager *proxy() const; FileTransDlg *ftdlg() const; + TabManager *tabManager() const; AlertManager *alertManager() const; @@ -86,12 +91,15 @@ class PsiCon : public QObject QMenuBar* defaultMenuBar() const; - void createAccount(const QString &name, const Jid &j="", const QString &pass="", bool opt_host=false, const QString &host="", int port=5222, bool legacy_ssl_probe = true, UserAccount::SSLFlag ssl=UserAccount::SSL_Auto, QString proxy="", const QString &tlsOverrideDomain="", const QByteArray &tlsOverrideCert=QByteArray()); + ContactUpdatesManager* contactUpdatesManager() const; + + PsiAccount* createAccount(const QString &name, const Jid &j="", const QString &pass="", bool opt_host=false, const QString &host="", int port=5222, bool legacy_ssl_probe = true, UserAccount::SSLFlag ssl=UserAccount::SSL_Auto, QString proxy="", const QString &tlsOverrideDomain="", const QByteArray &tlsOverrideCert=QByteArray()); PsiAccount *createAccount(const UserAccount &); //void createAccount(const QString &, const QString &host="", int port=5222, bool ssl=false, const QString &user="", const QString &pass=""); void removeAccount(PsiAccount *); void playSound(const QString &); + bool mainWinVisible() const; AccountsComboBox *accountsComboBox(QWidget *parent=0, bool online_only = false); @@ -102,14 +110,19 @@ class PsiCon : public QObject const QStringList & recentNodeList() const; void recentNodeAdd(const QString &); - EventDlg *createEventDlg(const QString &, PsiAccount *); + EventDlg *createEventDlg(const QString &, PsiAccount*); void updateContactGlobal(PsiAccount *, const Jid &); PsiActionList *actionList() const; IconSelectPopup *iconSelectPopup() const; + bool filterEvent(const PsiAccount*, const PsiEvent*) const; void processEvent(PsiEvent*, ActivationType activationType); + Status::Type currentStatusType() const; + Status::Type lastLoggedInStatusType() const; + QString currentStatusMessage() const; + bool haveAutoUpdater() const; signals: @@ -120,9 +133,10 @@ class PsiCon : public QObject void accountCountChanged(); void accountActivityChanged(); void emitOptionsUpdate(); + void restoringSavedChatsChanged(); public slots: - void setGlobalStatus(const Status &, bool withPriority = false); + void setGlobalStatus(const Status &, bool withPriority = false, bool isManualStatus = false); void doToolbars(); void checkAccountsEmpty(); @@ -142,7 +156,7 @@ public slots: void slotApplyOptions(); void queueChanged(); void recvNextEvent(); - void setStatusFromDialog(const XMPP::Status &, bool withPriority); + void setStatusFromDialog(const XMPP::Status &, bool withPriority, bool isManualStatus); void setStatusFromCommandline(const QString &status, const QString &message); void proxy_settingsChanged(); void updateMainwinStatus(); @@ -157,10 +171,13 @@ private slots: void optionChanged(const QString& option); void forceSavePreferences(); void startBounce(); + void aboutToQuit(); private: class Private; Private *d; + friend class Private; + ContactUpdatesManager* contactUpdatesManager_; void deleteAllDialogs(); void s5b_init(); @@ -170,11 +187,9 @@ private slots: friend class PsiAccount; // FIXME void promptUserToCreateAccount(); QString optionsFile() const; + void doQuit(int); void registerCaps(const QString& ext, const QStringList& features); - - friend class EventQueue; - int getId(); }; #endif diff --git a/src/psicontact.cpp b/src/psicontact.cpp new file mode 100755 index 000000000..d9d93132b --- /dev/null +++ b/src/psicontact.cpp @@ -0,0 +1,1046 @@ +/* + * psicontact.cpp - PsiContact + * Copyright (C) 2008 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "psicontact.h" + +#include +#include + +#include "avatars.h" +#include "common.h" +#include "contactview.h" +#include "iconset.h" +#include "jidutil.h" +#include "profiles.h" +#include "psiaccount.h" +#include "psicontactmenu.h" +#include "psiiconset.h" +#include "psioptions.h" +#include "userlist.h" +#include "userlist.h" +#include "alertable.h" +#include "avatars.h" +#ifdef YAPSI +#include "yaprivacymanager.h" +#include "yacommon.h" +#include "yaprofile.h" +#include "yatoastercentral.h" +#endif +#include "contactlistgroup.h" +#include "desktoputil.h" +#include "vcardfactory.h" +#include "psicon.h" +#include "psicontactlist.h" +#ifdef YAPSI +#include "yarostertooltip.h" +#endif + +static const int statusTimerInterval = 5000; + +class PsiContact::Private : public Alertable +{ + Q_OBJECT +public: + Private(PsiContact* contact) + : account_(0) + , statusTimer_(0) + , contact_(contact) +#ifdef YAPSI + , gender_(XMPP::VCard::UnknownGender) + , genderCached_(false) +#endif + { + oldStatus_ = XMPP::Status(XMPP::Status::Offline); +#ifdef YAPSI + showOnlineTemporarily_ = false; + reconnecting_ = false; + + delayedMoodUpdateTimer_ = new QTimer(this); + delayedMoodUpdateTimer_->setInterval(60 * 1000); + delayedMoodUpdateTimer_->setSingleShot(true); + connect(delayedMoodUpdateTimer_, SIGNAL(timeout()), contact, SLOT(moodUpdate())); +#endif + + statusTimer_ = new QTimer(this); + statusTimer_->setInterval(statusTimerInterval); + statusTimer_->setSingleShot(true); + connect(statusTimer_, SIGNAL(timeout()), SLOT(updateStatus())); + } + + ~Private() + { + } + + PsiAccount* account_; + QTimer* statusTimer_; + UserListItem u_; + QString name_; + Status status_; + Status oldStatus_; +#ifdef YAPSI + bool showOnlineTemporarily_; + bool reconnecting_; + QTimer* delayedMoodUpdateTimer_; + XMPP::VCard::Gender gender_; + bool genderCached_; +#endif + + XMPP::Status status(const UserListItem& u) const + { + XMPP::Status status = XMPP::Status(XMPP::Status::Offline); + if (u.priority() != u.userResourceList().end()) + status = (*u.priority()).status(); + return status; + } + + /** + * Returns userHost of the base jid combined with \param resource. + */ + Jid jidForResource(const QString& resource) + { + return u_.jid().withResource(resource); + } + + void setStatus(XMPP::Status status) + { + status_ = status; +#ifdef YAPSI + reconnecting_ = false; +#endif + if (account_ && !account_->notifyOnline()) + oldStatus_ = status_; + else + statusTimer_->start(); + } + +private slots: + void updateStatus() + { +#ifdef YAPSI + showOnlineTemporarily_ = false; +#endif + oldStatus_ = status_; + emit contact_->updated(); + } + +private: + PsiContact* contact_; + + void alertFrameUpdated() + { +#ifndef YAPSI + emit contact_->updated(); +#endif + } +}; + +/** + * Creates new PsiContact. + */ +PsiContact::PsiContact(const UserListItem& u, PsiAccount* account) + : ContactListItem(account) +{ + d = new Private(this); + d->account_ = account; + if (d->account_) { + connect(d->account_->avatarFactory(), SIGNAL(avatarChanged(const Jid&)), SLOT(avatarChanged(const Jid&))); + } + connect(VCardFactory::instance(), SIGNAL(vcardChanged(const Jid&)), SLOT(vcardChanged(const Jid&))); + update(u); + + // updateParent(); +} + +PsiContact::PsiContact() + : ContactListItem(0) +{ + d = new Private(this); + d->account_ = 0; +} + +/** + * Destructor. + */ +PsiContact::~PsiContact() +{ + emit destroyed(this); + delete d; +} + +/** + * Returns account to which a contact belongs. + */ +PsiAccount* PsiContact::account() const +{ + return d->account_; +} + +/** + * TODO: Think of ways to remove this function. + */ +const UserListItem& PsiContact::userListItem() const +{ + return d->u_; +} + +/** + * This function should be called only by PsiAccount to update + * PsiContact's current state. + */ +void PsiContact::update(const UserListItem& u) +{ + d->u_ = u; + Status status = d->status(d->u_); + + d->setStatus(status); + + emit updated(); + emit groupsChanged(); +} + +#ifdef YAPSI +void PsiContact::startDelayedMoodUpdate(int timeoutInSecs) +{ + d->delayedMoodUpdateTimer_->setInterval((timeoutInSecs + 1) * 1000); + d->delayedMoodUpdateTimer_->start(); +} + +void PsiContact::moodUpdate() +{ + Status status = d->status(d->u_); + + QString newMood = Ya::processMood(d->u_.yaMood(), status.status(), status.type()); + + if (status.isAvailable() && + // !status.status().isEmpty() && + newMood != d->u_.yaMood() && + !d->u_.isSelf() && + !isYaInformer()) + { + // qWarning("mood changed: %s, '%s' (%s)", qPrintable(jid().full()), qPrintable(status.status()), qPrintable(d->u_.yaMood())); + emit moodChanged(newMood); + } +} +#endif + +/** + * Triggers default action. + */ +void PsiContact::activate() +{ + if (!account()) + return; + account()->actionDefault(jid()); +} + +ContactListModel::Type PsiContact::type() const +{ + return ContactListModel::ContactType; +} + +/** + * Returns contact's display name. + */ +const QString& PsiContact::name() const +{ + d->name_ = JIDUtil::nickOrJid(d->u_.name(), jid().full()); + return d->name_; +} + +QString PsiContact::comparisonName() const +{ + return name() + jid().full() + account()->name(); +} + +#ifdef YAPSI +XMPP::VCard::Gender PsiContact::gender() const +{ + if (!d->genderCached_) + const_cast(this)->vcardChanged(jid()); + return d->gender_; +} +#endif + +/** + * Returns contact's Jabber ID. + */ +XMPP::Jid PsiContact::jid() const +{ + return d->u_.jid(); +} + +/** + * Returns contact's status. + */ +Status PsiContact::status() const +{ +#ifdef YAPSI + if (isBlocked()) { + return XMPP::Status(XMPP::Status::Blocked); + } + + if (d->reconnecting_) { + return XMPP::Status(XMPP::Status::Reconnecting); + } + + if (!authorizesToSeeStatus()) { + return XMPP::Status(XMPP::Status::NotAuthorizedToSeeStatus); + } +#endif + return d->status_; +} + +/** + * Returns tool tip text for contact in HTML format. + */ +QString PsiContact::toolTip() const +{ + return d->u_.makeTip(true, false); +} + +/** + * Returns contact's avatar picture. + */ +QIcon PsiContact::picture() const +{ + if (!account()) + return QIcon(); + return account()->avatarFactory()->getAvatar(jid().bare()); +} + +/** + * Creates a menu with actions for this contact. + */ +ContactListItemMenu* PsiContact::contextMenu(ContactListModel* model) +{ + if (!account()) + return 0; + return new PsiContactMenu(this, model); +} + +bool PsiContact::isFake() const +{ + return false; +} + +/** + * Returns true if user could modify (i.e. rename/change group/remove from + * contact list) this contact on server. + */ +bool PsiContact::isEditable() const +{ + if (!account()) + return false; +#ifdef YAPSI_ACTIVEX_SERVER + if (account()->psi() && + account()->psi()->contactList() && + account()->psi()->contactList()->onlineAccount() && + !account()->psi()->contactList()->onlineAccount()->isAvailable()) + { + return false; + } +#endif + return account()->isAvailable() && inList(); +} + +bool PsiContact::isDragEnabled() const +{ + return isEditable() + && !isPrivate() + && !isAgent(); +} + +/** + * This function should be invoked when contact is being renamed by + * user. \param name specifies new name. PsiContact is responsible + * for the actual roster update. + */ +void PsiContact::setName(const QString& name) +{ + if (account()) + account()->actionRename(jid(), name); +} + +QString PsiContact::generalGroupName() +{ + return tr("General"); +} + +QString PsiContact::notInListGroupName() +{ + return tr("Not in list"); +} + +static const QString globalGroupDelimiter = "::"; +static const QString accountGroupDelimiter = "::"; + +/** + * Processes the list of groups to the result ready to be fed into + * ContactListModel. All account-specific group delimiters are replaced + * in favor of the global Psi one. + */ +QStringList PsiContact::groups() const +{ + QStringList result; + if (!account()) + return result; + + // if (d->u_.isPrivate()) { + // result << tr("Private messages"); + // return result; + // } + + // if (!d->u_.inList()) { + // result << notInListGroupName(); + // return result; + // } + + if (d->u_.groups().isEmpty()) { + // empty group name means that the contact should be added + // to the 'General' group or no group at all +#ifdef USE_GENERAL_CONTACT_GROUP + result << generalGroupName(); +#else + result << QString(); +#endif + } + else { + foreach(QString group, d->u_.groups()) { + QString groupName = group.split(accountGroupDelimiter).join(globalGroupDelimiter); +#ifdef USE_GENERAL_CONTACT_GROUP + if (groupName.isEmpty()) { + groupName = generalGroupName(); + } +#endif + if (!result.contains(groupName)) { + result << groupName; + } + } + } + + return result; +} + +/** + * Sets contact's groups to be \param newGroups. All associations to all groups + * it belonged to prior to calling this function are lost. \param newGroups + * becomes the only groups of the contact. Note: \param newGroups should be passed + * with global Psi group delimiters. + */ +void PsiContact::setGroups(QStringList newGroups) +{ + if (!account()) + return; + QStringList newAccountGroups; + foreach(QString group, newGroups) { + QString groupName = group.split(globalGroupDelimiter).join(accountGroupDelimiter); +#ifdef USE_GENERAL_CONTACT_GROUP + if (groupName == generalGroupName()) { + groupName = QString(); + } +#endif + newAccountGroups << groupName; + } + + if (newAccountGroups.count() == 1 && newAccountGroups.first().isEmpty()) + newAccountGroups.clear(); + + account()->actionGroupsSet(jid(), newAccountGroups); +} + +bool PsiContact::groupOperationPermitted(const QString& oldGroupName, const QString& newGroupName) const +{ + Q_UNUSED(oldGroupName); + Q_UNUSED(newGroupName); + return true; +} + +bool PsiContact::isRemovable() const +{ + foreach(QString group, groups()) { + if (!groupOperationPermitted(group, QString())) + return false; + } +#ifdef YAPSI_ACTIVEX_SERVER + if (account()->psi() && + account()->psi()->contactList() && + account()->psi()->contactList()->onlineAccount() && + !account()->psi()->contactList()->onlineAccount()->isAvailable()) + { + return false; + } +#endif + return true; +} + +/** + * Returns true if contact currently have an alert set. + */ +bool PsiContact::alerting() const +{ + return d->alerting(); +} + +QIcon PsiContact::alertPicture() const +{ + Q_ASSERT(alerting()); + if (!alerting()) + return QIcon(); + return d->currentAlertFrame(); +} + +/** + * Sets alert icon for contact. Pass null pointer in order to clear alert. + */ +void PsiContact::setAlert(const PsiIcon* icon) +{ +#ifndef YAPSI + d->setAlert(icon); + // updateParent(); +#endif +} + +/** + * Contact should always be visible if it's alerting. + */ +bool PsiContact::shouldBeVisible() const +{ +#ifndef YAPSI + if (d->alerting()) + return true; +#endif + return false; + // return ContactListItem::shouldBeVisible(); +} + +/** + * Standard desired parent. + */ +// ContactListGroupItem* PsiContact::desiredParent() const +// { +// return ContactListContact::desiredParent(); +// } + +/** + * Returns true if this contact is the one for the \param jid. In case + * if reimplemented in metacontact-enabled subclass, it could match + * several different jids. + */ +bool PsiContact::find(const Jid& jid) const +{ + return this->jid().compare(jid); +} + +/** + * This behaves just like ContactListContact::contactList(), but statically + * casts returned value to PsiContactList. + */ +// PsiContactList* PsiContact::contactList() const +// { +// return static_cast(ContactListContact::contactList()); +// } + +const UserResourceList& PsiContact::userResourceList() const +{ + return d->u_.userResourceList(); +} + +void PsiContact::receiveIncomingEvent() +{ + if (account()) + account()->actionRecvEvent(jid()); +} + +void PsiContact::sendMessage() +{ + if (account()) + account()->actionSendMessage(jid()); +} + +void PsiContact::sendMessageTo(QString resource) +{ + if (account()) + account()->actionSendMessage(d->jidForResource(resource)); +} + +void PsiContact::openChat() +{ + if (account()) + account()->actionOpenChat(jid()); +} + +void PsiContact::openChatTo(QString resource) +{ + if (account()) + account()->actionOpenChatSpecific(d->jidForResource(resource)); +} + +#ifdef WHITEBOARDING +void PsiContact::openWhiteboard() +{ + if (account()) + account()->actionOpenWhiteboard(jid()); +} + +void PsiContact::openWhiteboardTo(QString resource) +{ + if (account()) + account()->actionOpenWhiteboardSpecific(d->jidForResource(resource)); +} +#endif + +void PsiContact::executeCommand(QString resource) +{ + if (account()) + account()->actionExecuteCommandSpecific(d->jidForResource(resource), ""); +} + +void PsiContact::openActiveChat(QString resource) +{ + if (account()) + account()->actionOpenChatSpecific(d->jidForResource(resource)); +} + +void PsiContact::sendFile() +{ + if (account()) + account()->actionSendFile(jid()); +} + +void PsiContact::inviteToGroupchat(QString groupChat) +{ + if (account()) + account()->actionInvite(jid(), groupChat); +} + +void PsiContact::toggleBlockedState() +{ + if (!account()) + return; + +// FIXME +#ifdef YAPSI + YaPrivacyManager* privacyManager = dynamic_cast(account()->privacyManager()); + Q_ASSERT(privacyManager); + + bool blocked = privacyManager->isContactBlocked(jid()); + if (!blocked) { + emit YaRosterToolTip::instance()->blockContact(this, 0); + } + else { + emit YaRosterToolTip::instance()->unblockContact(this, 0); + } +#endif +} + +void PsiContact::toggleBlockedStateConfirmation() +{ + if (!account()) + return; + +// FIXME +#ifdef YAPSI + YaPrivacyManager* privacyManager = dynamic_cast(account()->privacyManager()); + Q_ASSERT(privacyManager); + + bool blocked = privacyManager->isContactBlocked(jid()); + blockContactConfirmationHelper(!blocked); +#endif +} + +void PsiContact::blockContactConfirmationHelper(bool block) +{ + if (!account()) + return; + +#ifdef YAPSI + YaPrivacyManager* privacyManager = dynamic_cast(account()->privacyManager()); + Q_ASSERT(privacyManager); + + privacyManager->setContactBlocked(jid(), block); +#else + // FIXME + Q_UNUSED(block); +#endif +} + +void PsiContact::blockContactConfirmation(const QString& id, bool confirmed) +{ + Q_ASSERT(id == "blockContact"); + if (confirmed) { + blockContactConfirmationHelper(true); + } +} + +/*! + * The only way to get dual authorization with XMPP1.0-compliant servers + * is to request auth first and make a contact accept it, and request it + * on its own. + */ +void PsiContact::rerequestAuthorizationFrom() +{ + if (account()) + account()->dj_authReq(jid()); +} + +void PsiContact::removeAuthorizationFrom() +{ + qWarning("PsiContact::removeAuthorizationFrom()"); +} + +void PsiContact::remove() +{ + if (account()) + account()->actionRemove(jid()); +} + +void PsiContact::assignCustomPicture() +{ + if (!account()) + return; + + // FIXME: Should check the supported filetypes dynamically + // FIXME: parent of QFileDialog is NULL, probably should make it psi->contactList() + QString file = QFileDialog::getOpenFileName(0, tr("Choose an image"), "", tr("All files (*.png *.jpg *.gif)")); + if (!file.isNull()) { + account()->avatarFactory()->importManualAvatar(jid(), file); + } +} + +void PsiContact::clearCustomPicture() +{ + if (account()) + account()->avatarFactory()->removeManualAvatar(jid()); +} + +void PsiContact::userInfo() +{ + if (account()) + account()->actionInfo(jid()); +} + +void PsiContact::history() +{ +#ifdef YAPSI + if (account()) + Ya::showHistory(account(), jid()); +#else + if (account()) + account()->actionHistory(jid()); +#endif +} + +#ifdef YAPSI +bool PsiContact::isYaInformer() const +{ + return d->u_.groups().contains(Ya::INFORMERS_GROUP_NAME); +} + +bool PsiContact::isYaJid() +{ + return Ya::isYaJid(jid().full()); +} + +bool PsiContact::isYandexTeamJid() +{ + return Ya::isYandexTeamJid(jid().full()); +} + +YaProfile PsiContact::getYaProfile() const +{ + return YaProfile(account(), jid()); +} + +void PsiContact::yaProfile() +{ + if (!account() || !(isYaJid() || isYandexTeamJid())) + return; + getYaProfile().browse(); +} + +void PsiContact::yaPhotos() +{ + if (!account() || !isYaJid()) + return; + getYaProfile().browsePhotos(); +} + +void PsiContact::yaEmail() +{ + // FIXME: use vcard.email()? + DesktopUtil::openEMail(jid().bare()); +} +#endif + +void PsiContact::addRemoveAuthBlockAvailable(bool* addButton, bool* deleteButton, bool* authButton, bool* blockButton) const +{ + Q_ASSERT(addButton && deleteButton && authButton && blockButton); + *addButton = false; + *deleteButton = false; + *authButton = false; + *blockButton = false; + + if (account() && account()->isAvailable() && !userListItem().isSelf()) { + UserListItem* u = account()->findFirstRelevant(jid()); + + *blockButton = isEditable(); + *deleteButton = isEditable(); + + if (!u || !u->inList()) { + *addButton = isEditable(); + } + else { + if (!authorizesToSeeStatus()) { + *authButton = isEditable(); + } + } + + *deleteButton = *deleteButton && isEditable() && isRemovable(); + +// FIXME +#ifdef YAPSI + YaPrivacyManager* privacyManager = dynamic_cast(account()->privacyManager()); + Q_ASSERT(privacyManager); + *blockButton = *blockButton && privacyManager->isAvailable(); +#endif + } +} + +bool PsiContact::addAvailable() const +{ + bool addButton, deleteButton, authButton, blockButton; + addRemoveAuthBlockAvailable(&addButton, &deleteButton, &authButton, &blockButton); + return addButton; +} + +bool PsiContact::removeAvailable() const +{ + bool addButton, deleteButton, authButton, blockButton; + addRemoveAuthBlockAvailable(&addButton, &deleteButton, &authButton, &blockButton); + return deleteButton; +} + +bool PsiContact::authAvailable() const +{ + bool addButton, deleteButton, authButton, blockButton; + addRemoveAuthBlockAvailable(&addButton, &deleteButton, &authButton, &blockButton); + return authButton; +} + +bool PsiContact::blockAvailable() const +{ + bool addButton, deleteButton, authButton, blockButton; + addRemoveAuthBlockAvailable(&addButton, &deleteButton, &authButton, &blockButton); + return blockButton; +} + +#ifdef YAPSI +bool PsiContact::historyAvailable() const +{ + return Ya::historyAvailable(account(), jid()); +} +#endif + +void PsiContact::avatarChanged(const Jid& j) +{ + if (!j.compare(jid(), false)) + return; + emit updated(); +} + +void PsiContact::rereadVCard() +{ + vcardChanged(jid()); +} + +void PsiContact::vcardChanged(const Jid& j) +{ + if (!j.compare(jid(), false)) + return; + +#ifdef YAPSI + d->gender_ = XMPP::VCard::UnknownGender; + const VCard* vcard = VCardFactory::instance()->vcard(jid()); + if (vcard) { + d->gender_ = vcard->gender(); + } + d->genderCached_ = true; +#endif + emit updated(); +} + +static inline int rankStatus(int status) +{ + switch (status) { + case XMPP::Status::FFC: return 0; // YAPSI original: 0; + case XMPP::Status::Online: return 0; // YAPSI original: 1; + case XMPP::Status::Away: return 1; // YAPSI original: 2; + case XMPP::Status::XA: return 1; // YAPSI original: 3; + case XMPP::Status::DND: return 0; // YAPSI original: 4; + case XMPP::Status::Invisible: return 5; // YAPSI original: 5; + default: + return 6; + } + return 0; +} + +bool PsiContact::compare(const ContactListItem* other) const +{ + const ContactListGroup* group = dynamic_cast(other); + if (group) { + return !group->compare(this); + } + + const PsiContact* contact = dynamic_cast(other); + if (contact) { + int rank = rankStatus(d->oldStatus_.type()) - rankStatus(contact->d->oldStatus_.type()); + if (rank == 0) + rank = QString::localeAwareCompare(comparisonName().toLower(), contact->comparisonName().toLower()); + return rank < 0; + } + + return ContactListItem::compare(other); +} + +// FIXME +#ifdef YAPSI +static YaPrivacyManager* privacyManager(PsiAccount* account) +{ + return dynamic_cast(account->privacyManager()); +} +#endif + +bool PsiContact::isBlocked() const +{ +// FIXME +#ifdef YAPSI + return account() && privacyManager(account()) && + privacyManager(account())->isContactBlocked(jid()); +#else + return false; +#endif +} + +bool PsiContact::isSelf() const +{ + return false; +} + +bool PsiContact::isAgent() const +{ + return userListItem().isTransport(); +} + +bool PsiContact::inList() const +{ + return userListItem().inList(); +} + +bool PsiContact::isPrivate() const +{ + return userListItem().isPrivate(); +} + +bool PsiContact::noGroups() const +{ + return userListItem().groups().isEmpty(); +} + +/*! + * Returns true if contact could see our status. + */ +bool PsiContact::authorized() const +{ + return userListItem().subscription().type() == Subscription::Both || + userListItem().subscription().type() == Subscription::From; +} + +/*! + * Returns true if we could see contact's status. + */ +bool PsiContact::authorizesToSeeStatus() const +{ + return userListItem().subscription().type() == Subscription::Both || + userListItem().subscription().type() == Subscription::To; +} + +bool PsiContact::askingForAuth() const +{ + return userListItem().ask() == "subscribe"; +} + +bool PsiContact::isOnline() const +{ + if (!inList() || + isPrivate()) + { + return true; + } + + return d->status_.type() != XMPP::Status::Offline || + d->oldStatus_.type() != XMPP::Status::Offline +#ifdef YAPSI + || d->showOnlineTemporarily_ + || d->reconnecting_; +#endif + ; +} + +bool PsiContact::isHidden() const +{ + return false; +} + +#ifdef YAPSI +void PsiContact::showOnlineTemporarily() +{ + d->showOnlineTemporarily_ = true; + d->statusTimer_->start(); + emit updated(); +} + +void PsiContact::setReconnectingState(bool reconnecting) +{ + d->reconnecting_ = reconnecting; + emit updated(); +} +#endif + +void PsiContact::setEditing(bool editing) +{ + if (this->editing() != editing) { + ContactListItem::setEditing(editing); + emit updated(); + } +} + +#ifdef YAPSI +bool PsiContact::moodNotificationsEnabled() const +{ + return !account()->psi()->yaToasterCentral()->moodNotificationsDisabled(jid()); +} + +void PsiContact::setMoodNotificationsEnabled(bool enabled) +{ + account()->psi()->yaToasterCentral()->setMoodNotificationsDisabled(jid(), !enabled); +} +#endif + +#include "psicontact.moc" diff --git a/src/psicontact.h b/src/psicontact.h new file mode 100755 index 000000000..2ce8a603f --- /dev/null +++ b/src/psicontact.h @@ -0,0 +1,188 @@ +/* + * psicontact.h - PsiContact + * Copyright (C) 2008 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PSICONTACT_H +#define PSICONTACT_H + +#include "contactlistitem.h" +#include "psicontactlist.h" +#include "xmpp_vcard.h" + +class PsiIcon; +class PsiAccount; +class UserListItem; +class UserResourceList; +class YaProfile; + +class PsiContact : public ContactListItem +{ + Q_OBJECT +private: + PsiContact(const PsiContact&); + PsiContact& operator=(const PsiContact&); + +protected: + PsiContact(); + +public: + PsiContact(const UserListItem& u, PsiAccount* account); + ~PsiContact(); + + PsiAccount* account() const; + const UserListItem& userListItem() const; + const UserResourceList& userResourceList() const; + virtual void update(const UserListItem& u); + + bool isBlocked() const; + virtual bool isSelf() const; + virtual bool isAgent() const; + virtual bool inList() const; + virtual bool isPrivate() const; + virtual bool noGroups() const; + virtual bool authorized() const; + virtual bool authorizesToSeeStatus() const; + virtual bool askingForAuth() const; + virtual bool isOnline() const; + virtual bool isHidden() const; + + void activate(); + + virtual void setEditing(bool editing); + + bool addAvailable() const; + bool removeAvailable() const; + bool authAvailable() const; + bool blockAvailable() const; +#ifdef YAPSI + bool isYaInformer() const; + bool isYaJid(); + bool isYandexTeamJid(); + bool historyAvailable() const; + bool moodNotificationsEnabled() const; + void setMoodNotificationsEnabled(bool enabled); + + void showOnlineTemporarily(); + void setReconnectingState(bool reconnecting); + + void startDelayedMoodUpdate(int timeoutInSecs); +#endif + + virtual bool isFake() const; + + // reimplemented + virtual ContactListModel::Type type() const; + virtual const QString& name() const; + virtual QString comparisonName() const; + virtual void setName(const QString& name); + virtual ContactListItemMenu* contextMenu(ContactListModel* model); + virtual bool isEditable() const; + virtual bool isDragEnabled() const; + virtual bool compare(const ContactListItem* other) const; + virtual bool isRemovable() const; + + virtual XMPP::Jid jid() const; + virtual XMPP::Status status() const; + virtual QString toolTip() const; + virtual QIcon picture() const; + virtual QIcon alertPicture() const; + +#ifdef YAPSI + XMPP::VCard::Gender gender() const; +#endif + void rereadVCard(); + + bool groupOperationPermitted(const QString& oldGroupName, const QString& newGroupName) const; + virtual QStringList groups() const; + virtual void setGroups(QStringList); + bool alerting() const; + void setAlert(const PsiIcon* icon); + bool find(const Jid& jid) const; + // PsiContactList* contactList() const; + + static QString generalGroupName(); + static QString notInListGroupName(); + +protected: + virtual bool shouldBeVisible() const; + // virtual ContactListGroupItem* desiredParent() const; + +public slots: + virtual void receiveIncomingEvent(); + virtual void sendMessage(); + virtual void sendMessageTo(QString resource); + virtual void openChat(); + virtual void openChatTo(QString resource); +#ifdef WHITEBOARDING + virtual void openWhiteboard(); + virtual void openWhiteboardTo(QString resource); +#endif + virtual void executeCommand(QString resource); + virtual void openActiveChat(QString resource); + virtual void sendFile(); + virtual void inviteToGroupchat(QString groupChat); + virtual void toggleBlockedState(); + virtual void toggleBlockedStateConfirmation(); + virtual void rerequestAuthorizationFrom(); + virtual void removeAuthorizationFrom(); + virtual void remove(); + virtual void assignCustomPicture(); + virtual void clearCustomPicture(); + virtual void userInfo(); + virtual void history(); +#ifdef YAPSI + virtual void yaProfile(); + virtual void yaPhotos(); + virtual void yaEmail(); +#endif +#ifdef YAPSI + void moodUpdate(); +#endif + +private slots: + void avatarChanged(const Jid&); + void vcardChanged(const Jid&); + + void blockContactConfirmation(const QString& id, bool confirmed); + void blockContactConfirmationHelper(bool block); + +signals: + void updated(); + void groupsChanged(); +#ifdef YAPSI + void moodChanged(const QString&); +#endif + + /** + * This signal is emitted when PsiContact has entered its final + * destruction stage. + */ + void destroyed(PsiContact*); + +private: + class Private; + Private *d; + + void addRemoveAuthBlockAvailable(bool* add, bool* remove, bool* auth, bool* block) const; +#ifdef YAPSI + YaProfile getYaProfile() const; +#endif +}; + +#endif diff --git a/src/psicontactlist.cpp b/src/psicontactlist.cpp index b49ee25fa..d110d1be6 100644 --- a/src/psicontactlist.cpp +++ b/src/psicontactlist.cpp @@ -19,18 +19,26 @@ */ #include "psicontactlist.h" + +#include + #include "psiaccount.h" #include "psievent.h" #include "accountadddlg.h" #include "serverinfomanager.h" #include "psicon.h" -#include "contactview.h" /** * Constructs new PsiContactList. \param psi will not be PsiContactList's parent though. */ PsiContactList::PsiContactList(PsiCon* psi) - : psi_(psi) + : QObject() + , psi_(psi) + , showAgents_(false) + , showHidden_(false) + , showSelf_(false) + , showOffline_(false) + , accountsLoaded_(false) { } @@ -39,9 +47,15 @@ PsiContactList::PsiContactList(PsiCon* psi) */ PsiContactList::~PsiContactList() { + emit destroying(); + accountsLoaded_ = false; + // PsiAccount calls some signals while being deleted prior to being unlinked, // which in result could cause calls to PsiContactList::accounts() QList toDelete(accounts_); + + enabledAccounts_.clear(); + foreach(PsiAccount* account, toDelete) delete account; } @@ -82,6 +96,14 @@ bool PsiContactList::haveActiveAccounts() const return false; } +bool PsiContactList::haveAvailableAccounts() const +{ + foreach(PsiAccount* account, enabledAccounts_) + if (account->isAvailable()) + return true; + return false; +} + /** * Returns true if enabledAccounts() list is not empty. */ @@ -90,21 +112,46 @@ bool PsiContactList::haveEnabledAccounts() const return !enabledAccounts_.isEmpty(); } +/** + * Returns true if there are some accounts that are trying to establish a connection. + */ +bool PsiContactList::haveConnectingAccounts() const +{ + foreach(PsiAccount* account, enabledAccounts()) + if (account->isActive() && !account->isAvailable()) + return true; + + return false; +} + /** * At the moment, it returns first enabled account. + * Note: In YaPsi it tries to return first enabled ya.ru account, then + * reverts to the usual behavior. */ PsiAccount *PsiContactList::defaultAccount() const { - if (enabledAccounts_.isEmpty()) { - return 0; +#ifdef YAPSI_ACTIVEX_SERVER + if (onlineAccount_) { + return onlineAccount(); } - return enabledAccounts_.first(); +#endif + if (!enabledAccounts_.isEmpty()) { +#ifdef YAPSI + foreach(PsiAccount* account, enabledAccounts_) { + if (account->isYaAccount()) + return account; + } +#endif + return enabledAccounts_.first(); + } + return 0; } /** * Creates new PsiAccount based on some initial settings. This is used by AccountAddDlg. */ -void PsiContactList::createAccount(const QString& name, const Jid& j, const QString& pass, bool opt_host, const QString& host, int port, bool legacy_ssl_probe, UserAccount::SSLFlag ssl, QString proxyID, const QString &tlsOverrideDomain, const QByteArray &tlsOverrideCert, bool modify) +PsiAccount* PsiContactList::createAccount(const QString& name, const Jid& j, const QString& pass, bool opt_host, const QString& host, int port, bool legacy_ssl_probe, UserAccount::SSLFlag ssl, QString proxyID, const QString &tlsOverrideDomain, const QByteArray &tlsOverrideCert) { UserAccount acc; acc.name = name; @@ -122,15 +169,18 @@ void PsiContactList::createAccount(const QString& name, const Jid& j, const QStr acc.proxyID = proxyID; acc.legacy_ssl_probe = legacy_ssl_probe; + acc.tog_offline = showOffline(); + acc.tog_agents = showAgents(); + acc.tog_hidden = showHidden(); + acc.tog_self = showSelf(); + acc.tlsOverrideCert = tlsOverrideCert; acc.tlsOverrideDomain = tlsOverrideDomain; PsiAccount *pa = loadAccount(acc); emit saveAccounts(); - // pop up the modify dialog so the user can customize the new account - if (modify) - pa->modify(); + return pa; } void PsiContactList::createAccount(const UserAccount& acc) @@ -192,23 +242,28 @@ PsiAccount* PsiContactList::queueLowestEventId() */ PsiAccount *PsiContactList::loadAccount(const UserAccount& acc) { - beginBulkOperation(); + emit beginBulkContactUpdate(); PsiAccount *pa = psi_->createAccount(acc); connect(pa, SIGNAL(enabledChanged()), SIGNAL(accountCountChanged())); emit accountAdded(pa); - endBulkOperation(); + emit endBulkContactUpdate(); return pa; } /** * Loads accounts from \param list */ -void PsiContactList::loadAccounts(const UserAccountList &list) +void PsiContactList::loadAccounts(const UserAccountList &_list) { - beginBulkOperation(); + UserAccountList list = _list; + emit beginBulkContactUpdate(); foreach(UserAccount account, list) loadAccount(account); - endBulkOperation(); + emit endBulkContactUpdate(); + + accountsLoaded_ = true; + emit loadedAccounts(); + emit accountCountChanged(); } /** @@ -229,11 +284,17 @@ UserAccountList PsiContactList::getUserAccountList() const void PsiContactList::link(PsiAccount* account) { Q_ASSERT(!accounts_.contains(account)); + if (accounts_.contains(account)) + return; connect(account, SIGNAL(updatedActivity()), this, SIGNAL(accountActivityChanged())); connect(account->serverInfoManager(),SIGNAL(featuresChanged()), this, SIGNAL(accountFeaturesChanged())); + connect(account, SIGNAL(queueChanged()), this, SIGNAL(queueChanged())); + connect(account, SIGNAL(beginBulkContactUpdate()), this, SIGNAL(beginBulkContactUpdate())); + connect(account, SIGNAL(endBulkContactUpdate()), this, SIGNAL(endBulkContactUpdate())); + connect(account, SIGNAL(rosterRequestFinished()), this, SIGNAL(rosterRequestFinished())); accounts_.append(account); if (account->enabled()) - enabledAccounts_.append(account); + addEnabledAccount(account); connect(account, SIGNAL(enabledChanged()), SLOT(accountEnabledChanged())); emit accountCountChanged(); } @@ -244,12 +305,83 @@ void PsiContactList::link(PsiAccount* account) void PsiContactList::unlink(PsiAccount* account) { Q_ASSERT(accounts_.contains(account)); + if (!accounts_.contains(account)) + return; disconnect(account, SIGNAL(updatedActivity()), this, SIGNAL(accountActivityChanged())); accounts_.removeAll(account); - enabledAccounts_.removeAll(account); + removeEnabledAccount(account); emit accountCountChanged(); } +/** + * TODO + */ +bool PsiContactList::showAgents() const +{ + return showAgents_; +} + +/** + * TODO + */ +bool PsiContactList::showHidden() const +{ + return showHidden_; +} + +/** + * TODO + */ +bool PsiContactList::showSelf() const +{ + return showSelf_; +} + +bool PsiContactList::showOffline() const +{ + return showOffline_; +} + +/** + * TODO + */ +void PsiContactList::setShowAgents(bool showAgents) +{ + if (showAgents_ != showAgents) { + showAgents_ = showAgents; + emit showAgentsChanged(showAgents_); + } +} + +/** + * TODO + */ +void PsiContactList::setShowHidden(bool showHidden) +{ + if (showHidden_ != showHidden) { + showHidden_ = showHidden; + emit showHiddenChanged(showHidden_); + } +} + +/** + * TODO + */ +void PsiContactList::setShowSelf(bool showSelf) +{ + if (showSelf_ != showSelf) { + showSelf_ = showSelf; + emit showSelfChanged(showSelf_); + } +} + +void PsiContactList::setShowOffline(bool showOffline) +{ + if (showOffline_ != showOffline) { + showOffline_ = showOffline; + emit showOfflineChanged(showOffline_); + } +} PsiAccount *PsiContactList::tryQueueLowestEventId(bool includeDND) { @@ -279,45 +411,81 @@ PsiAccount *PsiContactList::tryQueueLowestEventId(bool includeDND) void PsiContactList::accountEnabledChanged() { PsiAccount* account = (PsiAccount*)sender(); - enabledAccounts_.removeAll(account); if (account->enabled()) - enabledAccounts_.append(account); + addEnabledAccount(account); + else + removeEnabledAccount(account); } -#ifdef NEWCONTACTLIST -#error "Don't forget to ditch PsiContactList::beginBulkOperation() and PsiContactList::endBulkOperation()" -#endif +PsiAccount* PsiContactList::getAccount(const QString& id) const +{ + foreach(PsiAccount* account, accounts()) + if (account->id() == id) + return account; -static int sortColumn = -1; -static int operationCount = 0; -/** - * This function should be called when a big update inside PsiContactList is taking place - * to enable optimizations (currently it just turns off the sorting of ContactView). - */ -void PsiContactList::beginBulkOperation() -{ - operationCount++; - if (operationCount == 1) { - Q_ASSERT(psi()); - Q_ASSERT(psi()->contactView()); - sortColumn = psi()->contactView()->sortColumn(); - Q_ASSERT(sortColumn != -1); - psi()->contactView()->setSorting(-1, true); - } + return 0; } -/** - * This function should be called when a big update inside PsiContactList is finished - * (currently it re-enables the sorting of ContactView). - */ -void PsiContactList::endBulkOperation() -{ - operationCount--; - if (operationCount <= 0) { - Q_ASSERT(psi()); - Q_ASSERT(psi()->contactView()); - Q_ASSERT(sortColumn != -1); - psi()->contactView()->setSorting(sortColumn, true); - sortColumn = -1; - } +PsiAccount* PsiContactList::getAccountByJid(const XMPP::Jid& jid) const +{ + foreach(PsiAccount* account, accounts()) + if (account->jid().compare(jid, false)) + return account; + + return 0; +} + +const QList& PsiContactList::contacts() const +{ + return contacts_; +} + +void PsiContactList::addEnabledAccount(PsiAccount* account) +{ + if (enabledAccounts_.contains(account)) + return; + + enabledAccounts_.append(account); + connect(account, SIGNAL(addedContact(PsiContact*)), SLOT(accountAddedContact(PsiContact*))); + connect(account, SIGNAL(removedContact(PsiContact*)), SLOT(accountRemovedContact(PsiContact*))); + + emit beginBulkContactUpdate(); + accountAddedContact(account->selfContact()); + foreach(PsiContact* contact, account->contactList()) + accountAddedContact(contact); + emit endBulkContactUpdate(); +} + +void PsiContactList::removeEnabledAccount(PsiAccount* account) +{ + if (!enabledAccounts_.contains(account)) + return; + + emit beginBulkContactUpdate(); + accountRemovedContact(account->selfContact()); + foreach(PsiContact* contact, account->contactList()) + accountRemovedContact(contact); + emit endBulkContactUpdate(); + disconnect(account, SIGNAL(addedContact(PsiContact*)), this, SLOT(accountAddedContact(PsiContact*))); + disconnect(account, SIGNAL(removedContact(PsiContact*)), this, SLOT(accountRemovedContact(PsiContact*))); + enabledAccounts_.removeAll(account); +} + +void PsiContactList::accountAddedContact(PsiContact* contact) +{ + Q_ASSERT(!contacts_.contains(contact)); + contacts_.append(contact); + emit addedContact(contact); +} + +void PsiContactList::accountRemovedContact(PsiContact* contact) +{ + Q_ASSERT(contacts_.contains(contact)); + contacts_.removeAll(contact); + emit removedContact(contact); +} + +bool PsiContactList::accountsLoaded() const +{ + return accountsLoaded_; } diff --git a/src/psicontactlist.h b/src/psicontactlist.h index 39b963237..71c3aa3ef 100644 --- a/src/psicontactlist.h +++ b/src/psicontactlist.h @@ -29,6 +29,7 @@ using namespace XMPP; class PsiCon; class PsiAccount; +class PsiContact; namespace XMPP { class Jid; } class PsiContactList : public QObject @@ -40,16 +41,28 @@ class PsiContactList : public QObject PsiCon* psi() const; + bool showAgents() const; + bool showHidden() const; + bool showSelf() const; + bool showOffline() const; + + bool accountsLoaded() const; + + PsiAccount* getAccount(const QString& id) const; + PsiAccount* getAccountByJid(const XMPP::Jid& jid) const; + const QList& accounts() const; const QList& enabledAccounts() const; bool haveActiveAccounts() const; + bool haveAvailableAccounts() const; bool haveEnabledAccounts() const; + bool haveConnectingAccounts() const; PsiAccount *defaultAccount() const; UserAccountList getUserAccountList() const; - void createAccount(const QString& name, const Jid& j = "", const QString& pass = "", bool opt_host = false, const QString& host = "", int port = 5222, bool legacy_ssl_probe = true, UserAccount::SSLFlag ssl = UserAccount::SSL_Auto, QString proxyID = "", const QString &tlsOverrideDomain="", const QByteArray &tlsOverrideCert=QByteArray(), bool modify = true); + PsiAccount* createAccount(const QString& name, const Jid& j = "", const QString& pass = "", bool opt_host = false, const QString& host = "", int port = 5222, bool legacy_ssl_probe = true, UserAccount::SSLFlag ssl = UserAccount::SSL_Auto, QString proxyID = "", const QString &tlsOverrideDomain="", const QByteArray &tlsOverrideCert=QByteArray()); void createAccount(const UserAccount&); void removeAccount(PsiAccount*); void setAccountEnabled(PsiAccount*, bool enabled = TRUE); @@ -61,10 +74,28 @@ class PsiContactList : public QObject void link(PsiAccount*); void unlink(PsiAccount*); - void beginBulkOperation(); - void endBulkOperation(); + const QList& contacts() const; + +public slots: + void setShowAgents(bool); + void setShowHidden(bool); + void setShowSelf(bool); + void setShowOffline(bool); signals: + void showAgentsChanged(bool); + void showHiddenChanged(bool); + void showSelfChanged(bool); + void showOfflineChanged(bool); + +signals: + void addedContact(PsiContact*); + void removedContact(PsiContact*); + + void beginBulkContactUpdate(); + void endBulkContactUpdate(); + void rosterRequestFinished(); + /** * This signal is emitted when account is loaded from disk or created * anew. @@ -82,7 +113,7 @@ class PsiContactList : public QObject void accountCountChanged(); /** * This signal is emitted when one of the accounts emits - * activityChanged() signal. + * updatedActivity() signal. */ void accountActivityChanged(); /** @@ -94,17 +125,44 @@ class PsiContactList : public QObject * existing one was removed altogether. */ void saveAccounts(); + /** + * This signal is emitted when event queue of one account was changed + * by adding or removing an event. + */ + void queueChanged(); + /** + * This signal is emitted at the end of loadAccounts() call, when all + * the accounts are initialized. + */ + void loadedAccounts(); + /** + * This signal is emitted when PsiContactList enters its final steps before + * total destruction, and all accounts are still accessible for last + * minute processing. + */ + void destroying(); private slots: void accountEnabledChanged(); + void accountAddedContact(PsiContact*); + void accountRemovedContact(PsiContact*); private: PsiAccount *loadAccount(const UserAccount &); PsiAccount *tryQueueLowestEventId(bool includeDND); PsiCon *psi_; - PsiContactList *contactList_; QList accounts_, enabledAccounts_; + QList contacts_; + + bool showAgents_; + bool showHidden_; + bool showSelf_; + bool showOffline_; + bool accountsLoaded_; + + void addEnabledAccount(PsiAccount* account); + void removeEnabledAccount(PsiAccount* account); }; #endif diff --git a/src/psicontactlistmodel.cpp b/src/psicontactlistmodel.cpp new file mode 100755 index 000000000..564b6984a --- /dev/null +++ b/src/psicontactlistmodel.cpp @@ -0,0 +1,73 @@ +/* + * psicontactlistmodel.cpp - a ContactListModel subclass that does Psi-specific things + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "psicontactlistmodel.h" + +#include "psicontact.h" +#include "psiaccount.h" +#include "userlist.h" +#include "contactlistgroup.h" +#include "contactlistaccountgroup.h" + +PsiContactListModel::PsiContactListModel(PsiContactList* contactList) + : ContactListDragModel(contactList) +{ +} + +QVariant PsiContactListModel::data(const QModelIndex &index, int role) const +{ + return ContactListDragModel::data(index, role); +} + +bool PsiContactListModel::setData(const QModelIndex& index, const QVariant& data, int role) +{ + return ContactListDragModel::setData(index, data, role); +} + +QVariant PsiContactListModel::contactData(const PsiContact* contact, int role) const +{ + if (role == Qt::ToolTipRole) { + return QVariant(contact->userListItem().makeTip(true, false)); + } + + return ContactListDragModel::contactData(contact, role); +} + +QVariant PsiContactListModel::contactGroupData(const ContactListGroup* group, int role) const +{ + if (role == Qt::ToolTipRole) { + QString text = itemData(group, Qt::DisplayRole).toString(); + text += QString(" (%1/%2)") + .arg(itemData(group, ContactListModel::OnlineContactsRole).toInt()) + .arg(itemData(group, ContactListModel::TotalContactsRole).toInt()); + return QVariant(text); + } + + return ContactListDragModel::contactGroupData(group, role); +} + +QVariant PsiContactListModel::accountData(const ContactListAccountGroup* account, int role) const +{ + if (role == Qt::ToolTipRole) { + return itemData(account->account()->selfContact(), role); + } + + return ContactListDragModel::accountData(account, role); +} diff --git a/src/psicontactlistmodel.h b/src/psicontactlistmodel.h new file mode 100755 index 000000000..25e26a9a0 --- /dev/null +++ b/src/psicontactlistmodel.h @@ -0,0 +1,42 @@ +/* + * psicontactlistmodel.h - a ContactListModel subclass that does Psi-specific things + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PSICONTACTLISTMODEL_H +#define PSICONTACTLISTMODEL_H + +#include "contactlistdragmodel.h" + +class PsiContactList; + +class PsiContactListModel : public ContactListDragModel +{ + Q_OBJECT +public: + PsiContactListModel(PsiContactList* contactList); + + // reimplemented + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex& index, const QVariant& data, int role); + virtual QVariant contactData(const PsiContact* contact, int role) const; + virtual QVariant contactGroupData(const ContactListGroup* group, int role) const; + virtual QVariant accountData(const ContactListAccountGroup* account, int role) const; +}; + +#endif diff --git a/src/psicontactlistview.cpp b/src/psicontactlistview.cpp new file mode 100755 index 000000000..d3adfcc34 --- /dev/null +++ b/src/psicontactlistview.cpp @@ -0,0 +1,40 @@ +/* + * psicontactlistview.cpp - Psi-specific ContactListView-subclass + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "psicontactlistview.h" + +#include + +#include "psicontactlistviewdelegate.h" +#include "psitooltip.h" + +PsiContactListView::PsiContactListView(QWidget* parent) + : ContactListDragView(parent) +{ + setIndentation(4); + setItemDelegate(new PsiContactListViewDelegate(this)); +} + +void PsiContactListView::showToolTip(const QModelIndex& index, const QPoint& globalPos) const +{ + Q_UNUSED(globalPos); + QString text = index.data(Qt::ToolTipRole).toString(); + PsiToolTip::showText(globalPos, text, this); +} diff --git a/src/psicontactlistview.h b/src/psicontactlistview.h new file mode 100755 index 000000000..9f019169f --- /dev/null +++ b/src/psicontactlistview.h @@ -0,0 +1,40 @@ +/* + * psicontactlistview.h - Psi-specific ContactListView-subclass + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PSICONTACTLISTVIEW_H +#define PSICONTACTLISTVIEW_H + +#include "contactlistdragview.h" + +class QAbstractItemModel; + +class PsiContactListView : public ContactListDragView +{ + Q_OBJECT + +public: + PsiContactListView(QWidget* parent); + +protected: + // reimplemented + virtual void showToolTip(const QModelIndex& index, const QPoint& globalPos) const; +}; + +#endif diff --git a/src/psicontactlistviewdelegate.cpp b/src/psicontactlistviewdelegate.cpp new file mode 100644 index 000000000..492ce6560 --- /dev/null +++ b/src/psicontactlistviewdelegate.cpp @@ -0,0 +1,268 @@ +/* + * psicontactlistviewdelegate.cpp + * Copyright (C) 2009-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "psicontactlistviewdelegate.h" + +#include + +#include "psiiconset.h" +#include "psioptions.h" +#include "contactlistview.h" +#include "common.h" + +static const QString contactListFontOptionPath = "options.ui.look.font.contactlist"; +static const QString showStatusMessagesOptionPath = "options.ui.contactlist.status-messages.show"; + +PsiContactListViewDelegate::PsiContactListViewDelegate(ContactListView* parent) + : ContactListViewDelegate(parent) + , font_(0) + , fontMetrics_(0) +{ + connect(PsiOptions::instance(), SIGNAL(optionChanged(const QString&)), SLOT(optionChanged(const QString&))); + optionChanged(contactListFontOptionPath); + optionChanged(showStatusMessagesOptionPath); +} + +PsiContactListViewDelegate::~PsiContactListViewDelegate() +{ + delete font_; + delete fontMetrics_; +} + +int PsiContactListViewDelegate::avatarSize() const +{ + return 18; +} + +QPixmap PsiContactListViewDelegate::statusPixmap(const QModelIndex& index) const +{ + int s = statusType(index); + ContactListModel::Type type = ContactListModel::indexType(index); + if (type == ContactListModel::ContactType || + type == ContactListModel::AccountType) + { + if (index.data(ContactListModel::IsAlertingRole).toBool()) { + QVariant alertData = index.data(ContactListModel::AlertPictureRole); + QIcon alert; + if (alertData.isValid()) { + if (alertData.type() == QVariant::Icon) { + alert = qvariant_cast(alertData); + } + } + + return alert.pixmap(100, 100); + } + } + + if (type == ContactListModel::ContactType) { + if (!index.data(ContactListModel::PresenceErrorRole).toString().isEmpty()) + s = STATUS_ERROR; + else if (index.data(ContactListModel::IsAgentRole).toBool()) + s = statusType(index); + else if (index.data(ContactListModel::AskingForAuthRole).toBool() && s == XMPP::Status::Offline) + s = STATUS_ASK; + else if (!index.data(ContactListModel::AuthorizesToSeeStatusRole).toBool() && s == XMPP::Status::Offline) + s = STATUS_NOAUTH; + } + + return PsiIconset::instance()->statusPtr(index.data(ContactListModel::JidRole).toString(), s)->pixmap(); +} + +void PsiContactListViewDelegate::drawContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + drawBackground(painter, option, index); + + QRect r = option.rect; + + QRect avatarRect(r); + const QPixmap statusPixmap = this->statusPixmap(index); + avatarRect.translate(1, 1); + avatarRect.setSize(statusPixmap.size()); + painter->drawPixmap(avatarRect.topLeft(), statusPixmap); + + r.setLeft(avatarRect.right() + 3); + + QColor textColor = Qt::black; + if (statusType(index) == XMPP::Status::Away || statusType(index) == XMPP::Status::XA) + textColor = PsiOptions::instance()->getOption("options.ui.look.colors.contactlist.status.away").value(); + else if (statusType(index) == XMPP::Status::DND) + textColor = PsiOptions::instance()->getOption("options.ui.look.colors.contactlist.status.do-not-disturb").value(); + else if (statusType(index) == XMPP::Status::Offline) + textColor = PsiOptions::instance()->getOption("options.ui.look.colors.contactlist.status.offline").value(); + +#if 0 + if (d->animatingNick) { + textColor = d->animateNickColor ? PsiOptions::instance()->getOption("options.ui.look.contactlist.status-change-animation.color1").value() : PsiOptions::instance()->getOption("options.ui.look.contactlist.status-change-animation.color2").value(); + xcg.setColor(QColorGroup::HighlightedText, d->animateNickColor ? PsiOptions::instance()->getOption("options.ui.look.contactlist.status-change-animation.color1").value() : PsiOptions::instance()->getOption("options.ui.look.contactlist.status-change-animation.color2").value()); + } +#endif + + QStyleOptionViewItemV2 o = option; + o.font = *font_; + o.fontMetrics = *fontMetrics_; + QPalette palette = o.palette; + palette.setColor(QPalette::Text, textColor); + o.palette = palette; + + QString text = nameText(o, index); + if (showStatusMessages_ && !statusText(index).isEmpty()) { + text = tr("%1 (%2)").arg(text).arg(statusText(index)); + } + + drawText(painter, o, r, text, index); + +#if 0 + int x; + if (d->status_single) + x = widthUsed(); + else { + QFontMetrics fm(p->font()); + const QPixmap *pix = pixmap(column); + x = fm.width(text(column)) + (pix ? pix->width() : 0) + 8; + } + + if (d->u) { + UserResourceList::ConstIterator it = d->u->priority(); + if (it != d->u->userResourceList().end()) { + if (d->u->isSecure((*it).name())) { + const QPixmap &pix = IconsetFactory::iconPixmap("psi/cryptoYes"); + int y = (height() - pix.height()) / 2; + p->drawPixmap(x, y, pix); + x += 24; + } + } + } +#endif +} + +void PsiContactListViewDelegate::drawGroup(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QStyleOptionViewItemV2 o = option; + o.font = *font_; + o.fontMetrics = *fontMetrics_; + QPalette palette = o.palette; + palette.setColor(QPalette::Base, PsiOptions::instance()->getOption("options.ui.look.colors.contactlist.grouping.header-background").value()); + palette.setColor(QPalette::Text, PsiOptions::instance()->getOption("options.ui.look.colors.contactlist.grouping.header-foreground").value()); + o.palette = palette; + + drawBackground(painter, o, index); + + QRect r = option.rect; + + const QPixmap& pixmap = index.data(ContactListModel::ExpandedRole).toBool() ? + IconsetFactory::iconPtr("psi/groupOpen")->pixmap() : + IconsetFactory::iconPtr("psi/groupClosed")->pixmap(); + + QRect pixmapRect(r); + pixmapRect.translate(1, 1); + pixmapRect.setSize(pixmap.size()); + painter->drawPixmap(pixmapRect.topLeft(), pixmap); + + r.setLeft(pixmapRect.right() + 3); + + QString text = index.data(Qt::ToolTipRole).toString(); + drawText(painter, o, r, text, index); +} + +void PsiContactListViewDelegate::drawAccount(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + QStyleOptionViewItemV2 o = option; + o.font = *font_; + o.fontMetrics = *fontMetrics_; + QPalette palette = o.palette; + palette.setColor(QPalette::Base, PsiOptions::instance()->getOption("options.ui.look.colors.contactlist.profile.header-background").value()); + palette.setColor(QPalette::Text, PsiOptions::instance()->getOption("options.ui.look.colors.contactlist.profile.header-foreground").value()); + o.palette = palette; + + drawBackground(painter, o, index); + + QRect r = option.rect; + + QRect avatarRect(r); + const QPixmap statusPixmap = this->statusPixmap(index); + avatarRect.translate(1, 1); + avatarRect.setSize(statusPixmap.size()); + painter->drawPixmap(avatarRect.topLeft(), statusPixmap); + + r.setLeft(avatarRect.right() + 3); + + QString text = nameText(o, index); + r.setRight(r.left() + o.fontMetrics.width(text)); + drawText(painter, o, r, text, index); + + QPixmap sslPixmap = index.data(ContactListModel::UsingSSLRole).toBool() ? + IconsetFactory::iconPixmap("psi/cryptoYes") : + IconsetFactory::iconPixmap("psi/cryptoNo"); + QRect sslRect(option.rect); + sslRect.setLeft(r.right() + 3); + sslRect.translate(1, 1); + sslRect.setSize(sslPixmap.size()); + painter->drawPixmap(sslRect.topLeft(), sslPixmap); + + r.setLeft(sslRect.right() + 3); + r.setRight(option.rect.right()); + + text = QString("(%1/%2)") + .arg(index.data(ContactListModel::OnlineContactsRole).toInt()) + .arg(index.data(ContactListModel::TotalContactsRole).toInt()); + drawText(painter, o, r, text, index); +} + +QRect PsiContactListViewDelegate::nameRect(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(index); + return option.rect; +} + +QRect PsiContactListViewDelegate::groupNameRect(const QStyleOptionViewItem& option, const QModelIndex& index) const +{ + Q_UNUSED(index); + return option.rect; +} + +QRect PsiContactListViewDelegate::editorRect(const QRect& nameRect) const +{ + return nameRect; +} + +void PsiContactListViewDelegate::optionChanged(const QString& option) +{ + if (option == contactListFontOptionPath) { + delete font_; + delete fontMetrics_; + + font_ = new QFont(); + font_->fromString(PsiOptions::instance()->getOption(contactListFontOptionPath).toString()); + fontMetrics_ = new QFontMetrics(*font_); + contactList()->viewport()->update(); + } + else if (option == showStatusMessagesOptionPath) { + showStatusMessages_ = PsiOptions::instance()->getOption(showStatusMessagesOptionPath).toBool(); + contactList()->viewport()->update(); + } +} + +void PsiContactListViewDelegate::drawText(QPainter* painter, const QStyleOptionViewItem& o, const QRect& rect, const QString& text, const QModelIndex& index) const +{ + QRect r(rect); + r.moveTop(r.top() + (r.height() - o.fontMetrics.height()) / 2); + rect.adjusted(0, 2, 0, 0); + ContactListViewDelegate::drawText(painter, o, r, text, index); +} diff --git a/src/psicontactlistviewdelegate.h b/src/psicontactlistviewdelegate.h new file mode 100644 index 000000000..34226b75f --- /dev/null +++ b/src/psicontactlistviewdelegate.h @@ -0,0 +1,61 @@ +/* + * psicontactlistviewdelegate.h + * Copyright (C) 2009-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PSICONTACTLISTVIEWDELEGATE_H +#define PSICONTACTLISTVIEWDELEGATE_H + +#include "contactlistviewdelegate.h" + +class PsiContactListViewDelegate : public ContactListViewDelegate +{ + Q_OBJECT +public: + PsiContactListViewDelegate(ContactListView* parent); + ~PsiContactListViewDelegate(); + + // reimplemented + virtual int avatarSize() const; + +protected: + // reimplemented + virtual void drawContact(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual void drawGroup(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual void drawAccount(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + + // reimplemented + virtual void drawText(QPainter* painter, const QStyleOptionViewItem& o, const QRect& rect, const QString& text, const QModelIndex& index) const; + + // reimplemented + virtual QRect nameRect(const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual QRect groupNameRect(const QStyleOptionViewItem& option, const QModelIndex& index) const; + virtual QRect editorRect(const QRect& nameRect) const; + + virtual QPixmap statusPixmap(const QModelIndex& index) const; + +private slots: + void optionChanged(const QString& option); + +private: + QFont* font_; + QFontMetrics* fontMetrics_; + bool showStatusMessages_; +}; + +#endif diff --git a/src/psicontactmenu.cpp b/src/psicontactmenu.cpp new file mode 100644 index 000000000..395ce2681 --- /dev/null +++ b/src/psicontactmenu.cpp @@ -0,0 +1,777 @@ +/* + * psicontactmenu.cpp - a PsiContact context menu + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "psicontactmenu.h" + +#include +#include +#include +#include + +#include "iconaction.h" +#include "iconset.h" +#include "psiaccount.h" +#include "psicontact.h" +#include "psioptions.h" +#include "resourcemenu.h" +#include "contactlistmodel.h" +#include "shortcutmanager.h" +#include "psicon.h" +#include "avatars.h" +#include "userlist.h" +#ifdef HAVE_PGPUTIL +#include "pgputil.h" +#endif + +#ifdef YAPSI +#include "yaprofile.h" +#endif + +//---------------------------------------------------------------------------- +// GroupMenu +//---------------------------------------------------------------------------- + +class GroupMenu : public QMenu +{ + Q_OBJECT +public: + GroupMenu(QWidget* parent) + : QMenu(parent) + { + } + + void updateMenu(PsiContact* contact) + { + if (isVisible()) + return; + contact_ = contact; + Q_ASSERT(contact_); + clear(); + + addGroup(tr("&None"), "", contact->userListItem().groups().isEmpty()); + addSeparator(); + + int n = 0; + QStringList groupList = contact->account()->groupList(); + // groupList.removeAll(tr("Hidden")); + foreach(QString groupName, groupList) { + QString displayName = groupName; + if (displayName.isEmpty()) + displayName = tr("General"); + + QString accelerator; + if (n++ < 9) + accelerator = "&"; + QString text = QString("%1%2. %3").arg(accelerator).arg(n).arg(displayName); + addGroup(text, groupName, contact->userListItem().groups().contains(groupName)); + } + + addSeparator(); + QAction* createNewGroupAction = new QAction(tr("&Create New..."), this); + connect(createNewGroupAction, SIGNAL(triggered()), SLOT(createNewGroup())); + addAction(createNewGroupAction); + } + +signals: + void groupActivated(QString groupName); + +private: + QPointer contact_; + QAction* createNewGroupAction_; + + /** + * \param text will be shown on screen, and \param groupName is the + * actual group name. Specify true as \param current when group is + * currently selected for a contact. + */ + void addGroup(QString text, QString groupName, bool selected) + { + QAction* action = new QAction(text, this); + addAction(action); + action->setCheckable(true); + action->setChecked(selected); + action->setProperty("groupName", QVariant(groupName)); + connect(action, SIGNAL(activated()), SLOT(actionActivated())); + } + +private slots: + void actionActivated() + { + QAction* action = static_cast(sender()); + emit groupActivated(action->property("groupName").toString()); + } + + void createNewGroup() + { + while (contact_) { + bool ok = false; + QString newgroup = QInputDialog::getText(tr("Create New Group"), + tr("Enter the new group name:"), + QLineEdit::Normal, + QString::null, + &ok, 0); + if (!ok) + break; + if (newgroup.isEmpty()) + continue; + + if (!contact_->userListItem().groups().contains(newgroup)) { + emit groupActivated(newgroup); + break; + } + } + } +}; + +//---------------------------------------------------------------------------- +// InviteToGroupChatMenu +//---------------------------------------------------------------------------- + +class InviteToGroupChatMenu : public QMenu +{ + Q_OBJECT +public: + InviteToGroupChatMenu(QWidget* parent) + : QMenu(parent) + { + } + + void updateMenu(PsiContact* contact) + { + if (isVisible()) + return; + Q_ASSERT(contact); + controller_ = contact->account()->psi(); + Q_ASSERT(controller_); + clear(); + + foreach(PsiAccount* acc, controller_->contactList()->accounts()) { + foreach(QString groupChat, acc->groupchats()) { + QAction* action = new QAction(groupChat, this); + addAction(action); + + action->setProperty("groupChat", QVariant(groupChat)); + action->setProperty("account", QVariant(acc->id())); + connect(action, SIGNAL(activated()), SLOT(actionActivated())); + } + } + } + +signals: + void inviteToGroupchat(PsiAccount* account, QString groupChat); + +private slots: + void actionActivated() + { + QAction* action = static_cast(sender()); + emit inviteToGroupchat(controller_->contactList()->getAccount(action->property("account").toString()), + action->property("groupChat").toString()); + } + +private: + PsiCon* controller_; +}; + +//---------------------------------------------------------------------------- +// PsiContactMenu::Private +//---------------------------------------------------------------------------- + +class PsiContactMenu::Private : public QObject +{ + Q_OBJECT + + QPointer contact_; + PsiContactMenu* menu_; + +public: + QAction* renameAction_; + QAction* removeAction_; + +#ifdef YAPSI + QAction* openChatAction_; + QAction* openHistoryAction_; + QAction* yaProfileAction_; + QAction* yaPhotosAction_; + QAction* yaEmailAction_; + QAction* addAction_; + QAction* authAction_; + QAction* blockAction_; + QAction* disableMoodNotificationsAction_; +#else + QAction* addAuthAction_; + QAction* receiveIncomingEventAction_; + QAction* sendMessageAction_; + QMenu* sendMessageToMenu_; + QAction* openChatAction_; + QMenu* openChatToMenu_; + QAction* openWhiteboardAction_; + QMenu* openWhiteboardToMenu_; + QMenu* executeCommandMenu_; + ResourceMenu* activeChatsMenu_; + QAction* voiceCallAction_; + QAction* sendFileAction_; + InviteToGroupChatMenu* inviteToGroupchatMenu_; + GroupMenu* groupMenu_; + QAction* transportLogonAction_; + QAction* transportLogoffAction_; + QMenu* authMenu_; + QAction* authResendAction_; + QAction* authRerequestAction_; + QAction* authRemoveAction_; + QMenu* pictureMenu_; + QAction* pictureAssignAction_; + QAction* pictureClearAction_; + QAction* gpgAssignKeyAction_; + QAction* gpgUnassignKeyAction_; + QAction* vcardAction_; + QAction* historyAction_; +#endif + +public: + Private(PsiContactMenu* menu, PsiContact* _contact) + : QObject(0) + , contact_(_contact) + , menu_(menu) + { + connect(PsiOptions::instance(), SIGNAL(optionChanged(const QString&)), SLOT(optionChanged(const QString&))); + connect(menu, SIGNAL(aboutToShow()), SLOT(updateActions())); + + connect(contact_, SIGNAL(updated()), SLOT(updateActions())); + + renameAction_ = new QAction(tr("Re&name"), this); + renameAction_->setShortcuts(menu->shortcuts("contactlist.rename")); + connect(renameAction_, SIGNAL(activated()), this, SLOT(rename())); + + removeAction_ = new QAction(tr("&Remove"), this); + removeAction_->setShortcuts(ShortcutManager::instance()->shortcuts("contactlist.delete")); + connect(removeAction_, SIGNAL(activated()), SLOT(removeContact())); + +#ifdef YAPSI + openChatAction_ = new QAction(tr("&Chat"), this); + connect(openChatAction_, SIGNAL(activated()), contact_, SLOT(openChat())); + + openHistoryAction_ = new QAction(tr("&History"), this); + connect(openHistoryAction_, SIGNAL(activated()), contact_, SLOT(history())); + + yaProfileAction_ = new QAction(tr("Pro&file"), this); + connect(yaProfileAction_, SIGNAL(activated()), contact_, SLOT(yaProfile())); + + yaPhotosAction_ = new QAction(tr("&Photos"), this); + connect(yaPhotosAction_, SIGNAL(activated()), contact_, SLOT(yaPhotos())); + + yaEmailAction_ = new QAction(tr("Send &E-mail"), this); + connect(yaEmailAction_, SIGNAL(activated()), contact_, SLOT(yaEmail())); + + addAction_ = new QAction(tr("&Add"), this); + connect(addAction_, SIGNAL(activated()), SLOT(addContact())); + + authAction_ = new QAction(tr("A&uth"), this); + connect(authAction_, SIGNAL(activated()), contact_, SLOT(rerequestAuthorizationFrom())); + + blockAction_ = new QAction(tr("&Block"), this); + connect(blockAction_, SIGNAL(activated()), contact_, SLOT(toggleBlockedState())); + + disableMoodNotificationsAction_ = new QAction(tr("Disable mood notifications"), this); + disableMoodNotificationsAction_->setCheckable(true); + connect(disableMoodNotificationsAction_, SIGNAL(triggered()), SLOT(disableMoodNotificationsTriggered())); + + menu->addAction(openChatAction_); + menu->addAction(yaEmailAction_); + menu->addAction(openHistoryAction_); + menu->addAction(yaProfileAction_); + menu->addAction(yaPhotosAction_); + menu->addSeparator(); + menu->addAction(renameAction_); + menu->addAction(removeAction_); + menu->addAction(addAction_); + menu->addAction(authAction_); + menu->addAction(blockAction_); + menu_->addSeparator(); + menu_->addAction(disableMoodNotificationsAction_); +#else + addAuthAction_ = new IconAction(tr("Add/Authorize to Contact List"), this, "psi/addContact"); + connect(addAuthAction_, SIGNAL(triggered()), SLOT(addAuth())); + + receiveIncomingEventAction_ = new IconAction(tr("&Receive Incoming Event"), this, ""); + connect(receiveIncomingEventAction_, SIGNAL(triggered()), SLOT(receiveIncomingEvent())); + receiveIncomingEventAction_->setShortcuts(ShortcutManager::instance()->shortcuts("contactlist.event")); + + sendMessageAction_ = new IconAction(tr("Send &Message"), this, "psi/sendMessage"); + connect(sendMessageAction_, SIGNAL(triggered()), SLOT(sendMessage())); + sendMessageAction_->setShortcuts(ShortcutManager::instance()->shortcuts("contactlist.message")); + + openChatAction_ = new IconAction(tr("Open &Chat Window"), this, "psi/start-chat"); + connect(openChatAction_, SIGNAL(triggered()), SLOT(openChat())); + openChatAction_->setShortcuts(ShortcutManager::instance()->shortcuts("contactlist.chat")); + + openWhiteboardAction_ = new IconAction(tr("Open a &Whiteboard"), this, "psi/whiteboard"); + connect(openWhiteboardAction_, SIGNAL(triggered()), SLOT(openWhiteboard())); + + voiceCallAction_ = new IconAction(tr("Voice Call"), this, "psi/voice"); + connect(voiceCallAction_, SIGNAL(triggered()), SLOT(voiceCall())); + + sendFileAction_ = new IconAction(tr("Send &File"), this, "psi/upload"); + connect(sendFileAction_, SIGNAL(triggered()), SLOT(sendFile())); + + transportLogonAction_ = new IconAction(tr("&Log on"), this, ""); + connect(transportLogonAction_, SIGNAL(triggered()), SLOT(transportLogon())); + transportLogonAction_->setShortcuts(ShortcutManager::instance()->shortcuts("contactlist.login-transport")); + + transportLogoffAction_ = new IconAction(tr("Log Off"), this, ""); + connect(transportLogoffAction_, SIGNAL(triggered()), SLOT(transportLogoff())); + + authResendAction_ = new IconAction(tr("Resend Authorization To"), this, ""); + connect(authResendAction_, SIGNAL(triggered()), SLOT(authResend())); + + authRerequestAction_ = new IconAction(tr("Rerequest Authorization From"), this, ""); + connect(authRerequestAction_, SIGNAL(triggered()), SLOT(authRerequest())); + + authRemoveAction_ = new IconAction(tr("Remove Authorization From"), this, ""); + connect(authRemoveAction_, SIGNAL(triggered()), SLOT(authRemove())); + + pictureAssignAction_ = new IconAction(tr("&Assign Custom Picture"), this, ""); + connect(pictureAssignAction_, SIGNAL(triggered()), SLOT(pictureAssign())); + pictureAssignAction_->setShortcuts(ShortcutManager::instance()->shortcuts("contactlist.assign-custom-avatar")); + + pictureClearAction_ = new IconAction(tr("&Clear Custom Picture"), this, ""); + connect(pictureClearAction_, SIGNAL(triggered()), SLOT(pictureClear())); + pictureClearAction_->setShortcuts(ShortcutManager::instance()->shortcuts("contactlist.clear-custom-avatar")); + + gpgAssignKeyAction_ = new IconAction(tr("Assign Open&PGP Key"), this, "psi/gpg-yes"); + connect(gpgAssignKeyAction_, SIGNAL(triggered()), SLOT(gpgAssignKey())); + + gpgUnassignKeyAction_ = new IconAction(tr("Unassign Open&PGP Key"), this, "psi/gpg-no"); + connect(gpgUnassignKeyAction_, SIGNAL(triggered()), SLOT(gpgUnassignKey())); + + vcardAction_ = new IconAction(tr("User &Info"), this, "psi/vCard"); + connect(vcardAction_, SIGNAL(triggered()), SLOT(vcard())); + vcardAction_->setShortcuts(ShortcutManager::instance()->shortcuts("common.user-info")); + + historyAction_ = new IconAction(tr("&History"), this, "psi/history"); + connect(historyAction_, SIGNAL(triggered()), SLOT(history())); + historyAction_->setShortcuts(ShortcutManager::instance()->shortcuts("common.history")); + + inviteToGroupchatMenu_ = new InviteToGroupChatMenu(menu_); + inviteToGroupchatMenu_->setTitle(tr("Invite To")); + connect(inviteToGroupchatMenu_, SIGNAL(inviteToGroupchat(PsiAccount*, QString)), SLOT(inviteToGroupchat(PsiAccount*, QString))); + + groupMenu_ = new GroupMenu(menu_); + groupMenu_->setTitle(tr("&Group")); + connect(groupMenu_, SIGNAL(groupActivated(QString)), SLOT(setContactGroup(QString))); + + sendMessageToMenu_ = new ResourceMenu(tr("Send Message To"), contact_, menu_); + connect(sendMessageToMenu_, SIGNAL(resourceActivated(PsiContact*, const XMPP::Jid&)), SLOT(sendMessageTo(PsiContact*, const XMPP::Jid&))); + + openChatToMenu_ = new ResourceMenu(tr("Open Chat To"), contact_, menu_); + connect(openChatToMenu_, SIGNAL(resourceActivated(PsiContact*, const XMPP::Jid&)), SLOT(openChatTo(PsiContact*, const XMPP::Jid&))); + + openWhiteboardToMenu_ = new ResourceMenu(tr("Open a Whiteboard To"), contact_, menu_); + connect(openWhiteboardToMenu_, SIGNAL(resourceActivated(PsiContact*, const XMPP::Jid&)), SLOT(openWhiteboardTo(PsiContact*, const XMPP::Jid&))); + + executeCommandMenu_ = new ResourceMenu(tr("E&xecute Command"), contact_, menu_); + connect(executeCommandMenu_, SIGNAL(resourceActivated(PsiContact*, const XMPP::Jid&)), SLOT(executeCommand(PsiContact*, const XMPP::Jid&))); + + activeChatsMenu_ = new ResourceMenu(tr("Active Chats"), contact_, menu_); + activeChatsMenu_->setActiveChatsMode(true); + connect(activeChatsMenu_, SIGNAL(resourceActivated(PsiContact*, const XMPP::Jid&)), SLOT(openActiveChat(PsiContact*, const XMPP::Jid&))); + + menu_->addAction(addAuthAction_); + menu_->addSeparator(); + menu_->addAction(receiveIncomingEventAction_); + menu_->addSeparator(); + menu_->addAction(sendMessageAction_); + menu_->addMenu(sendMessageToMenu_); + menu_->addAction(openChatAction_); + menu_->addMenu(openChatToMenu_); + menu_->addAction(openWhiteboardAction_); + menu_->addMenu(openWhiteboardToMenu_); + menu_->addMenu(executeCommandMenu_); + menu_->addMenu(activeChatsMenu_); + menu_->addAction(voiceCallAction_); + menu_->addSeparator(); + menu_->addAction(sendFileAction_); + menu_->addMenu(inviteToGroupchatMenu_); + menu_->addSeparator(); + menu_->addAction(renameAction_); + menu_->addMenu(groupMenu_); + menu_->addAction(transportLogonAction_); + menu_->addAction(transportLogoffAction_); + authMenu_ = menu_->addMenu(tr("Authorization")); + authMenu_->addAction(authResendAction_); + authMenu_->addAction(authRerequestAction_); + authMenu_->addAction(authRemoveAction_); + menu_->addAction(removeAction_); + menu_->addSeparator(); + pictureMenu_ = menu_->addMenu(tr("&Picture")); + pictureMenu_->addAction(pictureAssignAction_); + pictureMenu_->addAction(pictureClearAction_); + menu_->addAction(gpgAssignKeyAction_); + menu_->addAction(gpgUnassignKeyAction_); + menu_->addAction(vcardAction_); + menu_->addAction(historyAction_); +#endif + + updateActions(); + } + +private slots: + void optionChanged(const QString& option) + { +#ifdef YAPSI + if (option == "options.ya.popups.moods.enable") { + updateActions(); + } +#else + Q_UNUSED(option); +#endif + } + + void updateActions() + { + if (!contact_) + return; + + renameAction_->setEnabled(contact_->isEditable()); + removeAction_->setEnabled(contact_->removeAvailable()); + +#ifdef YAPSI + YaProfile* profile = YaProfile::create(contact_->account(), contact_->jid()); + openHistoryAction_->setEnabled(contact_->historyAvailable()); + delete profile; + + yaProfileAction_->setEnabled(contact_->isYaJid()); + yaPhotosAction_->setEnabled(contact_->isYaJid()); + addAction_->setVisible(contact_->addAvailable()); + authAction_->setVisible(contact_->authAvailable()); + blockAction_->setEnabled(contact_->blockAvailable()); + + blockAction_->setText(contact_->isBlocked() ? + tr("&Unblock") : tr("&Block")); + + disableMoodNotificationsAction_->setChecked(!contact_->moodNotificationsEnabled()); + disableMoodNotificationsAction_->setEnabled(PsiOptions::instance()->getOption("options.ya.popups.moods.enable").toBool()); +#else + inviteToGroupchatMenu_->updateMenu(contact_); + groupMenu_->updateMenu(contact_); + + addAuthAction_->setVisible(!contact_->isSelf() && !contact_->inList() && !PsiOptions::instance()->getOption("options.ui.contactlist.lockdown-roster").toBool()); + addAuthAction_->setEnabled(contact_->account()->isAvailable()); + receiveIncomingEventAction_->setVisible(contact_->alerting()); + if (!PsiOptions::instance()->getOption("options.ui.message.enabled").toBool()) { + sendMessageAction_->setVisible(false); + sendMessageToMenu_->setVisible(false); + } + sendMessageToMenu_->setEnabled(!sendMessageToMenu_->isEmpty()); + openChatToMenu_->setEnabled(!openChatToMenu_->isEmpty()); + openWhiteboardToMenu_->setEnabled(!openWhiteboardToMenu_->isEmpty()); +#ifndef WHITEBOARDING + openWhiteboardAction_->setVisible(false); + openWhiteboardToMenu_->setVisible(false); +#endif + executeCommandMenu_->setEnabled(!executeCommandMenu_->isEmpty()); + activeChatsMenu_->setEnabled(!activeChatsMenu_->isEmpty()); + // FIXME + // voiceCallAction_->setVisible(contact_->account()->avCallManager() && !contact_->isAgent()); + voiceCallAction_->setEnabled(contact_->account()->isAvailable()); + sendFileAction_->setVisible(!contact_->isAgent()); + sendFileAction_->setEnabled(contact_->account()->isAvailable()); + inviteToGroupchatMenu_->setEnabled(!inviteToGroupchatMenu_->isEmpty()); + renameAction_->setVisible(!PsiOptions::instance()->getOption("options.ui.contactlist.lockdown-roster").toBool()); + renameAction_->setEnabled(contact_->isEditable()); + if (contact_->isAgent()) { + groupMenu_->setVisible(false); + } + groupMenu_->setEnabled(contact_->isEditable() && contact_->isDragEnabled()); + transportLogonAction_->setVisible(contact_->isAgent()); + transportLogonAction_->setEnabled(contact_->account()->isAvailable() && contact_->status().type() == XMPP::Status::Offline); + transportLogoffAction_->setVisible(contact_->isAgent()); + transportLogoffAction_->setEnabled(contact_->account()->isAvailable() && contact_->status().type() != XMPP::Status::Offline); + if (PsiOptions::instance()->getOption("options.ui.contactlist.lockdown-roster").toBool() || !contact_->inList()) { + authMenu_->setVisible(false); + } + authMenu_->setEnabled(contact_->account()->isAvailable()); + removeAction_->setVisible(!PsiOptions::instance()->getOption("options.ui.contactlist.lockdown-roster").toBool()); + // removeAction_->setEnabled(contact_->account()->isAvailable() || !contact_->inList()); + if (!PsiOptions::instance()->getOption("options.ui.menu.contact.custom-picture").toBool()) { + pictureMenu_->setVisible(false); + } +#ifdef HAVE_PGPUTIL + gpgAssignKeyAction_->setVisible(PGPUtil::instance().pgpAvailable() && PsiOptions::instance()->getOption("options.ui.menu.contact.custom-pgp-key").toBool() && contact_->userListItem().publicKeyID().isEmpty()); + gpgUnassignKeyAction_->setVisible(PGPUtil::instance().pgpAvailable() && PsiOptions::instance()->getOption("options.ui.menu.contact.custom-pgp-key").toBool() && !contact_->userListItem().publicKeyID().isEmpty()); +#endif // HAVE_PGPUTIL +#endif // YAPSI + } + +#ifdef YAPSI + void disableMoodNotificationsTriggered() + { + if (contact_) { + contact_->setMoodNotificationsEnabled(!disableMoodNotificationsAction_->isChecked()); + } + } +#endif + + void rename() + { + if (!contact_) + return; + + menu_->model()->renameSelectedItem(); + } + + void addContact() + { + emit menu_->addSelection(); + } + + void removeContact() + { + emit menu_->removeSelection(); + } + +#ifndef YAPSI + void inviteToGroupchat(PsiAccount* account, QString groupchat) + { + if (!contact_) + return; + account->actionInvite(contact_->jid(), groupchat); + QMessageBox::information(0, tr("Invitation"), + tr("Sent groupchat invitation to %1.").arg(contact_->name())); + } + + void setContactGroup(QString group) + { + if (!contact_) + return; + contact_->setGroups(QStringList() << group); + } + + void addAuth() + { + if (!contact_) + return; + contact_->account()->actionAdd(contact_->jid()); + contact_->account()->actionAuth(contact_->jid()); + QMessageBox::information(0, tr("Add"), + tr("Added/Authorized %1 to the contact list.").arg(contact_->name())); + } + + void receiveIncomingEvent() + { + if (!contact_) + return; + contact_->account()->actionRecvEvent(contact_->jid()); + } + + void sendMessage() + { + if (!contact_) + return; + contact_->account()->actionSendMessage(contact_->jid()); + } + + void openChat() + { + if (!contact_) + return; + contact_->account()->actionOpenChat(contact_->jid()); + } + + void openWhiteboard() + { + if (!contact_) + return; +#ifdef WHITEBOARDING + contact_->account()->actionOpenWhiteboard(contact_->jid()); +#endif + } + + void voiceCall() + { + if (!contact_) + return; + contact_->account()->actionVoice(contact_->jid()); + } + + void sendFile() + { + if (!contact_) + return; + contact_->account()->actionSendFile(contact_->jid()); + } + + void transportLogon() + { + if (!contact_) + return; + contact_->account()->actionAgentSetStatus(contact_->jid(), contact_->account()->status()); + } + + void transportLogoff() + { + if (!contact_) + return; + contact_->account()->actionAgentSetStatus(contact_->jid(), XMPP::Status::Offline); + } + + void authResend() + { + if (!contact_) + return; + contact_->account()->actionAuth(contact_->jid()); + QMessageBox::information(0, tr("Authorize"), + tr("Sent authorization to %1.").arg(contact_->name())); + } + + void authRerequest() + { + if (!contact_) + return; + contact_->account()->actionAuthRequest(contact_->jid()); + QMessageBox::information(0, tr("Authorize"), + tr("Rerequested authorization from %1.").arg(contact_->name())); + } + + void authRemove() + { + if (!contact_) + return; + + int n = QMessageBox::information(0, tr("Remove"), + tr("Are you sure you want to remove authorization from %1?").arg(contact_->name()), + tr("&Yes"), tr("&No")); + + if(n == 0) + contact_->account()->actionAuthRemove(contact_->jid()); + } + + void pictureAssign() + { + if (!contact_) + return; + QString file = QFileDialog::getOpenFileName(0, tr("Choose an Image"), "", tr("All files (*.png *.jpg *.gif)")); + if (!file.isNull()) { + contact_->account()->avatarFactory()->importManualAvatar(contact_->jid(),file); + } + } + + void pictureClear() + { + if (!contact_) + return; + contact_->account()->avatarFactory()->removeManualAvatar(contact_->jid()); + } + + void gpgAssignKey() + { + if (!contact_) + return; + contact_->account()->actionAssignKey(contact_->jid()); + } + + void gpgUnassignKey() + { + if (!contact_) + return; + contact_->account()->actionUnassignKey(contact_->jid()); + } + + void vcard() + { + if (!contact_) + return; + contact_->account()->actionInfo(contact_->jid()); + } + + void history() + { + if (!contact_) + return; + contact_->account()->actionHistory(contact_->jid()); + } + + void sendMessageTo(PsiContact*, const XMPP::Jid& jid) + { + if (!contact_) + return; + + contact_->account()->actionSendMessage(jid); + } + + void openChatTo(PsiContact*, const XMPP::Jid& jid) + { + if (!contact_) + return; + + contact_->account()->actionOpenChatSpecific(jid); + } + + void openWhiteboardTo(PsiContact*, const XMPP::Jid& jid) + { + if (!contact_) + return; + +#ifdef WHITEBOARDING + contact_->account()->actionOpenWhiteboardSpecific(jid); +#else + Q_UNUSED(jid); +#endif + } + + void executeCommand(PsiContact*, const XMPP::Jid& jid) + { + if (!contact_) + return; + + contact_->account()->actionExecuteCommandSpecific(jid); + } + + void openActiveChat(PsiContact*, const XMPP::Jid& jid) + { + if (!contact_) + return; + + contact_->account()->actionOpenChatSpecific(jid); + } +#endif +}; + +PsiContactMenu::PsiContactMenu(PsiContact* contact, ContactListModel* model) + : ContactListItemMenu(contact, model) +{ + d = new Private(this, contact); +} + +PsiContactMenu::~PsiContactMenu() +{ + delete d; +} + +QList PsiContactMenu::availableActions() const +{ + QList result; + foreach(QAction* a, ContactListItemMenu::availableActions()) { + // if (a != d->removeAction_) + result << a; + } + return result; +} + +#include "psicontactmenu.moc" diff --git a/src/psicontactmenu.h b/src/psicontactmenu.h new file mode 100644 index 000000000..ea7be2832 --- /dev/null +++ b/src/psicontactmenu.h @@ -0,0 +1,48 @@ +/* + * psicontactmenu.h - a PsiContact context menu + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PSICONTACTMENU_H +#define PSICONTACTMENU_H + +#include "contactlistitemmenu.h" + +class PsiContact; + +class PsiContactMenu : public ContactListItemMenu +{ + Q_OBJECT +public: + PsiContactMenu(PsiContact* contact, ContactListModel* model); + ~PsiContactMenu(); + + // reimplemented + virtual QList availableActions() const; + +signals: + void removeSelection(); + void addSelection(); + +private: + class Private; + friend class Private; + Private* d; +}; + +#endif diff --git a/src/psievent.cpp b/src/psievent.cpp index b0a07e4c3..d5e5e4b91 100644 --- a/src/psievent.cpp +++ b/src/psievent.cpp @@ -23,14 +23,17 @@ #include #include #include +#include #include "psicon.h" #include "psiaccount.h" #include "xmpp_xmlcommon.h" +#include "dummystream.h" #include "filetransfer.h" #include "applicationinfo.h" #include "psicontactlist.h" #include "atomicxmlfile.h" +#include "globaleventqueue.h" #include "psioptions.h" #include "avcall/avcall.h" @@ -47,31 +50,6 @@ const int eventPriorityRosterExchange = 0; // LEGOPTFIXME: was uninitialised using namespace XMPP; using namespace XMLHelper; -//---------------------------------------------------------------------------- -// DummyStream -//---------------------------------------------------------------------------- -class DummyStream : public Stream -{ -public: - QDomDocument & doc() const { return v_doc; } - QString baseNS() const { return "jabber:client"; } - bool old() const { return false; } - - void close() { } - bool stanzaAvailable() const { return false; } - Stanza read() { return Stanza(); } - void write(const Stanza &) { } - - int errorCondition() const { return 0; } - QString errorText() const { return QString::null; } - QDomElement errorAppSpec() const { return v_doc.documentElement(); } - -private: - static QDomDocument v_doc; -}; - -QDomDocument DummyStream::v_doc; - //---------------------------------------------------------------------------- // PsiEvent //---------------------------------------------------------------------------- @@ -80,6 +58,9 @@ PsiEvent::PsiEvent(PsiAccount *acc) v_originLocal = false; v_late = false; v_account = acc; +#ifdef YAPSI + v_id = -1; +#endif } PsiEvent::PsiEvent(const PsiEvent &from) @@ -90,6 +71,9 @@ PsiEvent::PsiEvent(const PsiEvent &from) v_ts = from.v_ts; v_jid = from.v_jid; v_account = from.v_account; +#ifdef YAPSI + v_id = from.v_id; +#endif } PsiEvent::~PsiEvent() @@ -111,6 +95,11 @@ PsiAccount *PsiEvent::account() const return v_account; } +void PsiEvent::setAccount(PsiAccount* account) +{ + v_account = account; +} + bool PsiEvent::originLocal() const { return v_originLocal; @@ -145,6 +134,9 @@ QDomElement PsiEvent::toXml(QDomDocument *doc) const { QDomElement e = doc->createElement("event"); e.setAttribute("type", metaObject()->className()); +#ifdef YAPSI + e.setAttribute("id", QString::number(v_id)); +#endif e.appendChild( textTag(*doc, "originLocal", v_originLocal) ); e.appendChild( textTag(*doc, "late", v_late) ); @@ -164,6 +156,9 @@ bool PsiEvent::fromXml(PsiCon *psi, PsiAccount *account, const QDomElement *e) return false; if ( e->attribute("type") != metaObject()->className() ) return false; +#ifdef YAPSI + v_id = e->attribute("id").toInt(); +#endif readBoolEntry(*e, "originLocal", &v_originLocal); readBoolEntry(*e, "late", &v_late); @@ -201,6 +196,18 @@ PsiEvent *PsiEvent::copy() const return 0; } +#ifdef YAPSI +int PsiEvent::id() const +{ + return v_id; +} + +void PsiEvent::setId(int id) +{ + v_id = id; +} +#endif + //---------------------------------------------------------------------------- // MessageEvent //---------------------------------------------------------------------------- @@ -408,9 +415,9 @@ bool AuthEvent::fromXml(PsiCon *psi, PsiAccount *account, const QDomElement *e) v_from = Jid( subTagText(*e, "from") ); v_at = subTagText(*e, "authType"); - v_nick = subTagText(*e, "nick"); + v_nick = subTagText(*e, "nick"); - return false; + return true; } int AuthEvent::priority() const @@ -422,11 +429,11 @@ QString AuthEvent::description() const { QString result; if (authType() == "subscribe") - result = tr("This user wants to subscribe to your presence."); + result = tr("%1 wants to subscribe to your presence.").arg(from().bare()); else if (authType() == "subscribed") - result = tr("You are now authorized."); - else if (authType() == "unsubscribed") - result = tr("Your authorization has been removed!"); + result = tr("%1 authorized you to view his status.").arg(from().bare()); + else if (authType() == "unsubscribed" || authType() == "unsubscribe") + result = tr("%1 removed your authorization to view his status!").arg(from().bare()); else Q_ASSERT(false); @@ -610,6 +617,66 @@ void StatusEvent::setStatus(const XMPP::Status& s) v_status = s; }*/ +//---------------------------------------------------------------------------- +// EventIdGenerator +//---------------------------------------------------------------------------- + +class EventIdGenerator : public QObject +{ + Q_OBJECT +public: + static EventIdGenerator* instance(); + + int getId(); + +private: + static EventIdGenerator* instance_; + int id_; + + EventIdGenerator(); +}; + +EventIdGenerator* EventIdGenerator::instance_ = 0; + +EventIdGenerator* EventIdGenerator::instance() +{ + if (!instance_) { + instance_ = new EventIdGenerator(); + } + return instance_; +} + +static const QString idGeneratorOptionPath = "options.ya.last-event-id"; + +EventIdGenerator::EventIdGenerator() + : QObject(QCoreApplication::instance()) +{ +#ifdef YAPSI + id_ = PsiOptions::instance()->getOption(idGeneratorOptionPath).toInt(); +#else + id_ = 0; +#endif +} + +int EventIdGenerator::getId() +{ + int result = id_; +#ifdef YAPSI + // TODO: upgrade to uint64 + PsiOptions::instance()->setOption(idGeneratorOptionPath, ++id_); +#else + ++id_; +#endif + + if (id_ > 0x7FFFFFFF) { + id_ = 0; + } + + Q_ASSERT(id_ >= 0); + Q_ASSERT(result >= 0); + return result; +} + //---------------------------------------------------------------------------- // AvCallEvent //---------------------------------------------------------------------------- @@ -668,10 +735,22 @@ PsiEvent *AvCallEvent::copy() const // EventItem //---------------------------------------------------------------------------- -EventItem::EventItem(PsiEvent *_e, int i) +EventItem::EventItem(PsiEvent *_e) { e = _e; - v_id = i; +#ifdef YAPSI + if (e->id() >= 0) { + v_id = e->id(); + } + else { + Q_ASSERT(e->account()); + v_id = EventIdGenerator::instance()->getId(); + e->setId(v_id); + } +#else + Q_ASSERT(e->account()); + v_id = EventIdGenerator::instance()->getId(); +#endif } EventItem::EventItem(const EventItem &from) @@ -698,43 +777,70 @@ PsiEvent* EventItem::event() const // EventQueue //---------------------------------------------------------------------------- -class EventQueue::Private -{ -public: - Private() { } - - QList list; - PsiCon *psi; - PsiAccount *account; -}; - EventQueue::EventQueue(PsiAccount *account) + : psi_(0) + , account_(0) + , enabled_(false) { - d = new Private(); - - d->account = account; - d->psi = account->psi(); + account_ = account; + psi_ = account_->psi(); } EventQueue::EventQueue(const EventQueue &from) : QObject() + , psi_(0) + , account_(0) + , enabled_(false) { - d = new Private(); - - *this = from; + Q_ASSERT(false); + Q_UNUSED(from); } EventQueue::~EventQueue() { - delete d; + setEnabled(false); +} + +bool EventQueue::enabled() const +{ + return enabled_; +} + +void EventQueue::setEnabled(bool enabled) +{ + if (enabled_ != enabled) { + enabled_ = enabled; + foreach(EventItem* i, list_) { + if (enabled) + GlobalEventQueue::instance()->enqueue(i); + else + GlobalEventQueue::instance()->dequeue(i); + } + } +} + +EventQueue &EventQueue::operator= (const EventQueue &from) +{ + while(!list_.isEmpty()) + delete list_.takeFirst(); + + psi_ = from.psi_; + account_ = from.account_; + + foreach(EventItem *i, from.list_) { + PsiEvent *e = i->event(); + enqueue( e->copy() ); + } + + return *this; } int EventQueue::nextId() const { - if (d->list.isEmpty()) + if (list_.isEmpty()) return -1; - EventItem *i = d->list.first(); + EventItem *i = list_.first(); if(!i) return -1; return i->id(); @@ -742,13 +848,13 @@ int EventQueue::nextId() const int EventQueue::count() const { - return d->list.count(); + return list_.count(); } int EventQueue::count(const Jid &j, bool compareRes) const { int total = 0; - foreach(EventItem *i, d->list) { + foreach(EventItem *i, list_) { Jid j2(i->event()->jid()); if(j.compare(j2, compareRes)) ++total; @@ -758,15 +864,19 @@ int EventQueue::count(const Jid &j, bool compareRes) const void EventQueue::enqueue(PsiEvent *e) { - EventItem *i = new EventItem(e, d->psi->getId()); + EventItem *i = new EventItem(e); + + if (enabled_) { + GlobalEventQueue::instance()->enqueue(i); + } int prior = e->priority(); bool found = false; // skip all with higher or equal priority - foreach(EventItem *ei, d->list) { + foreach(EventItem *ei, list_) { if (ei && ei->event()->priority() < prior ) { - d->list.insert(d->list.indexOf(ei), i); + list_.insert(list_.indexOf(ei), i); found = true; break; } @@ -774,7 +884,7 @@ void EventQueue::enqueue(PsiEvent *e) // everything else if ( !found ) - d->list.append(i); + list_.append(i); emit queueChanged(); } @@ -784,9 +894,12 @@ void EventQueue::dequeue(PsiEvent *e) if ( !e ) return; - foreach(EventItem *i, d->list) { + foreach(EventItem *i, list_) { if ( e == i->event() ) { - d->list.removeAll(i); + if (enabled_) { + GlobalEventQueue::instance()->dequeue(i); + } + list_.removeAll(i); emit queueChanged(); delete i; return; @@ -796,11 +909,14 @@ void EventQueue::dequeue(PsiEvent *e) PsiEvent *EventQueue::dequeue(const Jid &j, bool compareRes) { - foreach(EventItem *i, d->list) { + foreach(EventItem *i, list_) { PsiEvent *e = i->event(); Jid j2(e->jid()); if(j.compare(j2, compareRes)) { - d->list.removeAll(i); + if (enabled_) { + GlobalEventQueue::instance()->dequeue(i); + } + list_.removeAll(i); emit queueChanged(); delete i; return e; @@ -812,7 +928,7 @@ PsiEvent *EventQueue::dequeue(const Jid &j, bool compareRes) PsiEvent *EventQueue::peek(const Jid &j, bool compareRes) const { - foreach(EventItem *i, d->list) { + foreach(EventItem *i, list_) { PsiEvent *e = i->event(); Jid j2(e->jid()); if(j.compare(j2, compareRes)) { @@ -825,14 +941,17 @@ PsiEvent *EventQueue::peek(const Jid &j, bool compareRes) const PsiEvent *EventQueue::dequeueNext() { - if (d->list.isEmpty()) + if (list_.isEmpty()) return 0; - EventItem *i = d->list.first(); + EventItem *i = list_.first(); if(!i) return 0; PsiEvent *e = i->event(); - d->list.removeAll(i); + if (enabled_) { + GlobalEventQueue::instance()->dequeue(i); + } + list_.removeAll(i); emit queueChanged(); delete i; return e; @@ -840,10 +959,10 @@ PsiEvent *EventQueue::dequeueNext() PsiEvent *EventQueue::peekNext() const { - if (d->list.isEmpty()) + if (list_.isEmpty()) return 0; - EventItem *i = d->list.first(); + EventItem *i = list_.first(); if(!i) return 0; return i->event(); @@ -851,7 +970,7 @@ PsiEvent *EventQueue::peekNext() const PsiEvent *EventQueue::peekFirstChat(const Jid &j, bool compareRes) const { - foreach(EventItem *i, d->list) { + foreach(EventItem *i, list_) { PsiEvent *e = i->event(); if(e->type() == PsiEvent::Message) { MessageEvent *me = (MessageEvent *)e; @@ -869,23 +988,35 @@ bool EventQueue::hasChats(const Jid &j, bool compareRes) const } // this function extracts all chats from the queue, and returns a list of queue positions -void EventQueue::extractChats(QList *el, const Jid &j, bool compareRes) +void EventQueue::extractChats(QList *el, const Jid &j, bool compareRes, bool removeEvents) { bool changed = false; - for(QList::Iterator it = d->list.begin(); it != d->list.end();) { + for(QList::Iterator it = list_.begin(); it != list_.end();) { PsiEvent *e = (*it)->event(); + bool extract = false; if(e->type() == PsiEvent::Message) { MessageEvent *me = (MessageEvent *)e; - if(j.compare(me->from(), compareRes) && me->message().type() == "chat") { - el->append(me); - EventItem* ei = *it; - it = d->list.erase(it); - delete ei; - changed = true; - continue; + if(j.compare(me->from(), compareRes) && me->message().type() == "chat") { // FIXME: refactor-refactor-refactor + extract = true; + } + } + + if (extract) { + el->append(e); + } + + if (extract && removeEvents) { + EventItem* ei = *it; + if (enabled_) { + GlobalEventQueue::instance()->dequeue(ei); } + it = list_.erase(it); + delete ei; + changed = true; + continue; } + ++it; } @@ -898,12 +1029,15 @@ void EventQueue::extractMessages(QList *el) { bool changed = false; - for(QList::Iterator it = d->list.begin(); it != d->list.end();) { + for(QList::Iterator it = list_.begin(); it != list_.end();) { PsiEvent *e = (*it)->event(); if(e->type() == PsiEvent::Message) { el->append(e); EventItem* ei = *it; - it = d->list.erase(it); + if (enabled_) { + GlobalEventQueue::instance()->dequeue(ei); + } + it = list_.erase(it); delete ei; changed = true; continue; @@ -917,7 +1051,7 @@ void EventQueue::extractMessages(QList *el) void EventQueue::printContent() const { - foreach(EventItem *i, d->list) { + foreach(EventItem *i, list_) { PsiEvent *e = i->event(); printf(" %d: (%d) from=[%s] jid=[%s]\n", i->id(), e->type(), qPrintable(e->from().full()), qPrintable(e->jid().full())); } @@ -925,8 +1059,8 @@ void EventQueue::printContent() const void EventQueue::clear() { - while(!d->list.isEmpty()) - delete d->list.takeFirst(); + while(!list_.isEmpty()) + delete list_.takeFirst(); emit queueChanged(); } @@ -936,12 +1070,15 @@ void EventQueue::clear(const Jid &j, bool compareRes) { bool changed = false; - for(QList::Iterator it = d->list.begin(); it != d->list.end();) { + for(QList::Iterator it = list_.begin(); it != list_.end();) { PsiEvent *e = (*it)->event(); Jid j2(e->jid()); if(j.compare(j2, compareRes)) { EventItem* ei = *it; - it = d->list.erase(it); + if (enabled_) { + GlobalEventQueue::instance()->dequeue(ei); + } + it = list_.erase(it); delete ei; changed = true; } @@ -953,28 +1090,13 @@ void EventQueue::clear(const Jid &j, bool compareRes) emit queueChanged(); } -EventQueue &EventQueue::operator= (const EventQueue &from) -{ - while(!d->list.isEmpty()) - delete d->list.takeFirst(); - - d->psi = from.d->psi; - - foreach(EventItem *i, from.d->list) { - PsiEvent *e = i->event(); - enqueue( e->copy() ); - } - - return *this; -} - QDomElement EventQueue::toXml(QDomDocument *doc) const { QDomElement e = doc->createElement("eventQueue"); e.setAttribute("version", "1.0"); e.appendChild(textTag(doc, "progver", ApplicationInfo::version())); - foreach(EventItem *i, d->list) { + foreach(EventItem *i, list_) { QDomElement event = i->event()->toXml(doc); e.appendChild( event ); } @@ -1007,14 +1129,14 @@ bool EventQueue::fromXml(const QDomElement *q) QString eventType = e.attribute("type"); if ( eventType == "MessageEvent" ) { event = new MessageEvent(0); - if ( !event->fromXml(d->psi, d->account, &e) ) { + if ( !event->fromXml(psi_, account_, &e) ) { delete event; event = 0; } } else if ( eventType == "AuthEvent" ) { event = new AuthEvent("", "", 0); - if ( !event->fromXml(d->psi, d->account, &e) ) { + if ( !event->fromXml(psi_, account_, &e) ) { delete event; event = 0; } @@ -1027,6 +1149,18 @@ bool EventQueue::fromXml(const QDomElement *q) return true; } +QList EventQueue::eventsFor(const XMPP::Jid& jid, bool compareRes) +{ + QList result; + + foreach(EventItem* i, list_) { + if (i->event()->from().compare(jid, compareRes)) + result << QPair(i->id(), i->event()); + } + + return result; +} + bool EventQueue::toFile(const QString &fname) { QDomDocument doc; @@ -1047,3 +1181,5 @@ bool EventQueue::fromFile(const QString &fname) QDomElement base = doc.documentElement(); return fromXml(&base); } + +#include "psievent.moc" diff --git a/src/psievent.h b/src/psievent.h index 9901c77cf..5748d035d 100644 --- a/src/psievent.h +++ b/src/psievent.h @@ -78,6 +78,7 @@ class PsiEvent : public QObject void setTimeStamp(const QDateTime &t); PsiAccount *account() const; + void setAccount(PsiAccount* account); virtual QDomElement toXml(QDomDocument *) const; virtual bool fromXml(PsiCon *, PsiAccount *, const QDomElement *); @@ -86,6 +87,11 @@ class PsiEvent : public QObject virtual QString description() const; +#ifdef YAPSI + int id() const; + void setId(int id); +#endif + virtual PsiEvent *copy() const; private: @@ -93,6 +99,9 @@ class PsiEvent : public QObject QDateTime v_ts; XMPP::Jid v_jid; PsiAccount *v_account; +#ifdef YAPSI + int v_id; +#endif }; // normal, chat, error, headline, etc @@ -295,7 +304,7 @@ class AvCallEvent : public PsiEvent class EventItem { public: - EventItem(PsiEvent *_e, int i); + EventItem(PsiEvent *_e); EventItem(const EventItem &from); ~EventItem(); int id() const; @@ -317,6 +326,9 @@ class EventQueue : public QObject EventQueue &operator= (const EventQueue &); + bool enabled() const; + void setEnabled(bool enabled); + int nextId() const; int count() const; int count(const XMPP::Jid &, bool compareRes=true) const; @@ -329,10 +341,12 @@ class EventQueue : public QObject bool hasChats(const XMPP::Jid &, bool compareRes=true) const; PsiEvent *peekFirstChat(const XMPP::Jid &, bool compareRes=true) const; void extractMessages(QList *list); - void extractChats(QList *list, const XMPP::Jid &, bool compareRes=true); + void extractChats(QList *list, const XMPP::Jid &, bool compareRes, bool removeEvents); void printContent() const; void clear(); void clear(const XMPP::Jid &, bool compareRes=true); + typedef QPair PsiEventId; + QList eventsFor(const XMPP::Jid& jid, bool compareRes=true); QDomElement toXml(QDomDocument *) const; // these work with pointers, to save inclusion of qdom.h, which is pretty large bool fromXml(const QDomElement *); @@ -345,8 +359,10 @@ class EventQueue : public QObject void queueChanged(); private: - class Private; - Private *d; + QList list_; + PsiCon* psi_; + PsiAccount* account_; + bool enabled_; }; diff --git a/src/psiselfcontact.cpp b/src/psiselfcontact.cpp new file mode 100755 index 000000000..9ecef3abd --- /dev/null +++ b/src/psiselfcontact.cpp @@ -0,0 +1,73 @@ +/* + * psiselfcontact.cpp - PsiContact that represents 'self' of an account + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "psiselfcontact.h" + +#include "psicontactlist.h" +#include "userlist.h" +#include "psicontactmenu.h" +#include "psiaccount.h" +#include "psicon.h" + +PsiSelfContact::PsiSelfContact(const UserListItem& u, PsiAccount* parent) + : PsiContact(u, parent) +{ +} + +void PsiSelfContact::update(const UserListItem& u) +{ + // if (u.userResourceList().count() != userListItem().userResourceList().count()) + // updateParent(); + + PsiContact::update(u); +} + +/** + * Self contact should be visible either if 'Show Self' option is enabled, or + * user is currently logged in by more than one account. + */ +bool PsiSelfContact::shouldBeVisible() const +{ +#ifdef YAPSI + return false; +#else + return PsiContact::shouldBeVisible() || account()->psi()->contactList()->showSelf() || userListItem().userResourceList().count() > 1; +#endif +} + +ContactListItemMenu* PsiSelfContact::contextMenu() +{ + // PsiContactMenu* menu = new PsiContactMenu(this); + // QStringList toDelete; + // toDelete << "act_rename" << "act_remove" << "act_group" << "act_authorization"; + // menu->removeActions(toDelete); + // return menu; + return 0; +} + +bool PsiSelfContact::isEditable() const +{ + return false; +} + +bool PsiSelfContact::isSelf() const +{ + return true; +} diff --git a/src/psiselfcontact.h b/src/psiselfcontact.h new file mode 100755 index 000000000..763f557c2 --- /dev/null +++ b/src/psiselfcontact.h @@ -0,0 +1,43 @@ +/* + * psiselfcontact.h - PsiContact that represents 'self' of an account + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef PSISELFCONTACT_H +#define PSISELFCONTACT_H + +#include "psicontact.h" + +class PsiSelfContact : public PsiContact +{ +public: + PsiSelfContact(const UserListItem& u, PsiAccount* parent); + + void update(const UserListItem& u); + + // reimplemented + virtual ContactListItemMenu* contextMenu(); + virtual bool isEditable() const; + virtual bool isSelf() const; + +protected: + // reimplemented + virtual bool shouldBeVisible() const; +}; + +#endif diff --git a/src/removeconfirmationmessagebox.cpp b/src/removeconfirmationmessagebox.cpp new file mode 100644 index 000000000..e2071236b --- /dev/null +++ b/src/removeconfirmationmessagebox.cpp @@ -0,0 +1,319 @@ +/* + * removeconfirmationmessagebox.cpp - generic confirmation of destructive action + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "removeconfirmationmessagebox.h" + +#include +#include +#include +#include + +#ifdef YAPSI +#include "yastyle.h" +#endif + +#include "applicationinfo.h" + +#ifdef YAPSI_ACTIVEX_SERVER +#include "yaonline.h" +#include "yapreferences.h" +#endif + +//---------------------------------------------------------------------------- +// RemoveConfirmationMessageBoxManager +//---------------------------------------------------------------------------- + +RemoveConfirmationMessageBoxManager* RemoveConfirmationMessageBoxManager::instance_ = 0; +int RemoveConfirmationMessageBoxManager::onlineId_ = 0; + +RemoveConfirmationMessageBoxManager* RemoveConfirmationMessageBoxManager::instance() +{ + if (!instance_) { + instance_ = new RemoveConfirmationMessageBoxManager(); + } + return instance_; +} + +void RemoveConfirmationMessageBoxManager::processData(const QString& id, const QList callbacks, const QString& title, const QString& informativeText, QWidget* parent, const QStringList& actionNames, QMessageBox::Icon icon) +{ + Data data; + data.onlineId = ++onlineId_; + data.id = id; + data.callbacks = callbacks; + data.title = title; + data.informativeText = informativeText; + data.buttons = actionNames; + data.parent = parent; + data.icon = icon; + + // FIXME: duplicates mustn't be allowed + foreach(Data d, data_) { + Q_ASSERT(d.id != id); + if (d.id == id) + return; + } + + data_ << data; +#ifndef YAPSI_ACTIVEX_SERVER + QTimer::singleShot(0, this, SLOT(update())); +#else + QString parentString = "0"; + if (parent) { + parentString = QString::number((int)parent->window()->winId()); + + if (dynamic_cast(parent->window())) { + parentString = "settings"; + } + } + + YaOnlineHelper::instance()->messageBox(parentString, + QString::number(data.onlineId), + RemoveConfirmationMessageBox::tr("Ya.Online"), + RemoveConfirmationMessageBox::processInformativeText(data.informativeText), + data.buttons, + data.icon); +#endif +} + +void RemoveConfirmationMessageBoxManager::showInformation(const QString& id, const QString& title, const QString& informativeText, QWidget* parent) +{ + QStringList buttons; + buttons << RemoveConfirmationMessageBox::tr("OK"); + + QList callbacks; + + processData(id, callbacks, title, informativeText, parent, buttons, QMessageBox::Information); +} + +void RemoveConfirmationMessageBoxManager::removeConfirmation(const QString& id, QObject* obj, const char* slot, const QString& title, const QString& informativeText, QWidget* parent, const QString& destructiveActionName) +{ + QStringList buttons; + if (!destructiveActionName.isEmpty()) + buttons << destructiveActionName; + else + buttons << RemoveConfirmationMessageBox::tr("Delete"); + buttons << RemoveConfirmationMessageBox::tr("Cancel"); + + QList callbacks; + callbacks << DataCallback(obj, slot); + + processData(id, callbacks, title, informativeText, parent, buttons, QMessageBox::Warning); +} + +void RemoveConfirmationMessageBoxManager::removeConfirmation(const QString& id, QObject* obj1, const char* action1slot, QObject* obj2, const char* action2slot, const QString& title, const QString& informativeText, QWidget* parent, const QString& action1name, const QString& action2name) +{ + QStringList buttons; + buttons << action1name; + buttons << action2name; + buttons << RemoveConfirmationMessageBox::tr("Cancel"); + + QList callbacks; + callbacks << DataCallback(obj1, action1slot); + callbacks << DataCallback(obj2, action2slot); + + processData(id, callbacks, title, informativeText, parent, buttons, QMessageBox::Warning); +} + +void RemoveConfirmationMessageBoxManager::update() +{ +#ifndef YAPSI_ACTIVEX_SERVER + while (!data_.isEmpty()) { + Data data = data_.takeFirst(); + + Q_ASSERT(data.buttons.count() >= 1 && data.buttons.count() <= 3); + RemoveConfirmationMessageBox msgBox(data.title, data.informativeText, data.parent); + msgBox.setIcon(data.icon); + + QStringList buttons = data.buttons; + if (data.icon == QMessageBox::Warning) { + buttons.takeLast(); // Cancel + Q_ASSERT(!buttons.isEmpty()); + msgBox.setDestructiveActionName(buttons.takeFirst()); + if (!buttons.isEmpty()) { + msgBox.setComplimentaryActionName(buttons.takeFirst()); + } + } + else { + msgBox.setInfoActionName(buttons.takeFirst()); + } + + msgBox.doExec(); + + QList callbackData; + for (int i = 0; i < data.callbacks.count(); ++i) { + if (i == 0) + callbackData << msgBox.removeAction(); + else if (i == 1) + callbackData << msgBox.complimentaryAction(); + else + callbackData << false; + } + + for (int i = 0; i < data.callbacks.count(); ++i) { + QMetaObject::invokeMethod(data.callbacks[i].obj, data.callbacks[i].slot, Qt::DirectConnection, + QGenericReturnArgument(), + Q_ARG(QString, data.id), + Q_ARG(bool, callbackData[i])); + } + } +#endif +} + +#ifdef YAPSI_ACTIVEX_SERVER +void RemoveConfirmationMessageBoxManager::onlineCallback(const QString& id, int button) +{ + if (id.isEmpty()) + return; + int intId = id.toInt(); + Q_ASSERT(intId > 0); + if (intId <= 0) + return; + + for (int i = 0; i < data_.count(); ++i) { + if (data_[i].onlineId == intId) { + Data data = data_[i]; + + for (int j = 0; j < data.callbacks.count(); ++j) { + QMetaObject::invokeMethod(data.callbacks[j].obj, data.callbacks[j].slot, Qt::DirectConnection, + QGenericReturnArgument(), + Q_ARG(QString, data.id), + Q_ARG(bool, (button - 1) == j)); + } + data_.removeAt(i); + break; + } + } +} +#endif + +RemoveConfirmationMessageBoxManager::RemoveConfirmationMessageBoxManager() + : QObject(QCoreApplication::instance()) +{ +#ifdef YAPSI_ACTIVEX_SERVER + connect(YaOnlineHelper::instance(), SIGNAL(messageBoxClosed(const QString&, int)), this, SLOT(onlineCallback(const QString&, int))); +#endif +} + +RemoveConfirmationMessageBoxManager::~RemoveConfirmationMessageBoxManager() +{ +} + +//---------------------------------------------------------------------------- +// RemoveConfirmationMessageBox +//---------------------------------------------------------------------------- +RemoveConfirmationMessageBox::RemoveConfirmationMessageBox(const QString& title, const QString& informativeText, QWidget* parent) + : QMessageBox() + , removeButton_(0) + , complimentaryButton_(0) + , cancelButton_(0) + , infoButton_(0) +{ +#ifdef YAPSI + setStyle(YaStyle::defaultStyle()); + + setWindowTitle(tr("Ya.Online")); +#else + setWindowTitle(ApplicationInfo::name()); +#endif + + setText(title); + setInformativeText(informativeText); + + setIcon(QMessageBox::Warning); + int iconSize = style()->pixelMetric(QStyle::PM_MessageBoxIconSize); + QIcon tmpIcon= style()->standardIcon(QStyle::SP_MessageBoxWarning); + if (!tmpIcon.isNull()) + setIconPixmap(tmpIcon.pixmap(iconSize, iconSize)); + + // doesn't work with borderless top-level windows on Mac OS X + // QWidget* window = parent->window(); + // msgBox.setParent(window); + // msgBox.setWindowFlags(Qt::Sheet); + Q_UNUSED(parent); +} + +void RemoveConfirmationMessageBox::setDestructiveActionName(const QString& destructiveAction) +{ + Q_ASSERT(!removeButton_); + Q_ASSERT(!cancelButton_); + Q_ASSERT(!infoButton_); + removeButton_ = addButton(destructiveAction, QMessageBox::AcceptRole /*QMessageBox::DestructiveRole*/); + cancelButton_ = addButton(QMessageBox::Cancel); + setDefaultButton(removeButton_); +} + +void RemoveConfirmationMessageBox::setComplimentaryActionName(const QString& complimentaryAction) +{ + Q_ASSERT(removeButton_); + Q_ASSERT(cancelButton_); + Q_ASSERT(!infoButton_); + Q_ASSERT(!complimentaryButton_); + complimentaryButton_ = addButton(complimentaryAction, QMessageBox::AcceptRole); +} + +void RemoveConfirmationMessageBox::setInfoActionName(const QString& infoAction) +{ + Q_ASSERT(!removeButton_); + Q_ASSERT(!cancelButton_); + Q_ASSERT(!infoButton_); + infoButton_ = addButton(infoAction, QMessageBox::AcceptRole); + setDefaultButton(infoButton_); +} + +QString RemoveConfirmationMessageBox::processInformativeText(const QString& informativeText) +{ + QString text = informativeText; + text.replace("
", "\n"); + QRegExp rx("<.+>"); + rx.setMinimal(true); + text.replace(rx, ""); + return text; +} + +void RemoveConfirmationMessageBox::doExec() +{ + if (!removeButton_ && !infoButton_) { + setDestructiveActionName(tr("Delete")); + } + + setText(processInformativeText(informativeText())); + setInformativeText(QString()); + + Q_ASSERT((removeButton_ && cancelButton_) || infoButton_); +#ifdef YAPSI + YaStyle::makeMeNativeLooking(this); +#endif + exec(); +} + +bool RemoveConfirmationMessageBox::removeAction() const +{ + return clickedButton() == removeButton_; +} + +bool RemoveConfirmationMessageBox::complimentaryAction() const +{ + return clickedButton() == complimentaryButton_; +} + +bool RemoveConfirmationMessageBox::infoAction() const +{ + return clickedButton() == infoButton_; +} diff --git a/src/removeconfirmationmessagebox.h b/src/removeconfirmationmessagebox.h new file mode 100644 index 000000000..19a1d6b97 --- /dev/null +++ b/src/removeconfirmationmessagebox.h @@ -0,0 +1,125 @@ +/* + * removeconfirmationmessagebox.h - generic confirmation of destructive action + * Copyright (C) 2008-2010 Yandex LLC (Michail Pishchagin) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef REMOVECONFIRMATIONMESSAGEBOX_H + +#include +#include + +class QPushButton; + +class RemoveConfirmationMessageBoxManager : public QObject +{ + Q_OBJECT +public: + static RemoveConfirmationMessageBoxManager* instance(); + + void showInformation(const QString& id, + const QString& title, const QString& informativeText, + QWidget* parent); + + // slots must accept (QString id, bool confirmed) + void removeConfirmation(const QString& id, QObject* obj, const char* slot, + const QString& title, const QString& informativeText, + QWidget* parent, const QString& destructiveActionName = QString()); + + // slots must accept (QString id, bool confirmed) + void removeConfirmation(const QString& id, + QObject* obj1, const char* action1slot, + QObject* obj2, const char* action2slot, + const QString& title, const QString& informativeText, + QWidget* parent, + const QString& action1name, + const QString& action2name); + +private slots: +#ifdef YAPSI_ACTIVEX_SERVER + void onlineCallback(const QString& id, int button); +#endif + void update(); + +private: + RemoveConfirmationMessageBoxManager(); + ~RemoveConfirmationMessageBoxManager(); + + struct DataCallback { + DataCallback(QObject* _obj, const char* _slot) + : obj(_obj), slot(_slot) + { + Q_ASSERT(!obj.isNull()); + Q_ASSERT(slot); + } + + QPointer obj; + const char* slot; + }; + + struct Data { + QString title; + QString informativeText; + QStringList buttons; + QWidget* parent; + QMessageBox::Icon icon; + + int onlineId; + QString id; + QList callbacks; + }; + + void processData(const QString& id, + const QList callbacks, + const QString& title, const QString& informativeText, + QWidget* parent, + const QStringList& actionNames, + QMessageBox::Icon icon); + + static RemoveConfirmationMessageBoxManager* instance_; + QList data_; + static int onlineId_; +}; + +class RemoveConfirmationMessageBox : public QMessageBox +{ + Q_OBJECT +protected: + RemoveConfirmationMessageBox(const QString& title, const QString& informativeText, QWidget* parent); + + void setDestructiveActionName(const QString& destructiveAction); + void setComplimentaryActionName(const QString& complimentaryAction); + void setInfoActionName(const QString& infoAction); + + void doExec(); + + bool removeAction() const; + bool complimentaryAction() const; + bool infoAction() const; + + static QString processInformativeText(const QString& informativeText); + +private: + QPushButton* removeButton_; + QPushButton* complimentaryButton_; + QPushButton* infoButton_; + QPushButton* cancelButton_; + + friend class RemoveConfirmationMessageBoxManager; +}; + +#endif diff --git a/src/resourcemenu.cpp b/src/resourcemenu.cpp index 561524225..177268aa6 100644 --- a/src/resourcemenu.cpp +++ b/src/resourcemenu.cpp @@ -1,6 +1,6 @@ /* * resourcemenu.cpp - helper class for displaying contact's resources - * Copyright (C) 2006 Michail Pishchagin + * Copyright (C) 2006-2010 Michail Pishchagin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -19,33 +19,81 @@ */ #include "resourcemenu.h" + #include "psiiconset.h" #include "userlist.h" #include "xmpp_status.h" +#include "psicontact.h" +#include "psiaccount.h" /** * \class ResourceMenu * Helper class that displays available resources using QMenu. - * Please note that PsiMacStyle references name of ResourceMenu. */ ResourceMenu::ResourceMenu(QWidget *parent) : QMenu(parent) + , activeChatsMode_(false) { - // nothing here +} + +ResourceMenu::ResourceMenu(const QString& title, PsiContact* contact, QWidget* parent) + : QMenu(parent) + , contact_(contact) + , activeChatsMode_(false) +{ + setTitle(title); + + Q_ASSERT(contact); + connect(contact_, SIGNAL(updated()), SLOT(contactUpdated())); + contactUpdated(); } /** * Helper function to add resource to the menu. */ -void ResourceMenu::addResource(const UserResource &r, int id) +void ResourceMenu::addResource(const UserResource &r) { - addResource(r.status().type(), r.name(), id); + addResource(r.status().type(), r.name()); } /** * Helper function to add resource to the menu. */ +void ResourceMenu::addResource(int status, QString name) +{ + QString rname = name; + if(rname.isEmpty()) + rname = tr("[blank]"); + + //rname += " (" + status2txt(status) + ")"; + + QAction* action = new QAction(PsiIconset::instance()->status(status).icon(), rname, this); + addAction(action); + action->setProperty("resource", QVariant(name)); + connect(action, SIGNAL(activated()), SLOT(actionActivated())); +} + +void ResourceMenu::actionActivated() +{ + QAction* action = static_cast(sender()); + emit resourceActivated(action->property("resource").toString()); + + if (contact_) { + XMPP::Jid jid(contact_->jid()); + jid = jid.withResource(action->property("resource").toString()); + emit resourceActivated(contact_, jid); + } +} + +#ifndef NEWCONTACTLIST +// FIXME: Deprecate this function and move to signal-based calls +void ResourceMenu::addResource(const UserResource &r, int id) +{ + addResource(r.status().type(), r.name(), id); +} + +// FIXME: Deprecate this function and move to signal-based calls void ResourceMenu::addResource(int status, QString name, int id) { QString rname = name; @@ -56,3 +104,41 @@ void ResourceMenu::addResource(int status, QString name, int id) insertItem(PsiIconset::instance()->status(status).icon(), rname, id); } +#endif + +void ResourceMenu::contactUpdated() +{ + if (!contact_) + return; + if (isVisible()) + return; + clear(); + + if (!activeChatsMode_) { + foreach(const UserResource& resource, contact_->userResourceList()) + addResource(resource); + } + else { + foreach(QString resourceName, contact_->account()->hiddenChats(contact_->jid())) { + XMPP::Status::Type status; + const UserResourceList &rl = contact_->userResourceList(); + UserResourceList::ConstIterator uit = rl.find(resourceName); + if (uit != rl.end() || (uit = rl.priority()) != rl.end()) + status = makeSTATUS((*uit).status()); + else + status = XMPP::Status::Offline; + addResource(status, resourceName); + } + } +} + +bool ResourceMenu::activeChatsMode() const +{ + return activeChatsMode_; +} + +void ResourceMenu::setActiveChatsMode(bool activeChatsMode) +{ + activeChatsMode_ = activeChatsMode; + contactUpdated(); +} diff --git a/src/resourcemenu.h b/src/resourcemenu.h index 95f8ea23a..1d9a58496 100644 --- a/src/resourcemenu.h +++ b/src/resourcemenu.h @@ -1,6 +1,6 @@ /* * resourcemenu.h - helper class for displaying contact's resources - * Copyright (C) 2006 Michail Pishchagin + * Copyright (C) 2006-2010 Michail Pishchagin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -22,17 +22,42 @@ #define RESOURCEMENU_H #include +#include class UserResource; +class PsiContact; + +#include "xmpp_jid.h" class ResourceMenu : public QMenu { Q_OBJECT public: ResourceMenu(QWidget *parent); + ResourceMenu(const QString& title, PsiContact* contact, QWidget* parent); + + bool activeChatsMode() const; + void setActiveChatsMode(bool activeChatsMode); + + void addResource(const UserResource &r); + void addResource(int status, QString name); +#ifndef NEWCONTACTLIST void addResource(const UserResource &r, int id); void addResource(int status, QString name, int id); +#endif + +signals: + void resourceActivated(PsiContact* contact, const XMPP::Jid& jid); + void resourceActivated(QString resource); + +private slots: + void actionActivated(); + void contactUpdated(); + +private: + QPointer contact_; + bool activeChatsMode_; }; -#endif /* RESOURCEMENU_H */ +#endif diff --git a/src/src.pri b/src/src.pri index 350acff3e..2461088a0 100644 --- a/src/src.pri +++ b/src/src.pri @@ -130,8 +130,6 @@ HEADERS += \ $$PWD/contactview.h \ $$PWD/psiiconset.h \ $$PWD/applicationinfo.h \ - $$PWD/pgpkeydlg.h \ - $$PWD/pgputil.h \ $$PWD/pgptransaction.h \ $$PWD/userlist.h \ $$PWD/mainwin.h \ @@ -156,10 +154,8 @@ HEADERS += \ $$PWD/chatsplitter.h \ $$PWD/chateditproxy.h \ $$PWD/adduserdlg.h \ - $$PWD/groupchatdlg.h \ $$PWD/minicmd.h \ $$PWD/mcmdmanager.h \ - $$PWD/gcuserview.h \ $$PWD/infodlg.h \ $$PWD/translationmanager.h \ $$PWD/eventdb.h \ @@ -176,7 +172,6 @@ HEADERS += \ $$PWD/alertable.h \ $$PWD/psipopup.h \ $$PWD/psiapplication.h \ - $$PWD/filetransdlg.h \ $$PWD/avatars.h \ $$PWD/actionlist.h \ $$PWD/serverinfomanager.h \ @@ -185,7 +180,6 @@ HEADERS += \ $$PWD/statuspreset.h \ $$PWD/lastactivitytask.h \ $$PWD/mucmanager.h \ - $$PWD/mucjoindlg.h \ $$PWD/mucconfigdlg.h \ $$PWD/mucaffiliationsmodel.h \ $$PWD/mucaffiliationsproxymodel.h \ @@ -258,8 +252,6 @@ SOURCES += \ $$PWD/contactview.cpp \ $$PWD/psiiconset.cpp \ $$PWD/applicationinfo.cpp \ - $$PWD/pgpkeydlg.cpp \ - $$PWD/pgputil.cpp \ $$PWD/pgptransaction.cpp \ $$PWD/serverinfomanager.cpp \ $$PWD/userlist.cpp \ @@ -286,10 +278,8 @@ SOURCES += \ $$PWD/chateditproxy.cpp \ $$PWD/tipdlg.cpp \ $$PWD/adduserdlg.cpp \ - $$PWD/groupchatdlg.cpp \ $$PWD/mcmdmanager.cpp \ $$PWD/mcmdsimplesite.cpp \ - $$PWD/gcuserview.cpp \ $$PWD/infodlg.cpp \ $$PWD/translationmanager.cpp \ $$PWD/eventdb.cpp \ @@ -304,7 +294,6 @@ SOURCES += \ $$PWD/alertable.cpp \ $$PWD/psipopup.cpp \ $$PWD/psiapplication.cpp \ - $$PWD/filetransdlg.cpp \ $$PWD/avatars.cpp \ $$PWD/actionlist.cpp \ $$PWD/psiactionlist.cpp \ @@ -312,7 +301,6 @@ SOURCES += \ $$PWD/lastactivitytask.cpp \ $$PWD/statuspreset.cpp \ $$PWD/mucmanager.cpp \ - $$PWD/mucjoindlg.cpp \ $$PWD/mucconfigdlg.cpp \ $$PWD/mucaffiliationsmodel.cpp \ $$PWD/mucaffiliationsproxymodel.cpp \ @@ -351,6 +339,39 @@ SOURCES += \ $$PWD/bookmarkmanagedlg.cpp \ $$PWD/vcardphotodlg.cpp +CONFIG += filetransfer +filetransfer { + DEFINES += FILETRANSFER + + HEADERS += \ + $$PWD/filetransdlg.h + + SOURCES += \ + $$PWD/filetransdlg.cpp + + INTERFACES += \ + $$PWD/filetrans.ui +} + +CONFIG += groupchat +groupchat { + DEFINES += GROUPCHAT + + HEADERS += \ + $$PWD/groupchatdlg.h \ + $$PWD/gcuserview.h \ + $$PWD/mucjoindlg.h + + SOURCES += \ + $$PWD/groupchatdlg.cpp \ + $$PWD/gcuserview.cpp \ + $$PWD/mucjoindlg.cpp + + INTERFACES += \ + $$PWD/groupchatdlg.ui \ + $$PWD/mucjoin.ui +} + whiteboarding { # Whiteboarding support. Still experimental. DEFINES += WHITEBOARDING @@ -400,13 +421,118 @@ mac { include($$PWD/CocoaUtilities/CocoaUtilities.pri) } +CONFIG += newcontactlist +newcontactlist { + DEFINES += NEWCONTACTLIST + DEFINES += USE_GENERAL_CONTACT_GROUP + # DEFINES += CONTACTLIST_NESTED_GROUPS + HEADERS += \ + $$PWD/contactlistview.h \ + $$PWD/contactlistdragview.h \ + $$PWD/hoverabletreeview.h \ + $$PWD/contactlistmodel.h \ + $$PWD/contactlistmodelselection.h \ + $$PWD/contactlistdragmodel.h \ + $$PWD/contactlistviewdelegate.h \ + $$PWD/contactlistmodelupdater.h \ + $$PWD/contactlistproxymodel.h \ + $$PWD/psicontact.h \ + $$PWD/psiselfcontact.h \ + $$PWD/psicontactmenu.h \ + $$PWD/contactlistgroupstate.h \ + $$PWD/contactlistgroupcache.h \ + $$PWD/contactlistgroup.h \ + $$PWD/contactlistnestedgroup.h \ + $$PWD/contactlistaccountgroup.h \ + $$PWD/contactlistspecialgroup.h \ + $$PWD/contactlistgroupmenu.h \ + $$PWD/contactlistaccountmenu.h \ + $$PWD/contactlistitem.h \ + $$PWD/contactlistitemmenu.h \ + $$PWD/contactlistutil.h \ + $$PWD/contactlistitemproxy.h \ + $$PWD/contactupdatesmanager.h \ + $$PWD/statusmenu.h + + SOURCES += \ + $$PWD/contactlistview.cpp \ + $$PWD/contactlistdragview.cpp \ + $$PWD/hoverabletreeview.cpp \ + $$PWD/contactlistmodel.cpp \ + $$PWD/contactlistmodelselection.cpp \ + $$PWD/contactlistdragmodel.cpp \ + $$PWD/contactlistviewdelegate.cpp \ + $$PWD/contactlistmodelupdater.cpp \ + $$PWD/contactlistproxymodel.cpp \ + $$PWD/psicontact.cpp \ + $$PWD/psiselfcontact.cpp \ + $$PWD/psicontactmenu.cpp \ + $$PWD/contactlistgroupstate.cpp \ + $$PWD/contactlistgroupcache.cpp \ + $$PWD/contactlistgroup.cpp \ + $$PWD/contactlistnestedgroup.cpp \ + $$PWD/contactlistaccountgroup.cpp \ + $$PWD/contactlistspecialgroup.cpp \ + $$PWD/contactlistgroupmenu.cpp \ + $$PWD/contactlistaccountmenu.cpp \ + $$PWD/contactlistitem.cpp \ + $$PWD/contactlistitemmenu.cpp \ + $$PWD/contactlistutil.cpp \ + $$PWD/contactlistitemproxy.cpp \ + $$PWD/contactupdatesmanager.cpp \ + $$PWD/statusmenu.cpp + + !yapsi { + HEADERS += \ + $$PWD/psicontactlistview.h \ + $$PWD/psicontactlistviewdelegate.h \ + $$PWD/psicontactlistmodel.h + + SOURCES += \ + $$PWD/psicontactlistview.cpp \ + $$PWD/psicontactlistviewdelegate.cpp \ + $$PWD/psicontactlistmodel.cpp + } +} +!newcontactlist { + HEADERS += \ + $$PWD/legacypsiaccount.h + + SOURCES += \ + $$PWD/legacypsiaccount.cpp +} + +CONFIG += pgputil +pgputil { + DEFINES += HAVE_PGPUTIL + HEADERS += \ + $$PWD/pgputil.h \ + $$PWD/pgpkeydlg.h + + SOURCES += \ + $$PWD/pgputil.cpp \ + $$PWD/pgpkeydlg.cpp + + INTERFACES += \ + $$PWD/pgpkey.ui +} + +HEADERS += \ + $$PWD/removeconfirmationmessagebox.h \ + $$PWD/globaleventqueue.h \ + $$PWD/dummystream.h + +SOURCES += \ + $$PWD/removeconfirmationmessagebox.cpp \ + $$PWD/globaleventqueue.cpp \ + $$PWD/dummystream.cpp + # Qt Designer interfaces INTERFACES += \ $$PWD/profileopen.ui \ $$PWD/profilemanage.ui \ $$PWD/profilenew.ui \ $$PWD/proxy.ui \ - $$PWD/pgpkey.ui \ $$PWD/accountmanage.ui \ $$PWD/accountadd.ui \ $$PWD/accountreg.ui \ @@ -415,7 +541,6 @@ INTERFACES += \ $$PWD/changepw.ui \ $$PWD/addurl.ui \ $$PWD/adduser.ui \ - $$PWD/mucjoin.ui \ $$PWD/info.ui \ $$PWD/search.ui \ $$PWD/about.ui \ @@ -426,11 +551,9 @@ INTERFACES += \ $$PWD/xmlconsole.ui \ $$PWD/disco.ui \ $$PWD/tip.ui \ - $$PWD/filetrans.ui \ $$PWD/mood.ui \ $$PWD/voicecall.ui \ $$PWD/chatdlg.ui \ - $$PWD/groupchatdlg.ui \ $$PWD/bookmarkmanage.ui \ $$PWD/ahcommanddlg.ui \ $$PWD/ahcformdlg.ui diff --git a/src/statusmenu.cpp b/src/statusmenu.cpp new file mode 100755 index 000000000..5952be207 --- /dev/null +++ b/src/statusmenu.cpp @@ -0,0 +1,87 @@ +/* + * statusmenu.cpp - helper class that displays available statuses using QMenu + * Copyright (C) 2008-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include "statusmenu.h" + +#include "psioptions.h" +#include "psiiconset.h" +#include "psiaccount.h" +#include "xmpp_status.h" +#include "common.h" + +/** + * \class StatusMenu + * Helper class that displays available statuses using QMenu. + */ + +StatusMenu::StatusMenu(QWidget* parent) + : QMenu(parent), currentStatus_(XMPP::Status::Offline) +{ + addStatus(XMPP::Status::Online); + if (PsiOptions::instance()->getOption("options.ui.menu.status.chat").toBool()) + addStatus(XMPP::Status::FFC); + addSeparator(); + addStatus(XMPP::Status::Away); + if (PsiOptions::instance()->getOption("options.ui.menu.status.xa").toBool()) + addStatus(XMPP::Status::XA); + addStatus(XMPP::Status::DND); + if (PsiOptions::instance()->getOption("options.ui.menu.status.invisible").toBool()) { + addSeparator(); + addStatus(XMPP::Status::Invisible); + } +#ifndef YAPSI_ACTIVEX_SERVER + addSeparator(); + addStatus(XMPP::Status::Offline); +#endif +} + +void StatusMenu::setStatus(XMPP::Status::Type status) +{ + if (currentStatus_ == status) + return; + + currentStatus_ = status; + foreach(QAction* action, actions()) + action->setChecked(actionStatus(action) == status); +} + +XMPP::Status::Type StatusMenu::actionStatus(const QAction* action) const +{ + Q_ASSERT(action); + return static_cast(action->property("type").toInt()); +} + +void StatusMenu::addStatus(XMPP::Status::Type type) +{ + QAction* action = new QAction(status2txt(type), this); + action->setCheckable(true); + action->setChecked(currentStatus_ == type); + action->setIcon(PsiIconset::instance()->status(type).icon()); + action->setProperty("type", QVariant(type)); + connect(action, SIGNAL(activated()), SLOT(actionActivated())); + addAction(action); +} + +void StatusMenu::actionActivated() +{ + QAction* action = static_cast(sender()); + setStatus(actionStatus(action)); + emit statusChanged(currentStatus_); +} diff --git a/src/statusmenu.h b/src/statusmenu.h new file mode 100755 index 000000000..e2a76d998 --- /dev/null +++ b/src/statusmenu.h @@ -0,0 +1,51 @@ +/* + * statusmenu.h - helper class that displays available statuses using QMenu + * Copyright (C) 2008-2010 Michail Pishchagin + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#ifndef STATUSMENU_H +#define STATUSMENU_H + +#include + +#include "xmpp_status.h" + +class StatusMenu : public QMenu +{ + Q_OBJECT +public: + StatusMenu(QWidget* parent); + + void setStatus(XMPP::Status::Type); + +signals: + void statusChanged(XMPP::Status::Type); + +private: + void addStatus(XMPP::Status::Type type); + +private slots: + void actionActivated(); + +private: + XMPP::Status::Type currentStatus_; + + XMPP::Status::Type actionStatus(const QAction* action) const; +}; + +#endif diff --git a/src/tabs/tabbablewidget.cpp b/src/tabs/tabbablewidget.cpp index 8432eb8ae..1d47f40c6 100644 --- a/src/tabs/tabbablewidget.cpp +++ b/src/tabs/tabbablewidget.cpp @@ -85,12 +85,14 @@ void TabbableWidget::ensureTabbedCorrectly() } } -void TabbableWidget::bringToFront() +void TabbableWidget::bringToFront(bool raiseWindow) { if (isTabbed()) { getManagingTabDlg()->selectTab(this); } - ::bringToFront(this); + if (raiseWindow) { + ::bringToFront(this); + } } TabbableWidget::~TabbableWidget() diff --git a/src/tabs/tabbablewidget.h b/src/tabs/tabbablewidget.h index b3943573d..41f826884 100644 --- a/src/tabs/tabbablewidget.h +++ b/src/tabs/tabbablewidget.h @@ -41,6 +41,8 @@ class TabbableWidget : public AdvancedWidget TabbableWidget(const Jid &, PsiAccount *, TabManager *tabManager); ~TabbableWidget(); + PsiAccount* account() const; + virtual Jid jid() const; virtual const QString & getDisplayName(); @@ -71,12 +73,11 @@ class TabbableWidget : public AdvancedWidget public slots: virtual void deactivated(); virtual void activated(); - void bringToFront(); + void bringToFront(bool raiseWindow = true); virtual void ensureTabbedCorrectly(); protected: virtual void setJid(const Jid&); - PsiAccount* account() const; // reimplemented void changeEvent(QEvent* e); diff --git a/src/tabs/tabdlg.cpp b/src/tabs/tabdlg.cpp index d23df8d26..b15ed260c 100644 --- a/src/tabs/tabdlg.cpp +++ b/src/tabs/tabdlg.cpp @@ -520,10 +520,10 @@ TabbableWidget *TabDlg::getTab(int i) const return static_cast(tabWidget_->page(i)); } -TabbableWidget* TabDlg::getTabPointer(QString fullJid) +TabbableWidget* TabDlg::getTabPointer(PsiAccount* account, QString fullJid) { foreach(TabbableWidget* tab, tabs_) { - if (tab->jid().full() == fullJid) { + if (tab->jid().full() == fullJid && tab->account() == account) { return tab; } } diff --git a/src/tabs/tabdlg.h b/src/tabs/tabdlg.h index 347e3cc33..1c3bd9a94 100644 --- a/src/tabs/tabdlg.h +++ b/src/tabs/tabdlg.h @@ -75,7 +75,7 @@ class TabDlg : public AdvancedWidget TabbableWidget *getTab(int i) const; void removeTabWithNoChecks(TabbableWidget *tab); - TabbableWidget* getTabPointer(QString fullJid); + TabbableWidget* getTabPointer(PsiAccount* account, QString fullJid); virtual QString desiredCaption() const; QString captionForTab(TabbableWidget* tab) const; diff --git a/src/widgets/iconaction.cpp b/src/widgets/iconaction.cpp index 045f130cf..c6759a647 100644 --- a/src/widgets/iconaction.cpp +++ b/src/widgets/iconaction.cpp @@ -97,6 +97,16 @@ IconAction::IconAction(const QString &statusTip, const QString &icon, const QStr setPsiIcon(icon); } +IconAction::IconAction(const QString &statusTip, const QString &icon, const QString &text, QList accel, QObject *parent, const QString &name, bool checkable) +: QAction(text, parent) +{ + d = new Private(this, parent); + d->init(name, statusTip, accel.first(), checkable); + setShortcuts(accel); + + setPsiIcon(icon); +} + IconAction::IconAction(const QString &statusTip, const QString &text, QKeySequence accel, QObject *parent, const QString &name, bool checkable) : QAction(text, parent) { @@ -104,6 +114,23 @@ IconAction::IconAction(const QString &statusTip, const QString &text, QKeySequen d->init(name, statusTip, accel, checkable); } +IconAction::IconAction(const QString &statusTip, const QString &text, QList accel, QObject *parent, const QString &name, bool checkable) +: QAction(text, parent) +{ + d = new Private(this, parent); + d->init(name, statusTip, accel.first(), checkable); + setShortcuts(accel); +} + +IconAction::IconAction(const QString &text, QObject *parent, const QString &icon) + : QAction(text, parent) +{ + d = new Private(this, parent); + d->init(QString(), QString(), QKeySequence(), false); + + setPsiIcon(icon); +} + IconAction::~IconAction() { // delete the buttons list before our own destruction @@ -154,6 +181,10 @@ void IconAction::setPsiIcon(const QString &name) #ifdef WIDGET_PLUGIN d->iconName = name; #else + if (name.isEmpty()) { + setPsiIcon( 0 ); + return; + } setPsiIcon( IconsetFactory::iconPtr(name) ); #endif } diff --git a/src/widgets/iconaction.h b/src/widgets/iconaction.h index 563bd39f3..9cacc00c3 100644 --- a/src/widgets/iconaction.h +++ b/src/widgets/iconaction.h @@ -37,7 +37,10 @@ class IconAction : public QAction public: IconAction(QObject *parent, const QString &name = QString()); IconAction(const QString &statusTip, const QString &icon, const QString &text, QKeySequence accel, QObject *parent, const QString &name = QString(), bool checkable = FALSE); + IconAction(const QString &statusTip, const QString &icon, const QString &text, QList accel, QObject *parent, const QString &name = QString(), bool checkable = FALSE); IconAction(const QString &statusTip, const QString &text, QKeySequence accel, QObject *parent, const QString &name = QString(), bool checkable = FALSE); + IconAction(const QString &statusTip, const QString &text, QList accel, QObject *parent, const QString &name = QString(), bool checkable = FALSE); + IconAction(const QString &text, QObject *parent, const QString &icon); ~IconAction(); virtual bool addTo(QWidget *);