diff -Naur psi-0.10-orig/ChangeLog psi-0.10/ChangeLog --- psi-0.10-orig/ChangeLog 2005-08-24 13:59:35.000000000 +0000 +++ psi-0.10/ChangeLog 2006-01-14 00:43:21.000000000 +0000 @@ -286,6 +286,14 @@ Dynamic 'avatars', 'vcard' and 'history' directory recreation, and removal of unused 'info' dir. +2004-09-01, Nolan Eakins + Hunted down a bug in XDataWidget's xml output for booleans + Changed Iris's xmlToString to clip ALL unneeded whitespace + +2004-08-29, Nolan Eakins + Moved the set button to be next to the cancel button in SetStatusDlg + + 2004-08-18, Michail Pishchagin TrayIcon correction by William Waghorn, so it should work correctly on Gnome from now on. But there'll be dependency on PsiApplication :-/ diff -Naur psi-0.10-orig/iconsets/system/default/bookmark.png psi-0.10/iconsets/system/default/bookmark.png --- psi-0.10-orig/iconsets/system/default/bookmark.png 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iconsets/system/default/bookmark.png 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,4 @@ +PNG + + IHDRagAMA7tEXtSoftwareAdobe ImageReadyqe<IDATxڤSKA~c7HDPJzBk/AxO?Pܓ x(zj7PRJuk0&$f;;3~k$ٖ>v}o?S]%0/䌅N K҆X*qƳ2j"K3<bAZ!Y$@ i1e'hӟY)EvF}pnͧmx A4_=?7c{7IpS!-CqI*RV*~KI+Ol"+}B4?f +7*$%@1xĒj;h 5ϴIENDB` \ Brak znaku nowej linii na kocu pliku diff -Naur psi-0.10-orig/iconsets/system/default/icondef.xml psi-0.10/iconsets/system/default/icondef.xml --- psi-0.10-orig/iconsets/system/default/icondef.xml 2006-01-07 21:19:01.000000000 +0000 +++ psi-0.10/iconsets/system/default/icondef.xml 2006-01-14 00:43:21.000000000 +0000 @@ -315,7 +315,62 @@ filemanager.png - + + + psi/muc_destroy_room + muc_destroy_room.png + + + + psi/muc_affiliation_editor + muc_affil_editor.png + + + + psi/muc_affiliation_owner + muc_affil_editor.png + + + + psi/muc_affiliation_admin + muc_affil_editor.png + + + + psi/muc_affiliation_member + muc_affil_editor.png + + + + psi/muc_affiliation_none + muc_affil_editor.png + + + + psi/muc_affiliation_outcast + muc_affil_editor.png + + + + psi/muc_configure_room + options.png + + + + psi/muc_kick + cancel.png + + + + psi/muc_ban + edittrash.png + + + + psi/bookmark + bookmark.png + + psi/roster diff -Naur psi-0.10-orig/iconsets/system/default/icondef.xml.orig psi-0.10/iconsets/system/default/icondef.xml.orig --- psi-0.10-orig/iconsets/system/default/icondef.xml.orig 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iconsets/system/default/icondef.xml.orig 2006-01-07 21:19:01.000000000 +0000 @@ -0,0 +1,430 @@ + + + + Crystal - System (default) + 1.0 + Crystal System Iconset + 2005-11-23 + http://psi.affinix.com + Jason Kim (Logo and misc. icons) + Everaldo Coelho (Crystal icons) + + + + psi/appearance + appearance.png + + + + psi/account + account.png + + + + psi/addContact + add.png + + + + psi/arrowUp + arrow_up.png + + + + psi/arrowDown + arrow_down.png + + + + psi/arrowLeft + arrow_left.png + + + + psi/arrowRight + arrow_right.png + + + + psi/profile + changeacc.png + + + + psi/clearChat + chatclear.png + + + + psi/compact + ok.png + + + + psi/disco + disco.png + + + + psi/events + events.png + + + + psi/groupChat + groupchat.png + + + + psi/help + help.png + + + + psi/history + history.png + + + + psi/vCard + vcard.png + + + + psi/jabber + jabber.png + + + + psi/options + options.png + + + + psi/toolbars + configure_toolbars.png + + + + psi/pgp + pgp.png + + + + psi/gpg-yes + key.png + + + + psi/gpg-no + key.png + + + + psi/keyBad + key_bad.png + + + + psi/keyUnknown + key_unknown.png + + + + psi/playSounds + play_sounds.png + + + + psi/psiMain + psimain.png + + + + psi/quit + quit.png + + + + psi/register + register.png + + + + psi/reload + reload.png + + + + psi/stop + stop.png + + + + psi/remove + remove.png + + + + psi/search + search.png + + + + psi/status + status.png + + + + psi/sendMessage + send.png + + + + psi/start-chat + start-chat.png + + + + psi/cryptoYes + ssl_yes.png + + + + psi/cryptoNo + ssl_no.png + + + + psi/time + time.png + + + + psi/www + url.png + + + + psi/xml + xml.png + + + + psi/psi16 + icon_16.png + + + + psi/psi32 + icon_32.png + + + + psi/psi48 + icon_48.png + + + + psi/psiMac + icon_mac.png + + + + psi/psiLogo + psilogo.png + + + + psi/smile + smile.png + + + + psi/email + send.png + + + + psi/ok + ok.png + + + + psi/done + close.png + + + + psi/closetab + closetab.png + + + + psi/cancel + cancel.png + + + + psi/close + close.png + + + + psi/apply + ok.png + + + + psi/info + info.png + + + + psi/tip + tip.png + + + + psi/browse + browse.png + + + + psi/play + play.png + + + + psi/eye + eye_blue.png + + + + psi/upload + upload.png + + + + psi/download + download.png + + + + psi/filemanager + filemanager.png + + + + + + psi/roster + url.png + + + + psi/advanced + advanced.png + + + + psi/show_self + self.png + + + + psi/show_offline + show_offline.png + + + + psi/show_away + show_away.png + + + + psi/show_hidden + show_hidden.png + + + + + diff -Naur psi-0.10-orig/iconsets/system/default/muc_affil_editor.png psi-0.10/iconsets/system/default/muc_affil_editor.png --- psi-0.10-orig/iconsets/system/default/muc_affil_editor.png 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iconsets/system/default/muc_affil_editor.png 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,4 @@ +PNG + + IHDRabKGD pHYs  ~tIME#v>;|IDATx͓ 0E?ơ\{At ʉR`dE,sd +ys#R#tPU!6] dAXXMm36ARZ=j>?2H]+~8_$)7IENDB` \ Brak znaku nowej linii na kocu pliku diff -Naur psi-0.10-orig/iconsets/system/default/muc_destroy_room.png psi-0.10/iconsets/system/default/muc_destroy_room.png --- psi-0.10-orig/iconsets/system/default/muc_destroy_room.png 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iconsets/system/default/muc_destroy_room.png 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,5 @@ +PNG + + IHDRabKGDC pHYs  ~tIME  +%6l\IDATxڵka?w6W%5CHᏡH"8: OH'T,IݺH[BHsLl wͽ-<3}1a岕fSBaq8^__ggsګ'1#9wupc~ Gq/Zֳ@jW#hjvX2 cq1Mu!4{;&o>-ͮ^DhTP,_f2|>O:fbb#ڠ]ҷ4jx^$,Bp?l68R)t:j<{exEu }ԾRB + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 "xmpp.h" +#include "im.h" +#include "xmpp_xmlcommon.h" +#include "bookmarks.h" + +using namespace XMPP; +using namespace XMLHelper; + +Bookmark::Bookmark(const QString &name) + : m_name(name) +{ +} + +Bookmark::~Bookmark() +{ +} + +void Bookmark::setName(const QString &name) +{ + m_name = name; +} + +const QString &Bookmark::name() const +{ + return m_name; +} + +UrlBookmark::UrlBookmark() +{ +} + +UrlBookmark::UrlBookmark(const QString &name, const QUrl &url) + : Bookmark(name), m_url(url) +{ +} + +UrlBookmark::~UrlBookmark() +{ +} + +void UrlBookmark::setUrl(const QUrl &url) +{ + m_url = url; +} + +const QUrl &UrlBookmark::url() const +{ + return m_url; +} + +int UrlBookmark::rtti() const +{ + return RTTI; +} + +QDomElement UrlBookmark::toXml(QDomDocument *doc) const +{ + QDomElement e = doc->createElement("url"); + QString s = name(); + if(!s.isEmpty()) + e.setAttribute("name", s); + s = url(); + if(!s.isEmpty()) + e.setAttribute("url", s); + return e; +} + +bool UrlBookmark::fromXml(const QDomElement &e) +{ + if(e.tagName() == "url") { + setName(e.attribute("name")); + setUrl(QUrl(e.attribute("url"))); + + return true; + } + + return false; +} + + + +GCBookmark::GCBookmark() + : m_autojoin(false) +{ +} + +GCBookmark::GCBookmark(const QString &name, const Jid &jid, + const QString &nick, bool autojoin, + const QString &pass) + : Bookmark(name), m_jid(jid), m_nick(nick), m_password(pass), m_autojoin(autojoin) +{ +} + +GCBookmark::~GCBookmark() +{ +} + +void GCBookmark::setJid(const Jid &jid) +{ + m_jid = jid; +} + +void GCBookmark::setNick(const QString &nick) +{ + m_nick = nick; +} + +void GCBookmark::setAutojoin(bool yes) +{ + m_autojoin = yes; +} + +void GCBookmark::setPassword(const QString &pass) +{ + m_password = pass; +} + +const Jid &GCBookmark::jid() const +{ + return m_jid; +} + +const QString &GCBookmark::nick() const +{ + return m_nick; +} + +bool GCBookmark::autojoin() const +{ + return m_autojoin; +} + +const QString &GCBookmark::password() const +{ + return m_password; +} + +int GCBookmark::rtti() const +{ + return RTTI; +} + +QDomElement GCBookmark::toXml(QDomDocument *doc) const +{ + QDomElement e = doc->createElement("conference"); + QString s = name(); + if(!s.isEmpty()) + e.setAttribute("name", name()); + + setBoolAttribute(e, "autojoin", autojoin()); + + const Jid &j(jid()); + if(j.isValid()) + e.setAttribute("jid", j.bare()); + + s = nick(); + if(!s.isEmpty()) + e.appendChild(textTag(doc, "nick", s)); + + s = password(); + if(!s.isEmpty()) + e.appendChild(textTag(doc, "password", s)); + + return e; +} + +bool GCBookmark::fromXml(const QDomElement &e) +{ + if(e.tagName() == "conference") { + setName(e.attribute("name")); + readBoolAttribute(e, "autojoin", &m_autojoin); + setJid(e.attribute("jid")); + readEntry(e, "nick", &m_nick); + readEntry(e, "password", &m_password); + + return true; + } + + return false; +} + + +BookmarkList::BookmarkList() +{ + //setAutoDelete(true); +} + +BookmarkList::BookmarkList(const BookmarkList &list) + : QPtrList(list) +{ + //setAutoDelete(true); +} + +QPtrList BookmarkList::findBookmarks(const QString &name) +{ + QPtrList ret; + + for(QPtrListIterator it(*this); + it.current(); + ++it) { + Bookmark *mark = *it; + if(mark->name() == name) + ret.append(mark); + } + + return ret; +} + +QPtrList BookmarkList::findUrlBookmarks(const QUrl &url) +{ + QPtrList ret; + + for(QPtrListIterator it(*this); it.current(); ++it) { + UrlBookmark *mark = (UrlBookmark *)(*it); + if(mark->rtti() == UrlBookmark::RTTI && mark->url() == url) + ret.append(mark); + } + + return ret; +} + +QPtrList BookmarkList::findGCBookmarks(const Jid &room) +{ + QPtrList ret; + + for(QPtrListIterator it(*this); it.current(); ++it) { + GCBookmark *mark = (GCBookmark *)(*it); + if(mark->rtti() == GCBookmark::RTTI && mark->jid().compare(room, false)) + ret.append(mark); + } + + return ret; +} + + +class JT_Bookmarks::Private +{ +public: + BookmarkList marks; + Jid jid; + bool setting; +}; + +JT_Bookmarks::JT_Bookmarks(Task *t) + : Task(t) +{ + d = new Private; + d->setting = false; + d->jid = client()->jid().bare(); +} + +JT_Bookmarks::~JT_Bookmarks() +{ + delete d; +} + +void JT_Bookmarks::setBookmarks(const BookmarkList &marks) +{ + d->marks = marks; +} + +BookmarkList &JT_Bookmarks::bookmarks() +{ + return d->marks; +} + +const BookmarkList &JT_Bookmarks::bookmarks() const +{ + return d->marks; +} + +void JT_Bookmarks::get(bool autodelete) +{ + d->setting = false; + go(autodelete); +} + +void JT_Bookmarks::set(bool autodelete) +{ + d->setting = true; + go(autodelete); +} + +class JT_BookmarksMarkFunk +{ +public: + QDomDocument *m_doc; + QDomElement &m_e; + + JT_BookmarksMarkFunk(QDomDocument *d, QDomElement &e) + : m_doc(d), m_e(e) + { + } + + void operator () (Bookmark *i) + { + //qDebug(i->name()); + QDomElement e = i->toXml(m_doc); + m_e.appendChild(e); + } +}; + +void JT_Bookmarks::onGo() +{ + QString action = d->setting ? "set" : "get"; + QDomElement iq = createIQ(doc(), action, d->jid.bare(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", "jabber:iq:private"); + iq.appendChild(query); + + QDomElement storage = doc()->createElement("storage"); + storage.setAttribute("xmlns", "storage:bookmarks"); + query.appendChild(storage); + + if(d->setting) { + //qDebug(QString("Count: %1").arg(d->marks.count())); + std::for_each(d->marks.begin(), + d->marks.end(), + JT_BookmarksMarkFunk(doc(), storage)); + } + + send(iq); +} + +void JT_Bookmarks::goThroughMarks(const QDomElement &e) +{ + d->marks.clear(); + + for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + Bookmark *m = NULL; + if(i.tagName() == "url") { + m = new UrlBookmark; + } + else if(i.tagName() == "conference") { + m = new GCBookmark; + } + + if(m != NULL) { + if(m->fromXml(i)) + d->marks.append(m); + } + } +} + +bool JT_Bookmarks::take(const QDomElement &e) +{ + if(!iqVerify(e, Jid(), id())) + return false; + + if(e.attribute("type") == "result") { + QDomElement query = queryTag(e); + if(query.attribute("xmlns") == "jabber:iq:private") { + bool found = false; + QDomElement e = findSubTag(query, "storage", &found); + if(found && e.attribute("xmlns") == "storage:bookmarks") { + goThroughMarks(e); + } + } + setSuccess(); + } + else { + setError(e); + } + + return true; +} diff -Naur psi-0.10-orig/iris/jabber/bookmarks.h psi-0.10/iris/jabber/bookmarks.h --- psi-0.10-orig/iris/jabber/bookmarks.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/jabber/bookmarks.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,155 @@ +/* + * bookmarks.h - Bookmark storage per JEP-0048 + * Copyright (C) 2005, Nolan Eakins + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 BOOKMARKS_H +#define BOOKMARKS_H + +#include +#include +#include +#include +#include "im.h" + +class QDomDocument; +class QDomElement; + +namespace XMPP +{ + class Bookmark + { + public: + Bookmark(const QString & = QString::null); + virtual ~Bookmark(); + + void setName(const QString &); + const QString &name() const; + + virtual int rtti() const = 0; + virtual QDomElement toXml(QDomDocument *) const = 0; + virtual bool fromXml(const QDomElement &) = 0; + + private: + QString m_name; + }; + + class UrlBookmark: public Bookmark + { + public: + static const int RTTI = 1; + + UrlBookmark(); + UrlBookmark(const QString &, const QUrl &); + virtual ~UrlBookmark(); + + void setUrl(const QUrl &); + const QUrl &url() const; + + int rtti() const; + QDomElement toXml(QDomDocument *) const; + bool fromXml(const QDomElement &); + + private: + QUrl m_url; + }; + + class GCBookmark: public Bookmark + { + public: + static const int RTTI = 2; + + GCBookmark(); + /** Constructs a groupchat bookmark. + * @param name The name of the bookmark + * @param jid The jid of the room + * @param nick The nickname to use in the room + * @param autojoin Whether to automatically join the room or not + * @param password The room's password + */ + GCBookmark(const QString &, const Jid &, + const QString & = QString::null, + bool = false, + const QString & = QString::null); + virtual ~GCBookmark(); + + void setJid(const Jid &); + void setNick(const QString &); + void setAutojoin(bool); + void setPassword(const QString &); + + const Jid &jid() const; + const QString &nick() const; + bool autojoin() const; + const QString &password() const; + + int rtti() const; + QDomElement toXml(QDomDocument *) const; + bool fromXml(const QDomElement &); + + private: + Jid m_jid; + QString m_nick, m_password; + bool m_autojoin; + }; + + class BookmarkList: public QPtrList + { + public: + BookmarkList(); + BookmarkList(const BookmarkList &); + + /** + * Find any bookmarks with a given name. + */ + QPtrList findBookmarks(const QString &); + /** + * Find any bookmarks with a given URL. + */ + QPtrList findUrlBookmarks(const QUrl &); + /** + * Find any groupchat bookmarks by Jid. + */ + QPtrList findGCBookmarks(const Jid &); + }; + + class JT_Bookmarks: public Task + { + Q_OBJECT; + public: + JT_Bookmarks(Task *); + ~JT_Bookmarks(); + + void setBookmarks(const BookmarkList &); + BookmarkList &bookmarks(); + const BookmarkList &bookmarks() const; + + void get(bool autodelete = false); + void set(bool autodelete = false); + + void onGo(); + bool take(const QDomElement &); + + private: + void goThroughMarks(const QDomElement &); + + class Private; + Private *d; + }; +} + +#endif /* BOOKMARKS_H */ diff -Naur psi-0.10-orig/iris/jabber/muc.cpp psi-0.10/iris/jabber/muc.cpp --- psi-0.10-orig/iris/jabber/muc.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/jabber/muc.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,335 @@ +/* + * muc.h - Multi-User Chat + * Copyright (C) 2005, Nolan Eakins + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 "muc.h" + +using namespace XMPP::MUC; + +//////////////////////////////////////////////////////// +// Affiliation +//////////////////////////////////////////////////////// + +// Affiliation helper constants +namespace XMPP +{ + namespace MUC + { + const Affiliation AffilUnknown(-1), AffilOutcast(0), AffilNone(1), + AffilMember(2), AffilAdmin(3), AffilOwner(4); + } +} + +const QString Affiliation::ProtoStrings[] = { + "outcast", + "none", + "member", + "admin", + "owner" +}; + +Affiliation::Affiliation(const QString &proto) + : m_index(-1) +{ + for(int i = 0; i < LastIndex; i++) { + if(ProtoStrings[i] == proto) { + m_index = i; + break; + } + } +} + +Affiliation::Affiliation(int index) + : m_index(index) +{ +} + +Affiliation::Affiliation(const Affiliation &a) + : m_index(a.index()) +{ +} + +Affiliation &Affiliation::operator=(const Affiliation &a) +{ + m_index = a.index(); + return *this; +} + +int Affiliation::index() const +{ + return m_index; +} + +QString Affiliation::toProtoString() const +{ + if(isValid()) + return ProtoStrings[m_index]; + else + return QString::null; +} + +QString Affiliation::toString(bool lower) const +{ + static const QString strings[] = { + QT_TRANSLATE_NOOP("Affiliation", "Banned"), + QT_TRANSLATE_NOOP("Affiliation", "None"), + QT_TRANSLATE_NOOP("Affiliation", "Member"), + QT_TRANSLATE_NOOP("Affiliation", "Admin"), + QT_TRANSLATE_NOOP("Affiliation", "Owner") + }; + + if(isValid()) { + QString str = qApp->translate("Affiliation", strings[m_index]); + if(lower) + return str.lower(); + else + return str; + } + else + return QString::null; +} + +Affiliation &Affiliation::operator++() +{ + m_index++; + return *this; +} + +Affiliation Affiliation::operator++(int) +{ + Affiliation temp = *this; + ++*this; + return temp; +} + +Affiliation &Affiliation::operator--() +{ + m_index--; + return *this; +} + +Affiliation Affiliation::operator--(int) +{ + Affiliation temp = *this; + --*this; + return temp; +} + +bool Affiliation::isValid() const +{ + if(m_index >= 0 && m_index < LastIndex) + return true; + else + return false; +} + +bool Affiliation::isLowest() const +{ + return (m_index <= 0); +} + +bool Affiliation::isHighest() const +{ + return (m_index >= (LastIndex - 1)); +} + +//////////////////////////////////////////////////////// +// Role +//////////////////////////////////////////////////////// + +// Role helper constants +namespace XMPP +{ + namespace MUC + { + const Role RoleUnknown(-1), RoleNone(0), RoleVisitor(1), + RoleParticipant(2), RoleModerator(3); + } +} + +const QString Role::ProtoStrings[] = { + "none", + "visitor", + "participant", + "moderator" +}; + +Role::Role(const QString &proto) + : m_index(-1) +{ + for(int i = 0; i < LastIndex; i++) { + if(ProtoStrings[i] == proto) { + m_index = i; + break; + } + } +} + +Role::Role(int index) + : m_index(index) +{ +} + +Role::Role(const Role &a) + : m_index(a.index()) +{ +} + +Role &Role::operator=(const Role &a) +{ + m_index = a.index(); + return *this; +} + +int Role::index() const +{ + return m_index; +} + +QString Role::toProtoString() const +{ + if(isValid()) + return ProtoStrings[m_index]; + else + return QString::null; +} + +QString Role::toString(bool lower) const +{ + static const QString strings[] = { + QT_TRANSLATE_NOOP("Role", "None"), + QT_TRANSLATE_NOOP("Role", "Visitor"), + QT_TRANSLATE_NOOP("Role", "Participant"), + QT_TRANSLATE_NOOP("Role", "Moderator") + }; + + if(isValid()) { + QString str = qApp->translate("Role", strings[m_index]); + if(lower) + return str.lower(); + else + return str; + } + else + return QString::null; +} + +Role &Role::operator++() +{ + m_index++; + return *this; +} + +Role Role::operator++(int) +{ + Role temp = *this; + ++*this; + return temp; +} + +Role &Role::operator--() +{ + m_index--; + return *this; +} + +Role Role::operator--(int) +{ + Role temp = *this; + --*this; + return temp; +} + +bool Role::isValid() const +{ + if(m_index >= 0 && m_index < LastIndex) + return true; + else + return false; +} + +bool Role::isLowest() const +{ + return (m_index <= 0); +} + +bool Role::isHighest() const +{ + return (m_index >= (LastIndex - 1)); +} + +namespace XMPP +{ + namespace MUC + { + // Affiliation + bool operator<(const Affiliation &a, const Affiliation &b) + { + return (a.index() < b.index()); + } + + bool operator<=(const Affiliation &a, const Affiliation &b) + { + return (a.index() <= b.index()); + } + + bool operator>(const Affiliation &a, const Affiliation &b) + { + return (a.index() > b.index()); + } + + bool operator>=(const Affiliation &a, const Affiliation &b) + { + return (a.index() >= b.index()); + } + + bool operator==(const Affiliation &a, const Affiliation &b) + { + return (a.index() == b.index()); + } + + // Role + bool operator<(const Role &a, const Role &b) + { + return (a.index() < b.index()); + } + + bool operator<=(const Role &a, const Role &b) + { + return (a.index() <= b.index()); + } + + bool operator>(const Role &a, const Role &b) + { + return (a.index() > b.index()); + } + + bool operator>=(const Role &a, const Role &b) + { + return (a.index() >= b.index()); + } + + bool operator==(const Role &a, const Role &b) + { + return (a.index() == b.index()); + } + } +} diff -Naur psi-0.10-orig/iris/jabber/muc.h psi-0.10/iris/jabber/muc.h --- psi-0.10-orig/iris/jabber/muc.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/jabber/muc.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,187 @@ +/* + * muc.h - Multi-User Chat + * Copyright (C) 2005, Nolan Eakins + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 MUC_H +#define MUC_H + +#include +class QString; + +namespace XMPP +{ + namespace MUC + { + /** Represents an occupant's affiliation. + */ + class Affiliation + { + public: + static const int LastIndex = 5; + static const QString ProtoStrings[]; + + /** Constructs an affiliation from a protocol string. + * If the string is not a valid protocol string, the + * affiliation is left in an invalid state. + * @arg proto An affiliation string from XML + */ + Affiliation(const QString &); + /** Constructs an affiliation from an index. + * If the index is out of range the affiliation is invalid. + * @arg index Index value, the default of -1 makes + * an invalid affiliation. + */ + Affiliation(int index = -1); + Affiliation(const Affiliation &); + Affiliation &operator=(const Affiliation &); + + /** Returns an integer that can be used for array indexes. + */ + int index() const; + /** Returns a string acceptable for use over the XML stream. + */ + QString toProtoString() const; + /** Returns a readable string acceptable for the user. + * @arg lower This determines if the string should be all lower case. + */ + QString toString(bool = false) const; + + /** Increases this affiliation to the next one, until the last. + */ + Affiliation &operator++(); + /** Postfix increase. + */ + Affiliation operator++(int); + /** Decreases this affiliation to previous one until the first. + */ + Affiliation &operator--(); + /** Postfix decrease. + */ + Affiliation operator--(int); + + /** Returns true if this affiliation instance is not a true affiliation. + */ + bool isValid() const; + /** Returns true if this affiliation is the lowest affiliation. + */ + bool isLowest() const; + /** Returns true if this affiliation is the highest. + */ + bool isHighest() const; + + private: + int m_index; + }; + + /** Represents an occupant's role. + */ + class Role + { + public: + static const int LastIndex = 4; + static const QString ProtoStrings[]; + + /** Constructs an role from a protocol string. + * If the string is not a valid protocol string, the + * role is left in an invalid state. + * @arg proto An role string from XML + */ + Role(const QString &); + /** Constructs an role from an index. + * If the index is out of range the role is invalid. + * @arg index Index value, the default of -1 makes + * an invalid role. + */ + Role(int index = -1); + Role(const Role &); + Role &operator=(const Role &); + + /** Returns an integer that can be used for array indexes. + */ + int index() const; + /** Returns a string acceptable for use over the XML stream. + */ + QString toProtoString() const; + /** Returns a readable string acceptable for the user. + * @arg lower This determines if the string should be all lower case. + */ + QString toString(bool = false) const; + + /** Increases this role to the next one, until the last. + */ + Role &operator++(); + /** Postfix increase. + */ + Role operator++(int); + /** Decreases this role to previous one until the first. + */ + Role &operator--(); + /** Postfix decrease. + */ + Role operator--(int); + + /** Returns true if this role instance is not a true role. + */ + bool isValid() const; + /** Returns true if this role is the lowest role. + */ + bool isLowest() const; + /** Returns true if this role is the highest. + */ + bool isHighest() const; + + private: + int m_index; + }; + + bool operator<(const Affiliation &, const Affiliation &); + bool operator<=(const Affiliation &, const Affiliation &); + bool operator>(const Affiliation &, const Affiliation &); + bool operator>=(const Affiliation &, const Affiliation &); + bool operator==(const Affiliation &, const Affiliation &); + inline bool operator!= (const Affiliation &a, const Affiliation &b) + { + return !(a == b); + } + + bool operator<(const Role &, const Role &); + bool operator<=(const Role &, const Role &); + bool operator>(const Role &, const Role &); + bool operator>=(const Role &, const Role &); + bool operator==(const Role &, const Role &); + inline bool operator!= (const Role &a, const Role &b) + { + return !(a == b); + } + + extern const Affiliation AffilUnknown, AffilOutcast, AffilNone, + AffilMember, AffilAdmin, AffilOwner; + extern const Role RoleUnknown, RoleNone, RoleVisitor, + RoleParticipant, RoleModerator; + + template class AffilMap: public QMap + { + }; + + template class RoleMap: public QMap + { + }; + } +} + +#endif /* MUC_H */ diff -Naur psi-0.10-orig/iris/jabber/muc_tasks.cpp psi-0.10/iris/jabber/muc_tasks.cpp --- psi-0.10-orig/iris/jabber/muc_tasks.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/jabber/muc_tasks.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,608 @@ +/* + * muc_tasks.cpp - basic tasks + * Copyright (C) 2004, Nolan Eakins + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 "xmpp_xmlcommon.h" +#include "xmpp_xdata.h" +#include "muc.h" +#include "muc_tasks.h" + +using std::for_each; +using namespace XMPP::MUC; + +//---------------------------------------------------------------------------- +// MT_RoomConfig +//---------------------------------------------------------------------------- +static const char *MUC_OWNER_NS = "http://jabber.org/protocol/muc#owner"; +static const char *MUC_ADMIN_NS = "http://jabber.org/protocol/muc#admin"; + +class MT_RoomConfig::Private +{ +public: + Type type; + Jid room; + QDomElement iq; + XData xdata; +}; + +MT_RoomConfig::MT_RoomConfig(Task *parent) + : Task(parent) +{ + d = new Private; + d->type = None; +} + +MT_RoomConfig::~MT_RoomConfig() +{ + delete d; +} + +MT_RoomConfig::Type MT_RoomConfig::type() const +{ + return d->type; +} + +bool MT_RoomConfig::wasForbidden() const +{ + return (d->type == Forbidden ? true : false); +} + +const XData &MT_RoomConfig::xdata() const +{ + return d->xdata; +} + +void MT_RoomConfig::getXData(const Jid &room) +{ + d->type = Get; + d->room = room; + + d->iq = createIQ(doc(), "get", room.bare(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", MUC_OWNER_NS); + d->iq.appendChild(query); +} + +void MT_RoomConfig::setXData(const Jid &room, const XData &xdata) +{ + d->type = Set; + d->room = room; + + d->iq = createIQ(doc(), "set", room.bare(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", MUC_OWNER_NS); + d->iq.appendChild(query); + + d->xdata = xdata; + d->xdata.setType(XData::Data_Submit); + query.appendChild(d->xdata.toXml(doc())); +} + +void MT_RoomConfig::cancel(const Jid &room) +{ + d->type = Set; + d->room = room; + + d->iq = createIQ(doc(), "set", room.bare(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", MUC_OWNER_NS); + d->iq.appendChild(query); + + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "jabber:x:data"); + x.setAttribute("type", "cancel"); + query.appendChild(x); +} + +void MT_RoomConfig::instant(const Jid &room) +{ + d->type = Set; + d->room = room; + + d->iq = createIQ(doc(), "set", room.full(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", MUC_OWNER_NS); + d->iq.appendChild(query); + + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "jabber:x:data"); + x.setAttribute("type", "submit"); + query.appendChild(x); +} + +bool MT_RoomConfig::take(const QDomElement &e) +{ + if(!iqVerify(e, d->room, id())) + return false; + + if(e.attribute("type") == "result") { + if(d->type == Get) { + QDomElement query = queryTag(e); + bool found = false; + QDomElement x = findSubTag(query, "x", &found); + if(found) { + d->xdata.fromXml(x); + setSuccess(Success); + } + else { + setError(NoConfig, tr("No configuration form")); + } + } + else if(d->type == Set) { + setSuccess(Success); + } + } + else if(e.attribute("type") == "error") { + if(d->type == Get) + setError(Forbidden, tr("Forbidden")); + else + setError(Error, tr("Unhandled error")); + } + + return true; +} + +void MT_RoomConfig::onGo() +{ + send(d->iq); +} + + +//---------------------------------------------------------------------------- +// MT_ChangeOccupants::Item +//---------------------------------------------------------------------------- +class MT_ChangeOccupants::Item::Private +{ +public: + Private() + : role(RoleUnknown), + orig_role(RoleUnknown), + affil(AffilUnknown), + orig_affil(AffilUnknown) + { + } + + Jid jid; + QString nick, reason; + Role role, orig_role; + Affiliation affil, orig_affil; +}; + +MT_ChangeOccupants::Item::Item() +{ + d = new Private; +} + +MT_ChangeOccupants::Item::Item(const MT_ChangeOccupants::Item &i) +{ + d = new Private(*i.d); +} + +MT_ChangeOccupants::Item::~Item() +{ + delete d; +} + +MT_ChangeOccupants::Item &MT_ChangeOccupants::Item::operator=(const MT_ChangeOccupants::Item &i) +{ + *d = *i.d; + return *this; +} + +QString MT_ChangeOccupants::Item::nick() const +{ + return d->nick; +} + +Jid &MT_ChangeOccupants::Item::jid() const +{ + return d->jid; +} + +void MT_ChangeOccupants::Item::setJid(const Jid &j) +{ + d->jid = j; +} + +QString MT_ChangeOccupants::Item::reason() const +{ + return d->reason; +} + +void MT_ChangeOccupants::Item::setReason(const QString &r) +{ + d->reason = r; +} + +void MT_ChangeOccupants::Item::setNick(const QString &n) +{ + d->nick = n; +} + +Role MT_ChangeOccupants::Item::role() const +{ + return d->role; +} + +void MT_ChangeOccupants::Item::setRole(Role r, bool orig) +{ + d->role = r; + if(orig) + d->orig_role = r; +} + +Affiliation MT_ChangeOccupants::Item::affil() const +{ + return d->affil; +} + +void MT_ChangeOccupants::Item::setAffil(Affiliation a, bool orig) +{ + d->affil = a; + if(orig) + d->orig_affil = a; +} + +bool MT_ChangeOccupants::Item::changed() const +{ + return ((d->affil != d->orig_affil) || + (d->role != d->orig_role)); +} + +Role MT_ChangeOccupants::Item::origRole() const +{ + return d->orig_role; +} + +Affiliation MT_ChangeOccupants::Item::origAffil() const +{ + return d->orig_affil; +} + +//---------------------------------------------------------------------------- +// MT_ChangeOccupants +//---------------------------------------------------------------------------- +class MT_ChangeOccupants::Private +{ +public: + Jid room; + Affiliation affil; + Role role; + ItemList items; + + enum Action { Get, Set }; + Action action; +}; + +MT_ChangeOccupants::MT_ChangeOccupants(Task *parent, const Jid &room, Affiliation affil) + : Task(parent) +{ + d = new Private; + d->room = room; + d->affil = affil; + d->role = RoleUnknown; +} + +MT_ChangeOccupants::MT_ChangeOccupants(Task *parent, const Jid &room, Role role) + : Task(parent) +{ + d = new Private; + d->room = room; + d->affil = AffilUnknown; + d->role = role; +} + +MT_ChangeOccupants::~MT_ChangeOccupants() +{ + delete d; +} + +const Jid &MT_ChangeOccupants::room() const +{ + return d->room; +} + +Affiliation MT_ChangeOccupants::affil() const +{ + return d->affil; +} + +Role MT_ChangeOccupants::role() const +{ + return d->role; +} + +void MT_ChangeOccupants::getList(bool autodelete) +{ + d->action = Private::Get; + go(autodelete); +} + +void MT_ChangeOccupants::setList(bool autodelete) +{ + d->action = Private::Set; + go(autodelete); +} + +bool MT_ChangeOccupants::take(const QDomElement &e) +{ + if(!iqVerify(e, d->room, id())) + return false; + + bool cleared_items = false; + + QDomElement query = queryTag(e); + for(QDomNode n = query.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if(i.isNull()) + continue; + + if(i.tagName() == "item") { + // only clear the item list if the returned query has them + if(!cleared_items) { + d->items.clear(); + cleared_items = true; // don't forget this! + } + + Item item; + item.setJid(i.attribute("jid")); + item.setNick(i.attribute("nick")); + item.setAffil(Affiliation(i.attribute("affiliation")), true); + item.setRole(Role(i.attribute("role")), true); + d->items.push_back(item); + } + } + + if(e.attribute("type") == "result") { + setSuccess(); + } + else { + setError(e); + } + + return true; +} + +MT_ChangeOccupants::ItemList &MT_ChangeOccupants::items() +{ + return d->items; +} + +const MT_ChangeOccupants::ItemList &MT_ChangeOccupants::items() const +{ + return d->items; +} + +void MT_ChangeOccupants::setItems(const ItemList &items) +{ + d->items = items; +} + +class ItemListBuilderFunk +{ +public: + MT_ChangeOccupants *par; + MT_ChangeOccupants::ItemList &items; + QString reason; + + ItemListBuilderFunk(MT_ChangeOccupants *p, const QString &r) + : par(p), items(p->items()), reason(r) + { + } + + void common(MT_ChangeOccupants::Item &i) + { + i.setReason(reason); + i.setRole(par->role()); + i.setAffil(par->affil()); + items.push_back(i); + } + + void operator () (const QString &nick) + { + MT_ChangeOccupants::Item i; + i.setNick(nick); + common(i); + } + + void operator () (const Jid &jid) + { + MT_ChangeOccupants::Item i; + i.setJid(jid); + common(i); + } +}; + +void MT_ChangeOccupants::setItems(const QStringList &strings, const QString &reason) +{ + for_each(strings.begin(), strings.end(), + ItemListBuilderFunk(this, reason)); +} + +void MT_ChangeOccupants::setItems(const QValueList &jids, const QString &reason) +{ + for_each(jids.begin(), jids.end(), + ItemListBuilderFunk(this, reason)); +} + +void MT_ChangeOccupants::onGo() +{ + QString action; + if(d->action == Private::Get) + action = "get"; + else + action = "set"; + + QDomElement iq = createIQ(doc(), action, d->room.bare(), id()); + QDomElement query = doc()->createElement("query"); + iq.appendChild(query); + +#ifdef OLD_JEP_0045 + // This is ifdeffed out due to updates to the JEP. + // In the case that legacy servers don't update quick + // enough, then this may need to be enabled. + if(d->affil == AffilOwner || + d->affil == AffilAdmin) + query.setAttribute("xmlns", MUC_OWNER_NS); + else +#endif + query.setAttribute("xmlns", MUC_ADMIN_NS); + + if(d->action == Private::Get) { + QDomElement item = doc()->createElement("item"); + query.appendChild(item); + + if(d->affil != AffilUnknown) { + item.setAttribute("affiliation", d->affil.toProtoString()); + } + else { + item.setAttribute("role", d->role.toProtoString()); + } + } + else { + // iterate through items + ItemList::iterator it(d->items.begin()); + bool doing_affils = d->affil != AffilUnknown; + + for(; it != d->items.end(); ++it) { + // create element for each changed item + Item &item(*it); + + if(item.changed()) { + QDomElement item_elem = doc()->createElement("item"); + query.appendChild(item_elem); + + if(doing_affils) { + item_elem.setAttribute("jid", item.jid().bare()); + item_elem.setAttribute("affiliation", + item.affil().toProtoString()); + } + else { + item_elem.setAttribute("nick", item.nick()); + item_elem.setAttribute("role", + item.role().toProtoString()); + } + + if(!item.reason().isEmpty()) { + QDomElement reason = textTag(doc(), "reason", + item.reason()); + item_elem.appendChild(reason); + } + } + } + } + + send(iq); +} + +bool MT_ChangeOccupants::changed() const +{ + for(ItemList::const_iterator it = d->items.begin(); + it != d->items.end(); + ++it) { + const Item &item(*it); + if(item.changed()) + return true; + } + + return false; +} + +//---------------------------------------------------------------------------- +// MT_DestroyRoom +//---------------------------------------------------------------------------- +class MT_DestroyRoom::Private +{ +public: + Private(const Jid &r, const QString &reas, const Jid &v) + : room(r), venue(v), reason(reas) + { + } + + Jid room, venue; + QString reason; +}; + +MT_DestroyRoom::MT_DestroyRoom(Task *parent, const Jid &room, + const QString &reason, const Jid &venue) + : Task(parent) +{ + d = new Private(room, reason, venue); +} + +MT_DestroyRoom::~MT_DestroyRoom() +{ + delete d; +} + +const Jid &MT_DestroyRoom::room() const +{ + return d->room; +} + +QString MT_DestroyRoom::reason() const +{ + return d->reason; +} + +const Jid &MT_DestroyRoom::venue() const +{ + return d->venue; +} + +bool MT_DestroyRoom::take(const QDomElement &e) +{ + if(!iqVerify(e, d->room, id())) + return false; + + if(e.attribute("type") == "result") { + setSuccess(); + } + else if(e.attribute("type") == "error") { + setError(e); + } + + return true; +} + +void MT_DestroyRoom::onGo() +{ + QDomElement iq = createIQ(doc(), "set", d->room.bare(), id()); + QDomElement query = doc()->createElement("query"); + query.setAttribute("xmlns", MUC_OWNER_NS); + iq.appendChild(query); + + QDomElement destroy = doc()->createElement("destroy"); + query.appendChild(destroy); + + if(d->venue.isValid()) { + destroy.setAttribute("jid", d->venue.bare()); + } + + if(!d->reason.isEmpty()) { + destroy.appendChild(textTag(doc(), "reason", d->reason)); + } + + send(iq); +} diff -Naur psi-0.10-orig/iris/jabber/muc_tasks.h psi-0.10/iris/jabber/muc_tasks.h --- psi-0.10-orig/iris/jabber/muc_tasks.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/jabber/muc_tasks.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,345 @@ +/* + * muc_tasks.h - MUC tasks + * Copyright (C) 2004, Nolan Eakins + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 MUC_TASKS_H +#define MUC_TASKS_H + +#include +#include "im.h" + +namespace XMPP +{ + class XData; + namespace MUC + { + class Affiliation; + class Role; + }; +}; + +class QStringList; +using XMPP::XData; +using XMPP::Task; +using XMPP::Jid; +using XMPP::Status; +using XMPP::MUC::Affiliation; +using XMPP::MUC::Role; + +/** + * MT_RoomConfig is the Task to use to configure a groupchat. + * Using it is pretty basic. Basically you need to create it, + * get the room's configuration form, change the values, + * and send it back to the room. Here's an example: + * + * Create the task: + * \code + * MT_RoomConfig *task = new MT_RoomConfig(client->rootTask()); + * connect(task, SIGNAL(finished()), SLOT(formReceived())); + * task->getXData(rooms_jid); + * task->go(); + * \endcode + * + * When the finished signal is emitted, your slot should do + * something like the following: + * \code + * if(task->success()) { + * XData &xdata = task->xdata(); + * // mess with xdata + * } + * else { + * // something went wrong + * if(task->statusCode() == MT_RoomConfig::NoConfig) { + * // the room didn't have a config form, so... + * } + * else { + * // display the statusString + * qWarn(task->statusString()); + * } + * } + * \endcode + * + * Sending the form is just as easy and will emit the + * finished() signal when it is done: + * \code + * // assuming the task has been created like the get + * task->setXData(room, xdata); + * task->go(); + * \endcode + * + * To see this task in action refer to either groupchatdlg.cpp + * or roomcfgdlg.cpp. + */ +class MT_RoomConfig: public Task +{ + Q_OBJECT; + +public: + /** Specifies the type of form this task is currently handling. + */ + enum Type{ + None, /*!< Nothing has been done yet. */ + Get, /*!< Retrieve the form. */ + Set, /*!< Set the room's form. */ + Success, /*!< The task was successful. */ + Error, /*!< There was trouble. */ + NoConfig, /*!< No config form was available. */ + Forbidden /*!< The account is not allowed to config the room. */ + }; + + /** + * Constructs a Task used to configure groupchats. + * \param task The parent task. + */ + MT_RoomConfig(Task *); + virtual ~MT_RoomConfig(); + + Type type() const; + bool wasForbidden() const; + bool wasSuccessful() const; + + const XData &xdata() const; + /** + * Sets the task up to get a room's form. + * \param room The Jid of the room. + */ + void getXData(const Jid &); + /** + * Uploads the form to the room. + * \param room The room's Jid. + * \param xdata The XData form to send back that should be + * the retrieved form with its values set. + */ + void setXData(const Jid &, const XData &); + + /** + * Cancels a room configuration. + * \pre The room's form has been retrieved. + * \param room The room's Jid. + */ + void cancel(const Jid &); + /** + * Instantly configures the room. + * This sets the room up with the default configuration. + * \param room The room's Jid. + */ + void instant(const Jid &); + + /** + * Processes the QDomElement received from + * the room. + */ + virtual bool take(const QDomElement &); + +protected: + void onGo(); + +private: + class Private; + Private *d; +}; + + +/** + * A Task used to change or request the roles and/or affiliations of occupants. + * This task is primarily used in groupchatdlg.cpp and muc_affileditordlg.cpp. + * + * Usage of this class is quite simple. For an example you can do the following + * to make "romeo@shakespeare.lit" and "juliet@shakespeare.lit" owners of + * the "starcrossed@muc.shakespeare.lit" room: + * \code + * MT_ChangeOccupants *task = new MT_ChangeOccupants(root_task, "starcrossed@muc.shakespeare.lit", AffilOwner); + * QValueList jids; + * jids << Jid("romeo@shakespeare.lit"); + * jids << Jid("juliet@shakespeare.lit"); + * task->setItems(jids, "Starcrossed indeed"); + * + * task->setList(); + * // and await for the finished() signal + * \endcode + * + * Do note that go() is never called. setList() and getList() call this for + * you. + */ +class MT_ChangeOccupants: public Task +{ + Q_OBJECT; + +public: + /** + * Represents an occupant that is to be changed. + */ + class Item + { + public: + Item(); + Item(const Item &); + ~Item(); + + Item &operator=(const Item &); + + bool changed() const; + + /** The occupant's real Jid. */ + Jid &jid() const; + /** Sets the occupant's real Jid. */ + void setJid(const Jid &); + + QString nick() const; + void setNick(const QString &); + + /** The reason this occupant has been changed. */ + QString reason() const; + /** Sets the reason for the change. */ + void setReason(const QString &); + + Role role() const; + /** Changes the occupant's role. + * \param role The new role. + * \param orig If true the original role value is set too. + */ + void setRole(Role, bool orig = false); + + Affiliation affil() const; + /** Changes the occupant's affiliation. + * \param affil The new affiliation. + * \param orig If true the original affiliation value is set too. + */ + void setAffil(Affiliation, bool orig = false); + + Role origRole() const; + Affiliation origAffil() const; + + private: + class Private; + Private *d; + }; + + /** The type used to store the list of occupants. */ + typedef QValueList ItemList; + + /** Constructs the change occupant task. + * \pre The Client is in the room that is being changed. + * \param task The parent task. + * \param room The room that this task is changing. + * \param affil The affiliation we are changing or requesting. + */ + MT_ChangeOccupants(Task *, const Jid &, Affiliation); + /** + * Constructs the change occupant task. + * \pre The Client is in the room that is being changed. + * \param task The parent task. + * \param room The room that this task is changing. + * \param role The role we are changing or requesting. + */ + MT_ChangeOccupants(Task *, const Jid &, Role); + ~MT_ChangeOccupants(); + + const Jid &room() const; + Affiliation affil() const; + Role role() const; + + /** + * Retrieves the list of occupants with the role + * or affiliation that this task was constructed + * with. This calls Task::go(). + * \param autodelete Set to true to automatically delete + * this task when it done. + */ + void getList(bool autodelete = false); + /** + * Sets the roles or affiliations of the occupants + * that this task's list contains. This calls Task::go(). + * \param autodelete Set to true to automatically delete + * this task when it done. + */ + void setList(bool autodelete = false); + + virtual bool take(const QDomElement &); + + ItemList &items(); + const ItemList &items() const; + + /** + * Sets the internal list of items to the supplied list. + */ + void setItems(const ItemList &); + /** + * Populates the list of items with a list of nicknames and sets + * their reason to the one supplied. + * \param nicks A list of strings specifing each occupant's nick. + * \param reason The reason why they're being changed. + */ + void setItems(const QStringList &, const QString &); + /** + * Populates the list of items with a list of JIDs and sets their + * reason to the one supplied. + * \param jids A list of Jids. + * \param reason The reason why they're being changed. + */ + void setItems(const QValueList &, const QString &); + + /** Returns true if any items have been changed. */ + bool changed() const; + +protected: + void onGo(); + +private: + class Private; + Private *d; +}; + +/** + * Use this task to destroy a groupchat. + * This task is extremely easy to use. Just create it + * and go(). Not much else to it. + * + * This class is used in groupchatdlg.cpp. + */ +class MT_DestroyRoom: public Task +{ + Q_OBJECT; + +public: + /** + * Constructs the room destroyer. + * \param task The parent task. + * \param room The room to be destroyed. + * \param reason Why this room is being destroyed. + * \param venue Where the occupants will be told to go. + */ + MT_DestroyRoom(Task *, const Jid &, const QString &, const Jid &); + virtual ~MT_DestroyRoom(); + + const Jid &room() const; + QString reason() const; + const Jid &venue() const; + + virtual bool take(const QDomElement &); + +protected: + void onGo(); + +private: + class Private; + Private *d; +}; + +#endif diff -Naur psi-0.10-orig/iris/tests/jabber/test_muc.cpp psi-0.10/iris/tests/jabber/test_muc.cpp --- psi-0.10-orig/iris/tests/jabber/test_muc.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/tests/jabber/test_muc.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,290 @@ +/* + * test_muc.cpp - Multi-User Chat unit test + * Copyright (C) 2005, Nolan Eakins + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 "muc.h" +#include "test_muc.h" + +using namespace XMPP::MUC; + +void AffiliationTest::testConstructors() +{ + Affiliation a, b(0); + CPPUNIT_ASSERT(a.index() == -1); + CPPUNIT_ASSERT(b.index() == 0); + CPPUNIT_ASSERT(a.isValid() == false); + CPPUNIT_ASSERT(b.isValid() == true); + + Affiliation c(b); + CPPUNIT_ASSERT(c.index() == b.index()); + + a = b; + CPPUNIT_ASSERT(a.index() == b.index()); + + a = Affiliation(QString::null); + CPPUNIT_ASSERT(a.isValid() == false); + CPPUNIT_ASSERT(a.isLowest() == true); + CPPUNIT_ASSERT(a.isHighest() == false); + + a = Affiliation("outcast"); + CPPUNIT_ASSERT(a.isValid() == true); + CPPUNIT_ASSERT(a.index() == 0); + CPPUNIT_ASSERT(a.isLowest() == true); + CPPUNIT_ASSERT(a.isHighest() == false); + + a = Affiliation("member"); + CPPUNIT_ASSERT(a.isValid() == true); + CPPUNIT_ASSERT(a.index() == 2); + CPPUNIT_ASSERT(a.isLowest() == false); + CPPUNIT_ASSERT(a.isHighest() == false); + + a = Affiliation("owner"); + CPPUNIT_ASSERT(a.isValid() == true); + CPPUNIT_ASSERT(a.index() == 4); + CPPUNIT_ASSERT(a.isLowest() == false); + CPPUNIT_ASSERT(a.isHighest() == true); + + a = Affiliation("garbage"); + CPPUNIT_ASSERT(a.index() == -1); + CPPUNIT_ASSERT(a.isValid() == false); + CPPUNIT_ASSERT(a.isLowest() == true); +} + +void AffiliationTest::testIncAndDec() +{ + Affiliation a(0), b; + b = a++; + CPPUNIT_ASSERT(a.index() == 1); + CPPUNIT_ASSERT(b.index() == 0); + b = ++a; + CPPUNIT_ASSERT(a.index() == 2); + CPPUNIT_ASSERT(b.index() == 2); + + a = Affiliation(4); + b = a--; + CPPUNIT_ASSERT(a.index() == 3); + CPPUNIT_ASSERT(b.index() == 4); + b = --a; + CPPUNIT_ASSERT(a.index() == 2); + CPPUNIT_ASSERT(b.index() == 2); +} + +void AffiliationTest::testToStrings() +{ + QString proto_strings[] = { + QString::null, + "outcast", + "none", + "member", + "admin", + "owner", + QString::null + }; + + QString strings[] = { + QString::null, + "Banned", + "None", + "Member", + "Admin", + "Owner", + QString::null + }; + + for(int i = 0; i <= (Affiliation::LastIndex + 1); i++) { + Affiliation a(i - 1); + CPPUNIT_ASSERT(a.toProtoString() == proto_strings[i]); + CPPUNIT_ASSERT(a.toString() == strings[i]); + CPPUNIT_ASSERT(a.toString(true) == strings[i].lower()); + } +} + +void AffiliationTest::testComparators() +{ + CPPUNIT_ASSERT(AffilOutcast < AffilOwner); + CPPUNIT_ASSERT(AffilOutcast <= AffilOwner); + CPPUNIT_ASSERT(AffilOutcast <= AffilOutcast); + CPPUNIT_ASSERT(AffilOwner > AffilOutcast); + CPPUNIT_ASSERT(AffilOwner >= AffilOutcast); + CPPUNIT_ASSERT(AffilOwner == AffilOwner); + CPPUNIT_ASSERT(AffilOutcast != AffilOwner); + CPPUNIT_ASSERT(!(AffilOutcast == AffilOwner)); + CPPUNIT_ASSERT(!(AffilOwner != AffilOwner)); +} + +void AffiliationTest::testHelpers() +{ + CPPUNIT_ASSERT(AffilUnknown.toProtoString() == QString::null); + CPPUNIT_ASSERT(AffilOutcast.toProtoString() == "outcast"); + CPPUNIT_ASSERT(AffilNone.toProtoString() == "none"); + CPPUNIT_ASSERT(AffilMember.toProtoString() == "member"); + CPPUNIT_ASSERT(AffilAdmin.toProtoString() == "admin"); + CPPUNIT_ASSERT(AffilOwner.toProtoString() == "owner"); +} + +void AffiliationTest::testIteration() +{ + int i = 0; + for(Affiliation a(AffilOutcast); + a.isValid(); + ++a, ++i) { + CPPUNIT_ASSERT(a.index() == i); + } + + i = Affiliation::LastIndex - 1; + for(Affiliation a = AffilOwner; + a.isValid(); + --a, --i) { + CPPUNIT_ASSERT(a.index() == i); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(AffiliationTest); + +void RoleTest::testConstructors() +{ + Role a, b(0); + CPPUNIT_ASSERT(a.index() == -1); + CPPUNIT_ASSERT(b.index() == 0); + CPPUNIT_ASSERT(a.isValid() == false); + CPPUNIT_ASSERT(b.isValid() == true); + + Role c(b); + CPPUNIT_ASSERT(c.index() == b.index()); + + a = b; + CPPUNIT_ASSERT(a.index() == b.index()); + + a = Role(QString::null); + CPPUNIT_ASSERT(a.isValid() == false); + CPPUNIT_ASSERT(a.isLowest() == true); + CPPUNIT_ASSERT(a.isHighest() == false); + + a = Role("none"); + CPPUNIT_ASSERT(a.isValid() == true); + CPPUNIT_ASSERT(a.index() == 0); + CPPUNIT_ASSERT(a.isLowest() == true); + CPPUNIT_ASSERT(a.isHighest() == false); + + a = Role("visitor"); + CPPUNIT_ASSERT(a.isValid() == true); + CPPUNIT_ASSERT(a.index() == 1); + CPPUNIT_ASSERT(a.isLowest() == false); + CPPUNIT_ASSERT(a.isHighest() == false); + + a = Role("participant"); + CPPUNIT_ASSERT(a.isValid() == true); + CPPUNIT_ASSERT(a.index() == 2); + CPPUNIT_ASSERT(a.isLowest() == false); + CPPUNIT_ASSERT(a.isHighest() == false); + + a = Role("moderator"); + CPPUNIT_ASSERT(a.isValid() == true); + CPPUNIT_ASSERT(a.index() == 3); + CPPUNIT_ASSERT(a.isLowest() == false); + CPPUNIT_ASSERT(a.isHighest() == true); +} + +void RoleTest::testIncAndDec() +{ + Role a(0), b; + b = a++; + CPPUNIT_ASSERT(a.index() == 1); + CPPUNIT_ASSERT(b.index() == 0); + b = ++a; + CPPUNIT_ASSERT(a.index() == 2); + CPPUNIT_ASSERT(b.index() == 2); + + a = Role(4); + b = a--; + CPPUNIT_ASSERT(a.index() == 3); + CPPUNIT_ASSERT(b.index() == 4); + b = --a; + CPPUNIT_ASSERT(a.index() == 2); + CPPUNIT_ASSERT(b.index() == 2); +} + +void RoleTest::testToStrings() +{ + QString proto_strings[] = { + QString::null, + "none", + "visitor", + "participant", + "moderator", + QString::null + }; + + QString strings[] = { + QString::null, + "None", + "Visitor", + "Participant", + "Moderator", + QString::null + }; + + for(int i = 0; i <= (Role::LastIndex + 1); i++) { + Role a(i - 1); + CPPUNIT_ASSERT(a.toProtoString() == proto_strings[i]); + CPPUNIT_ASSERT(a.toString() == strings[i]); + CPPUNIT_ASSERT(a.toString(true) == strings[i].lower()); + } +} + +void RoleTest::testComparators() +{ + CPPUNIT_ASSERT(RoleNone < RoleModerator); + CPPUNIT_ASSERT(RoleNone <= RoleModerator); + CPPUNIT_ASSERT(RoleVisitor <= RoleVisitor); + CPPUNIT_ASSERT(RoleModerator > RoleNone); + CPPUNIT_ASSERT(RoleVisitor >= RoleVisitor); + CPPUNIT_ASSERT(RoleModerator == RoleModerator); + CPPUNIT_ASSERT(RoleNone != RoleModerator); + CPPUNIT_ASSERT(!(RoleNone == RoleModerator)); + CPPUNIT_ASSERT(!(RoleVisitor != RoleVisitor)); +} + +void RoleTest::testHelpers() +{ + CPPUNIT_ASSERT(RoleUnknown.toProtoString() == QString::null); + CPPUNIT_ASSERT(RoleNone.toProtoString() == "none"); + CPPUNIT_ASSERT(RoleVisitor.toProtoString() == "visitor"); + CPPUNIT_ASSERT(RoleParticipant.toProtoString() == "participant"); + CPPUNIT_ASSERT(RoleModerator.toProtoString() == "moderator"); +} + +void RoleTest::testIteration() +{ + int i = 0; + for(Role a(RoleNone); + a.isValid(); + ++a, ++i) { + CPPUNIT_ASSERT(a.index() == i); + } + + i = Role::LastIndex - 1; + for(Role a = RoleModerator; + a.isValid(); + --a, --i) { + CPPUNIT_ASSERT(a.index() == i); + } +} + +CPPUNIT_TEST_SUITE_REGISTRATION(RoleTest); diff -Naur psi-0.10-orig/iris/tests/jabber/test_muc.h psi-0.10/iris/tests/jabber/test_muc.h --- psi-0.10-orig/iris/tests/jabber/test_muc.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/tests/jabber/test_muc.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,66 @@ +/* + * test_muc.h - Multi-User Chat unit test + * Copyright (C) 2005, Nolan Eakins + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser 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 TEST_MUC_H +#define TEST_MUC_H + +#include +#include + +class AffiliationTest: public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(AffiliationTest); + CPPUNIT_TEST(testConstructors); + CPPUNIT_TEST(testIncAndDec); + CPPUNIT_TEST(testToStrings); + CPPUNIT_TEST(testComparators); + CPPUNIT_TEST(testHelpers); + CPPUNIT_TEST(testIteration); + CPPUNIT_TEST_SUITE_END(); + +public: + void testConstructors(); + void testIncAndDec(); + void testToStrings(); + void testComparators(); + void testHelpers(); + void testIteration(); +}; + +class RoleTest: public CppUnit::TestFixture +{ + CPPUNIT_TEST_SUITE(RoleTest); + CPPUNIT_TEST(testConstructors); + CPPUNIT_TEST(testIncAndDec); + CPPUNIT_TEST(testToStrings); + CPPUNIT_TEST(testComparators); + CPPUNIT_TEST(testHelpers); + CPPUNIT_TEST(testIteration); + CPPUNIT_TEST_SUITE_END(); + +public: + void testConstructors(); + void testIncAndDec(); + void testToStrings(); + void testComparators(); + void testHelpers(); + void testIteration(); +}; + +#endif /* TEST_MUC_H */ diff -Naur psi-0.10-orig/iris/tests/jabber/test_muc.pro psi-0.10/iris/tests/jabber/test_muc.pro --- psi-0.10-orig/iris/tests/jabber/test_muc.pro 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/tests/jabber/test_muc.pro 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,14 @@ +TEMPLATE = app +CONFIG += qt x11 debug + +DEPENDPATH += ../../jabber ../../jabber ../../tests +INCLUDEPATH += . ../../jabber + +unix: { + include(conf.pri) + include(extra.pri) +} + +# Input +HEADERS += test_muc.h ../../jabber/muc.h +SOURCES += test_muc.cpp ../../jabber/muc.cpp ../main.cpp diff -Naur psi-0.10-orig/iris/tests/jabber/test_muc.qc psi-0.10/iris/tests/jabber/test_muc.qc --- psi-0.10-orig/iris/tests/jabber/test_muc.qc 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/tests/jabber/test_muc.qc 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,12 @@ + + test_muc + test_muc.pro + test_muc + qcm + + + + + + + \ Brak znaku nowej linii na kocu pliku diff -Naur psi-0.10-orig/iris/tests/main.cpp psi-0.10/iris/tests/main.cpp --- psi-0.10-orig/iris/tests/main.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/tests/main.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,12 @@ +#include +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + CppUnit::TextUi::TestRunner runner; + CppUnit::TestFactoryRegistry ®istry = CppUnit::TestFactoryRegistry::getRegistry(); + runner.addTest(registry.makeTest()); + return runner.run("", false); +} diff -Naur psi-0.10-orig/iris/tests/qcm/cppunit.qcm psi-0.10/iris/tests/qcm/cppunit.qcm --- psi-0.10-orig/iris/tests/qcm/cppunit.qcm 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/iris/tests/qcm/cppunit.qcm 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,82 @@ +/* +-----BEGIN QCMOD----- +name: CppUnit +-----END QCMOD----- +*/ +#include + +class qc_cppunit: public ConfObj +{ +public: + qc_cppunit(Conf *c) : ConfObj(c) { } + QString name() const { return "CppUnit"; } + QString shortname() const { return "cppunit"; } + bool exec() + { + // QString inc, lib; + // QString s; + + // s = conf->getenv("QC_WITH_CPPUNIT_INC"); + // if(!s.isEmpty()) { + // if(!conf->checkHeader(s, "cppunit/TestSuite.h")) + // return false; + // inc = s; + // } + // else { + // if(!conf->findHeader("cppunit/TestSuite.h", QStringList(), &s)) + // return false; + // inc = s; + // } + + // s = conf->getenv("QC_WITH_CPPUNIT_LIB"); + // if(!s.isEmpty()) { + // if(!conf->checkLibrary(s, "cppunit")) + // return false; + // lib = s; + // } + // else { + // if(!conf->findLibrary("cppunit", &s)) + // return false; + // lib = s; + // } + + // if(!inc.isEmpty()) + // conf->addIncludePath(inc); + // if(!lib.isEmpty()) + // conf->addLib(QString("-L") + lib); + // conf->addLib("-lcppunit"); + + // return true; + QString cppunit_config = QString("cppunit-config"); + QString inc, lib; + + QProcess proc; + proc.addArgument(cppunit_config); + proc.addArgument("--cflags"); + if(!proc.start()) + return false; + + while(proc.isRunning()); + + inc = proc.readLineStdout(); + conf->debug(inc); + + proc.clearArguments(); + proc.addArgument(cppunit_config); + proc.addArgument("--libs"); + if(!proc.start()) + return false; + + while(proc.isRunning()); + + lib = proc.readLineStdout(); + conf->debug(lib); + + if(!inc.isEmpty()) + conf->addIncludePath(inc); + if(!lib.isEmpty()) + conf->addLib(lib); + + return true; + } +}; diff -Naur psi-0.10-orig/iris/xmpp-core/xmlprotocol.cpp psi-0.10/iris/xmpp-core/xmlprotocol.cpp --- psi-0.10-orig/iris/xmpp-core/xmlprotocol.cpp 2005-08-21 17:44:29.000000000 +0000 +++ psi-0.10/iris/xmpp-core/xmlprotocol.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -420,6 +420,11 @@ //elementSend(e); QString out = elementToString(e, clip); +#ifdef MUCONF_HACK + // NOTE: this is to work around mu-conference's newline bug + out = out.replace(">\n<", "><"); +#endif + return internalWriteString(out, TrackItem::Custom, id); } diff -Naur psi-0.10-orig/iris/xmpp-im/client.cpp psi-0.10/iris/xmpp-im/client.cpp --- psi-0.10-orig/iris/xmpp-im/client.cpp 2005-08-21 17:44:28.000000000 +0000 +++ psi-0.10/iris/xmpp-im/client.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -280,7 +280,8 @@ } } -bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick) +bool Client::groupChatJoin(const QString &host, const QString &room, const QString &nick, + const QString &pass, const Status &status) { Jid jid(room + "@" + host + "/" + nick); for(QValueList::Iterator it = d->groupChatList.begin(); it != d->groupChatList.end();) { @@ -302,8 +303,12 @@ i.status = GroupChat::Connecting; d->groupChatList += i; + Status s(status); + s.setMuc(true); + s.setMucPassword(pass); + JT_Presence *j = new JT_Presence(rootTask()); - j->pres(jid, Status()); + j->pres(jid, s); j->go(true); return true; @@ -352,6 +357,27 @@ } } +QString Client::groupChatErrorToString(int code) +{ + static const QString errs[] = { + QString::null, + tr("The password was incorrect."), + QString::null, + tr("You were banned from the room."), + tr("The room does not exist."), + tr("Room creation is restricted."), + QString::null, + tr("You are not a member of the room."), + QString::null, + tr("Someone is already using your nickname.") + }; + + if(code >= 401 && code <= 409) + return errs[code - 400]; + else + return QString::null; +} + /*void Client::start() { if(d->stream->old()) { @@ -709,7 +735,8 @@ break; case GroupChat::Connected: groupChatPresence(j, s); - break; + // we go ahead and fall through in case + // we got kicked or something case GroupChat::Closing: if(us && !s.isAvailable()) { Jid j = i.j; diff -Naur psi-0.10-orig/iris/xmpp-im/types.cpp psi-0.10/iris/xmpp-im/types.cpp --- psi-0.10-orig/iris/xmpp-im/types.cpp 2005-11-01 16:17:36.000000000 +0000 +++ psi-0.10/iris/xmpp-im/types.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -19,75 +19,14 @@ */ #include"im.h" +#include"xmpp_xmlcommon.h" +#include "muc.h" #include #include #define NS_XML "http://www.w3.org/XML/1998/namespace" -static QDomElement textTag(QDomDocument *doc, const QString &name, const QString &content) -{ - QDomElement tag = doc->createElement(name); - QDomText text = doc->createTextNode(content); - tag.appendChild(text); - - return tag; -} - -static QString tagContent(const QDomElement &e) -{ - // look for some tag content - for(QDomNode n = e.firstChild(); !n.isNull(); n = n.nextSibling()) { - QDomText i = n.toText(); - if(i.isNull()) - continue; - return i.data(); - } - - return ""; -} - -static QDateTime stamp2TS(const QString &ts) -{ - if(ts.length() != 17) - return QDateTime(); - - int year = ts.mid(0,4).toInt(); - int month = ts.mid(4,2).toInt(); - int day = ts.mid(6,2).toInt(); - - int hour = ts.mid(9,2).toInt(); - int min = ts.mid(12,2).toInt(); - int sec = ts.mid(15,2).toInt(); - - QDate xd; - xd.setYMD(year, month, day); - if(!xd.isValid()) - return QDateTime(); - - QTime xt; - xt.setHMS(hour, min, sec); - if(!xt.isValid()) - return QDateTime(); - - return QDateTime(xd, xt); -} - -/*static QString TS2stamp(const QDateTime &d) -{ - QString str; - - str.sprintf("%04d%02d%02dT%02d:%02d:%02d", - d.date().year(), - d.date().month(), - d.date().day(), - d.time().hour(), - d.time().minute(), - d.time().second()); - - return str; -}*/ - namespace XMPP { @@ -185,13 +124,16 @@ Stanza::Error error; // extensions + int muc_status; QDateTime timeStamp; UrlList urlList; QValueList eventList; QString eventId; - QString xencrypted, invite; + QString xencrypted, invitationReason, invitationPword; + Jid invitationTo, invitationFrom; bool spooled, wasEncrypted; + bool declineInvite; }; //! \brief Constructs Message with given Jid information. @@ -204,6 +146,9 @@ d->to = to; d->spooled = false; d->wasEncrypted = false; + d->declineInvite = false; + + d->muc_status = 0; /*d->flag = false; d->spooled = false; d->wasEncrypted = false; @@ -353,6 +298,66 @@ d->error = err; } +int Message::mucStatus() const +{ + return d->muc_status; +} + +void Message::setMucStatus(int i) +{ + d->muc_status = i; +} + +bool Message::declineInvite() const +{ + return d->declineInvite; +} + +void Message::setDeclineInvite(bool yes) +{ + d->declineInvite = yes; +} + +const Jid &Message::invitationTo() const +{ + return d->invitationTo; +} + +void Message::setInvitationTo(const Jid &j) +{ + d->invitationTo = j; +} + +const Jid &Message::invitationFrom() const +{ + return d->invitationFrom; +} + +void Message::setInvitationFrom(const Jid &j) +{ + d->invitationFrom = j; +} + +const QString &Message::invitationReason() const +{ + return d->invitationReason; +} + +void Message::setInvitationReason(const QString &r) +{ + d->invitationReason = r; +} + +const QString &Message::invitationPword() const +{ + return d->invitationPword; +} + +void Message::setInvitationPword(const QString &p) +{ + d->invitationPword = p; +} + QDateTime Message::timeStamp() const { return d->timeStamp; @@ -430,16 +435,6 @@ d->xencrypted = s; } -QString Message::invite() const -{ - return d->invite; -} - -void Message::setInvite(const QString &s) -{ - d->invite = s; -} - bool Message::spooled() const { return d->spooled; @@ -500,6 +495,42 @@ s.appendChild(e); }*/ + // MUC + if(mucStatus() != 0 || !d->invitationTo.isEmpty()) { + QDomElement x = s.createElement("http://jabber.org/protocol/muc#user", "x"); + s.appendChild(x); + if(mucStatus() != 0) { + QDomElement status = s.createElement("http://jabber.org/protocol/muc#user", "status"); + status.setAttribute("code", mucStatus()); + + x.appendChild(status); + } + + if(!d->invitationTo.isEmpty()) { + QDomElement inv; + if(d->declineInvite) + inv = s.createElement("http://jabber.org/protocol/muc#user", "decline"); + else + inv = s.createElement("http://jabber.org/protocol/muc#user", "invite"); + x.appendChild(inv); + + inv.setAttribute("to", invitationTo().full()); + if(!invitationReason().isEmpty()) { + QDomElement reason = s.createTextElement(QString::null, + "reason", + invitationReason()); + inv.appendChild(reason); + } + } + + if(!d->invitationPword.isEmpty()) { + QDomElement pw = s.createTextElement(QString::null, + "password", + invitationPword()); + x.appendChild(pw); + } + } + // urls for(QValueList::ConstIterator uit = d->urlList.begin(); uit != d->urlList.end(); ++uit) { QDomElement x = s.createElement("jabber:x:oob", "x"); @@ -546,18 +577,20 @@ if(!d->xencrypted.isEmpty()) s.appendChild(s.createTextElement("jabber:x:encrypted", "x", d->xencrypted)); - // invite - if(!d->invite.isEmpty()) { - QDomElement e = s.createElement("jabber:x:conference", "x"); - e.setAttribute("jid", d->invite); - s.appendChild(e); - } + // obsolete invite + // if(!d->invite.isEmpty()) { + // QDomElement e = s.createElement("jabber:x:conference", "x"); + // e.setAttribute("jid", d->invite); + // s.appendChild(e); + // } return s; } bool Message::fromStanza(const Stanza &s, int timeZoneOffset) { + bool found = false; + if(s.kind() != Stanza::Message) return false; @@ -603,7 +636,7 @@ // timestamp QDomElement t = root.elementsByTagNameNS("jabber:x:delay", "x").item(0).toElement(); if(!t.isNull()) { - d->timeStamp = stamp2TS(t.attribute("stamp")); + stamp2TS(t.attribute("stamp"), &d->timeStamp); d->timeStamp = d->timeStamp.addSecs(timeZoneOffset * 3600); d->spooled = true; } @@ -653,10 +686,43 @@ // invite t = root.elementsByTagNameNS("jabber:x:conference", "x").item(0).toElement(); - if(!t.isNull()) - d->invite = t.attribute("jid"); - else - d->invite = QString(); + if(!t.isNull()) { + setInvitationFrom(from()); + setFrom(t.attribute("jid")); + + QDomElement e = findSubTag(t, "reason", &found); + if(found) + d->invitationReason = e.text(); + } + + // MUC + t = root.elementsByTagNameNS("http://jabber.org/protocol/muc#user", "x").item(0).toElement(); + if(!t.isNull()) { + found = false; + QDomElement status = findSubTag(t, "status", &found); + if(found) + setMucStatus(status.attribute("code").toInt()); + + QDomElement inv = findSubTag(t, "invite", &found); + if(!found) { + // invites and declines should be exclusive + inv = findSubTag(t, "decline", &found); + d->declineInvite = found; + } + + if(found) { + setInvitationTo(Jid(inv.attribute("to"))); + setInvitationFrom(Jid(inv.attribute("from"))); + + QDomElement e = findSubTag(inv, "reason", &found); + if(found) + setInvitationReason(e.text()); + } + + inv = findSubTag(t, "password", &found); + if(found) + setInvitationPword(inv.text()); + } return true; } @@ -713,85 +779,137 @@ //--------------------------------------------------------------------------- // Status //--------------------------------------------------------------------------- +class Status::Private +{ +public: + Private() + : v_timeStamp(QDateTime::currentDateTime()), + v_isInvisible(false), + ecode(-1), + is_muc(false), muc_destroyed(false), + muc_user_status(-1), + muc_user_role(MUC::RoleUnknown), + muc_user_affiliation(MUC::AffilUnknown), + muc_hist_max_chars(-1), muc_hist_max_stanzas(-1), + muc_hist_seconds(-1) + { + } + + int v_priority; + QString v_show, v_status, v_key; + QDateTime v_timeStamp; + bool v_isAvailable; + bool v_isInvisible; + + QString v_xsigned; + // gabber song extension + QString v_songTitle; + + int ecode; + QString estr; + + bool is_muc, muc_destroyed; + Jid muc_user_jid, muc_actor, muc_destroy_venue; + QString muc_user_nick, muc_password, muc_reason; + int muc_user_status; + MUC::Role muc_user_role; + MUC::Affiliation muc_user_affiliation; + + int muc_hist_max_chars, muc_hist_max_stanzas, muc_hist_seconds; + QDateTime muc_hist_since; +}; + Status::Status(const QString &show, const QString &status, int priority, bool available) { - v_isAvailable = available; - v_show = show; - v_status = status; - v_priority = priority; - v_timeStamp = QDateTime::currentDateTime(); - v_isInvisible = false; - ecode = -1; + d = new Private(); + + d->v_isAvailable = available; + d->v_show = show; + d->v_status = status; + d->v_priority = priority; +} + +Status::Status(const Status &from) +{ + d = new Private; + *this = from; +} + +Status &Status::operator=(const Status &from) +{ + *d = *from.d; + return *this; } Status::~Status() { + delete d; } bool Status::hasError() const { - return (ecode != -1); + return (d->ecode != -1); } void Status::setError(int code, const QString &str) { - ecode = code; - estr = str; + d->ecode = code; + d->estr = str; } void Status::setIsAvailable(bool available) { - v_isAvailable = available; + d->v_isAvailable = available; } void Status::setIsInvisible(bool invisible) { - v_isInvisible = invisible; + d->v_isInvisible = invisible; } void Status::setPriority(int x) { - v_priority = x; + d->v_priority = x; } void Status::setShow(const QString & _show) { - v_show = _show; + d->v_show = _show; } void Status::setStatus(const QString & _status) { - v_status = _status; + d->v_status = _status; } void Status::setTimeStamp(const QDateTime & _timestamp) { - v_timeStamp = _timestamp; + d->v_timeStamp = _timestamp; } void Status::setKeyID(const QString &key) { - v_key = key; + d->v_key = key; } void Status::setXSigned(const QString &s) { - v_xsigned = s; + d->v_xsigned = s; } void Status::setSongTitle(const QString & _songtitle) { - v_songTitle = _songtitle; + d->v_songTitle = _songtitle; } bool Status::isAvailable() const { - return v_isAvailable; + return d->v_isAvailable; } bool Status::isAway() const { - if(v_show == "away" || v_show == "xa" || v_show == "dnd") + if(d->v_show == "away" || d->v_show == "xa" || d->v_show == "dnd") return true; return false; @@ -799,51 +917,201 @@ bool Status::isInvisible() const { - return v_isInvisible; + return d->v_isInvisible; } int Status::priority() const { - return v_priority; + return d->v_priority; } const QString & Status::show() const { - return v_show; + return d->v_show; } const QString & Status::status() const { - return v_status; + return d->v_status; } QDateTime Status::timeStamp() const { - return v_timeStamp; + return d->v_timeStamp; } const QString & Status::keyID() const { - return v_key; + return d->v_key; } const QString & Status::xsigned() const { - return v_xsigned; + return d->v_xsigned; } const QString & Status::songTitle() const { - return v_songTitle; + return d->v_songTitle; } int Status::errorCode() const { - return ecode; + return d->ecode; } const QString & Status::errorString() const { - return estr; + return d->estr; +} + +bool Status::isMuc() const +{ + return d->is_muc; +} + +const Jid &Status::mucUserJid() const +{ + return d->muc_user_jid; +} + +const QString &Status::mucUserNick() const +{ + return d->muc_user_nick; +} + +int Status::mucUserStatus() const +{ + return d->muc_user_status; +} + +const QString &Status::mucPassword() const +{ + return d->muc_password; +} + +MUC::Role Status::mucUserRole() const +{ + return d->muc_user_role; +} + +MUC::Affiliation Status::mucUserAffiliation() const +{ + return d->muc_user_affiliation; +} + +Jid Status::mucActor() const +{ + return d->muc_actor; +} + +const QString &Status::mucReason() const +{ + return d->muc_reason; +} + +bool Status::mucDestroyed() const +{ + return d->muc_destroyed; +} + +const Jid &Status::mucDestroyVenue() const +{ + return d->muc_destroy_venue; +} + +void Status::setMucUserJid(const Jid &j) +{ + d->muc_user_jid = j; +} + +void Status::setMucUserNick(const QString &n) +{ + d->muc_user_nick = n; +} + +void Status::setMucUserStatus(int s) +{ + d->muc_user_status = s; +} + +void Status::setMuc(bool y) +{ + d->is_muc = y; +} + +void Status::setMucPassword(const QString &p) +{ + d->muc_password = p; +} + +void Status::setMucUserRole(MUC::Role r) +{ + d->muc_user_role = r; +} + +void Status::setMucUserAffiliation(MUC::Affiliation a) +{ + d->muc_user_affiliation = a; +} + +void Status::setMucActor(const Jid &j) +{ + d->muc_actor = j; +} + +void Status::setMucReason(const QString &r) +{ + d->muc_reason = r; +} + +void Status::setMucDestroyed(bool yes) +{ + d->muc_destroyed = yes; +} + +void Status::setMucDestroyVenue(const Jid &j) +{ + d->muc_destroy_venue = j; +} + +int Status::mucHistoryMaxChars() const +{ + return d->muc_hist_max_chars; +} + +int Status::mucHistoryMaxStanzas() const +{ + return d->muc_hist_max_stanzas; +} + +int Status::mucHistorySeconds() const +{ + return d->muc_hist_seconds; +} + +const QDateTime &Status::mucHistorySince() const +{ + return d->muc_hist_since; +} + +void Status::setMucHistoryMaxChars(int i) +{ + d->muc_hist_max_chars = i; +} + +void Status::setMucHistoryMaxStanzas(int i) +{ + d->muc_hist_max_stanzas = i; +} + +void Status::setMucHistorySeconds(int i) +{ + d->muc_hist_seconds = i; +} + +void Status::setMucHistorySince(const QDateTime &since) +{ + d->muc_hist_since = since; } @@ -1421,7 +1689,7 @@ bool Features::canGroupchat() const { QStringList ns; - ns << "http://jabber.org/protocol/muc"; + ns << FID_MUC; ns << FID_GROUPCHAT; return test(ns); diff -Naur psi-0.10-orig/iris/xmpp-im/xmpp_tasks.cpp psi-0.10/iris/xmpp-im/xmpp_tasks.cpp --- psi-0.10-orig/iris/xmpp-im/xmpp_tasks.cpp 2005-08-21 17:44:28.000000000 +0000 +++ psi-0.10/iris/xmpp-im/xmpp_tasks.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -26,6 +26,7 @@ //#include"xmpp_stream.h" //#include"xmpp_types.h" #include"xmpp_vcard.h" +#include"muc.h" #include #include @@ -517,6 +518,37 @@ tag.appendChild(x); } } + + if(s.isMuc()) { + QDomElement x = doc()->createElement("x"); + x.setAttribute("xmlns", "http://jabber.org/protocol/muc"); + tag.appendChild(x); + + if(!s.mucPassword().isEmpty()) { + QDomElement p = textTag(doc(), "password", s.mucPassword()); + x.appendChild(p); + } + + if(s.mucHistoryMaxChars() > -1 || + s.mucHistoryMaxStanzas() > -1 || + s.mucHistorySeconds() > -1 || + s.mucHistorySince().isValid()) { + QDomElement h = doc()->createElement("history"); + if(s.mucHistoryMaxStanzas() > -1) + h.setAttribute("maxstanzas", s.mucHistoryMaxStanzas()); + if(s.mucHistoryMaxChars() > -1) + h.setAttribute("maxchars", s.mucHistoryMaxChars()); + if(s.mucHistorySeconds() > -1) + h.setAttribute("seconds", s.mucHistorySeconds()); + if(s.mucHistorySince().isValid()) { + QDateTime dt = s.mucHistorySince(); + // adjust from local time + dt = dt.addSecs(client()->timeZoneOffset() * -3600); + h.setAttribute("since", dt.toString(Qt::ISODate)); + } + x.appendChild(h); + } + } } void JT_Presence::pres(const Jid &to, const Status &s) @@ -572,7 +604,15 @@ getErrorFromElement(e, &code, &str); p.setError(code, str); } - else { + else if(type.contains("subscribe") > 0) { + // NOTE: this was just an 'else', but + // if the type was non-standard + // it would be treated like a subcription. + // now it just falls through like an + // available presence. I'm still unsure + // as to what happnes when the type + // is 'probe'. (RFC 3921, 2.2.1) + // -- Nolan Eakins subscription(j, type); return true; } @@ -625,6 +665,95 @@ else if(i.tagName() == "x" && i.attribute("xmlns") == "http://jabber.org/protocol/e2e") { p.setKeyID(tagContent(i)); } + else if(i.tagName() == "x" && i.attribute("xmlns") == "http://jabber.org/protocol/muc") { + p.setMuc(true); + QDomElement t; + bool found; + + t = findSubTag(i, "history", &found); + if(found) { + if(t.hasAttribute("maxchars")) { + p.setMucHistoryMaxChars(t.attribute("maxchars").toInt()); + } + if(t.hasAttribute("maxstanzas")) { + p.setMucHistoryMaxStanzas(t.attribute("maxstanzas").toInt()); + } + if(t.hasAttribute("seconds")) { + p.setMucHistorySeconds(t.attribute("seconds").toInt()); + } + if(t.hasAttribute("since")) { + QString since = t.attribute("since"); + QDateTime dt = QDateTime::fromString(since, Qt::ISODate); + // adjust time from UTC + dt = dt.addSecs(client()->timeZoneOffset() * 3600); + p.setMucHistorySince(dt); + } + } + + t = findSubTag(i, "password", &found); + if(found) { + p.setMucPassword(tagContent(t)); + } + } + else if(i.tagName() == "x" && i.attribute("xmlns") == "http://jabber.org/protocol/muc#user") { + p.setMuc(true); + + QDomElement t; + bool found; + + t = findSubTag(i, "item", &found); + if(found) { + if(t.hasAttribute("affiliation")) { + p.setMucUserAffiliation(MUC::Affiliation(t.attribute("affiliation"))); + } + if(t.hasAttribute("role")) { + p.setMucUserRole(MUC::Role(t.attribute("role"))); + } + if(t.hasAttribute("jid")) { + p.setMucUserJid(Jid(t.attribute("jid"))); + } + if(t.hasAttribute("nick")) { + p.setMucUserNick(t.attribute("nick")); + } + + QDomElement sub; + sub = findSubTag(t, "actor", &found); + if(found) { + if(sub.hasAttribute("jid")) { + p.setMucActor(Jid(sub.attribute("jid"))); + } + } + + sub = findSubTag(t, "reason", &found); + if(found) { + p.setMucReason(tagContent(sub)); + } + } + + t = findSubTag(i, "destroy", &found); + p.setMucDestroyed(found); + if(found) { + if(t.hasAttribute("jid")) { + p.setMucDestroyVenue(Jid(t.attribute("jid"))); + } + + QDomElement sub = findSubTag(t, "reason", &found); + if(found) { + p.setMucReason(tagContent(sub)); + } + } + + t = findSubTag(i, "status", &found); + if(found) { + if(t.hasAttribute("code")) + p.setMucUserStatus(t.attribute("code").toInt()); + } + + t = findSubTag(i, "password", &found); + if(found) { + p.setMucPassword(tagContent(t)); + } + } } presence(j, p); @@ -1269,19 +1398,16 @@ QDomElement query = doc()->createElement("query"); query.setAttribute("xmlns", "http://jabber.org/protocol/disco#info"); iq.appendChild(query); - QDomElement feature; - feature = doc()->createElement("feature"); - feature.setAttribute("var", "http://jabber.org/protocol/bytestreams"); - query.appendChild(feature); - - feature = doc()->createElement("feature"); - feature.setAttribute("var", "http://jabber.org/protocol/si"); - query.appendChild(feature); - - feature = doc()->createElement("feature"); - feature.setAttribute("var", "http://jabber.org/protocol/si/profile/file-transfer"); - query.appendChild(feature); + QDomElement identity = doc()->createElement("identity"); + identity.setAttribute("category", "client"); + identity.setAttribute("type", "pc"); + query.appendChild(identity); + + addFeature(query, "http://jabber.org/protocol/bytestreams"); + addFeature(query, "http://jabber.org/protocol/si"); + addFeature(query, "http://jabber.org/protocol/si/profile/file-transfer"); + addFeature(query, FID_MUC); send(iq); return true; @@ -1290,6 +1416,12 @@ return false; } +void JT_ServInfo::addFeature(QDomElement &q, const QString &var) { + QDomElement f = doc()->createElement("feature"); + f.setAttribute("var", var); + q.appendChild(f); +} + //---------------------------------------------------------------------------- // JT_Gateway diff -Naur psi-0.10-orig/iris/xmpp-im/xmpp_tasks.h psi-0.10/iris/xmpp-im/xmpp_tasks.h --- psi-0.10-orig/iris/xmpp-im/xmpp_tasks.h 2005-08-21 17:44:28.000000000 +0000 +++ psi-0.10/iris/xmpp-im/xmpp_tasks.h 2006-01-14 00:43:21.000000000 +0000 @@ -311,6 +311,9 @@ ~JT_ServInfo(); bool take(const QDomElement &); + + private: + void addFeature(QDomElement &, const QString &); }; class JT_Gateway : public Task diff -Naur psi-0.10-orig/iris.pri psi-0.10/iris.pri --- psi-0.10-orig/iris.pri 2005-08-21 17:44:27.000000000 +0000 +++ psi-0.10/iris.pri 2006-01-14 00:43:21.000000000 +0000 @@ -39,7 +39,10 @@ $$IRIS_BASE/jabber/xmpp_jidlink.h \ $$IRIS_BASE/jabber/filetransfer.h \ $$IRIS_BASE/include/xmpp.h \ - $$IRIS_BASE/include/im.h + $$IRIS_BASE/include/im.h \ + $$IRIS_BASE/jabber/muc_tasks.h \ + $$IRIS_BASE/jabber/muc.h \ + $$IRIS_BASE/jabber/bookmarks.h SOURCES += \ $$IRIS_BASE/xmpp-core/connector.cpp \ @@ -60,5 +63,8 @@ $$IRIS_BASE/jabber/s5b.cpp \ $$IRIS_BASE/jabber/xmpp_ibb.cpp \ $$IRIS_BASE/jabber/xmpp_jidlink.cpp \ - $$IRIS_BASE/jabber/filetransfer.cpp + $$IRIS_BASE/jabber/filetransfer.cpp \ + $$IRIS_BASE/jabber/muc_tasks.cpp \ + $$IRIS_BASE/jabber/muc.cpp \ + $$IRIS_BASE/jabber/bookmarks.cpp diff -Naur psi-0.10-orig/src/actionlist.cpp psi-0.10/src/actionlist.cpp --- psi-0.10-orig/src/actionlist.cpp 2005-08-21 17:44:28.000000000 +0000 +++ psi-0.10/src/actionlist.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -33,7 +33,7 @@ { Q_OBJECT public: - Private() { } + Private() : QObject() { } Private( const Private & ); QString name; diff -Naur psi-0.10-orig/src/common.cpp psi-0.10/src/common.cpp --- psi-0.10-orig/src/common.cpp 2006-01-07 21:37:22.000000000 +0000 +++ psi-0.10/src/common.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -1520,6 +1520,34 @@ return type; } +int rankStatus(int status) +{ + static int rankstatuses[7] = { + STATUS_CHAT, + STATUS_ONLINE, + STATUS_AWAY, + STATUS_XA, + STATUS_DND, + STATUS_INVISIBLE, + STATUS_OFFLINE, + }; + + int n; + for(n = 0; n < (int)sizeof(rankstatuses); ++n) { + if(rankstatuses[n] == status) + break; + } + if(n == sizeof(rankstatuses)) + return sizeof(rankstatuses)-1; + + return n; +} + +int rankStatus(const XMPP::Status &s) +{ + return rankStatus(makeSTATUS(s)); +} + #include QLayout *rw_recurseFindLayout(QLayout *lo, QWidget *w) { diff -Naur psi-0.10-orig/src/common.h psi-0.10/src/common.h --- psi-0.10-orig/src/common.h 2005-10-06 13:50:35.000000000 +0000 +++ psi-0.10/src/common.h 2006-01-14 00:43:21.000000000 +0000 @@ -35,6 +35,7 @@ //#include"xmpp_types.h" #include"varlist.h" #include"psiiconset.h" +#include"reasons.h" #define PROXY_NONE 0 #define PROXY_HTTPS 1 @@ -91,7 +92,7 @@ bool useleft, singleclick, hideMenubar, askOnline, askOffline, popupMsgs, popupChats, popupHeadlines, popupFiles, raise; bool alwaysOnTop, noAwaySound, noAwayPopup, noUnlistedPopup, rosterAnim, autoVersion, autoVCardOnLogin, xmlConsoleOnLogin; bool useDock, dockDCstyle, dockHideMW, dockToolMW, isWMDock; - bool smallChats, chatLineEdit, showJoins, useTabs, putTabsAtBottom, usePerTabCloseButton, autoRosterSize, autoRosterSizeGrowTop, autoResolveNicksOnAdd; + bool smallChats, chatLineEdit, useTabs, putTabsAtBottom, usePerTabCloseButton, autoRosterSize, autoRosterSizeGrowTop, autoResolveNicksOnAdd; bool autoCopy; // although this setting is ignored under linux, // it is preserved in case user uses the same config file on different platforms @@ -124,6 +125,9 @@ bool raiseChatWindow, showSubjects, showCounter, chatSays, chatSoftReturn, showGroupCounts; QSize sizeEventDlg, sizeChatDlg, sizeTabDlg; + QSize sizeGCDlg; + int sizeOccList; + bool jidComplete, grabUrls, noGCSound; QString pgp; @@ -144,10 +148,20 @@ }; QMap< QString, QValueList > toolbars; + // groupchat options + bool showJoins, showStatusChanges, showRoleAffilChanges; + + // groupchat history management + int gcHistMaxChars, gcHistMaxStanzas, gcHistSeconds; // -1 == not used + QDateTime gcHistSince; // this is in local time + // groupchat highlighting/nick colouring bool gcHighlighting, gcNickColoring; QStringList gcHighlights, gcNickColors; + // groupchat reasons + ReasonMap reasons; + bool clNewHeadings; bool outlineHeadings; @@ -288,6 +302,9 @@ XMPP::Status makeStatus(int, const QString &); int makeSTATUS(const XMPP::Status &); +int rankStatus(int); + +int rankStatus(const XMPP::Status &); void replaceWidget(QWidget *, QWidget *); void closeDialogs(QWidget *); diff -Naur psi-0.10-orig/src/contactview.cpp psi-0.10/src/contactview.cpp --- psi-0.10-orig/src/contactview.cpp 2006-01-07 21:19:01.000000000 +0000 +++ psi-0.10/src/contactview.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -20,6 +20,7 @@ #include"contactview.h" +#include #include #include #include @@ -41,6 +42,10 @@ #include"iconaction.h" #include"alerticon.h" #include"avatars.h" +#include"jiddrag.h" + +using std::for_each; +using XMPP::Jid; //---------------------------------------------------------------------------- // ContactProfile @@ -186,6 +191,18 @@ QObject::setName(s); } +void ContactProfile::invite(const Jid &j, const QString &room) +{ + bool ok = false; + QString reason = QInputDialog::getText(tr("Send Invitation"), + tr("Why are you inviting %1 to %2?").arg(j.bare()).arg(room), + QLineEdit::Normal, QString::null, + &ok); + if(ok) { + actionInvite(j, room, reason); + } +} + void ContactProfile::setState(int state) { if(state == d->oldstate) @@ -1451,11 +1468,9 @@ } else if(x >= base_gc && x < base_group) { if(online) { + // nolan QString gc = groupchats[x - base_gc]; - actionInvite(u->jid(), gc); - - QMessageBox::information(d->cv, tr("Invitation"), - tr("Sent groupchat invitation to %1.").arg(name)); + invite(u->jid(), gc); } } else if(x >= base_group) { @@ -1648,7 +1663,7 @@ } } -void ContactProfile::dragDrop(const QString &text, ContactViewItem *i) +void ContactProfile::dragDrop(const Jid &jid, ContactViewItem *i) { if(!d->pa->loggedIn()) return; @@ -1660,10 +1675,9 @@ else gr = (ContactViewItem *)static_cast(i)->parent(); - Jid j(text); - if(!j.isValid()) + if(!jid.isValid()) return; - Entry *e = findEntry(j); + Entry *e = findEntry(jid); if(!e) return; const UserListItem &u = e->u; @@ -2366,8 +2380,7 @@ if(i->type() != ContactViewItem::Contact) return 0; - QDragObject *d = new QTextDrag(i->u()->jid().full(), this); - d->setPixmap(IconsetFactory::iconPixmap("status/online"), QPoint(8,8)); + JidDrag *d = new JidDrag(i->u()->jid(), this); return d; } @@ -2846,29 +2859,6 @@ return n; } -int ContactViewItem::rankStatus(int status) const -{ - static int rankstatuses[7] = { - STATUS_CHAT, - STATUS_ONLINE, - STATUS_AWAY, - STATUS_XA, - STATUS_DND, - STATUS_INVISIBLE, - STATUS_OFFLINE, - }; - - int n; - for(n = 0; n < (int)sizeof(rankstatuses); ++n) { - if(rankstatuses[n] == status) - break; - } - if(n == sizeof(rankstatuses)) - return sizeof(rankstatuses)-1; - - return n; -} - int ContactViewItem::compare(QListViewItem *lvi, int, bool) const { ContactViewItem *i = (ContactViewItem *)lvi; @@ -3176,12 +3166,13 @@ return true; } - if(!QTextDrag::canDecode(m)) + if(!JidDrag::canDecode(m)) return false; - QString str; - if(!QTextDrag::decode(m, str)) - return false; + // Note: Why is this decoded? - sneakin + // QString str; + // if(!QTextDrag::decode(m, str)) + // return false; return true; } @@ -3196,6 +3187,22 @@ //printf("left\n"); } +class DroppedJidFunk +{ + ContactViewItem *m_view_item; + +public: + DroppedJidFunk(ContactViewItem *vi) + : m_view_item(vi) + { + } + + void operator () (const Jid &j) + { + m_view_item->contactProfile()->dragDrop(j, m_view_item); + } +}; + void ContactViewItem::dropped(QDropEvent *i) { if(!acceptDrop(i)) @@ -3210,11 +3217,12 @@ } } - // Text - if(QTextDrag::canDecode(i)) { - QString text; - if(QTextDrag::decode(i, text)) - d->cp->dragDrop(text, this); + // Text (or a Jid) + if(JidDrag::canDecode(i)) { + JidDrag::List jids; + if(JidDrag::decode(i, jids)) { + for_each(jids.begin(), jids.end(), DroppedJidFunk(this)); + } } } diff -Naur psi-0.10-orig/src/contactview.h psi-0.10/src/contactview.h --- psi-0.10-orig/src/contactview.h 2005-08-22 19:52:28.000000000 +0000 +++ psi-0.10/src/contactview.h 2006-01-14 00:43:21.000000000 +0000 @@ -40,6 +40,7 @@ using namespace XMPP; class ContactProfile; +class DroppedJidFunk; // ContactProfile: this holds/manipulates a roster profile for an account class ContactProfile : public QObject @@ -82,6 +83,7 @@ void setName(const char *); + void invite(const Jid &, const QString &); signals: void actionDefault(const Jid &); @@ -107,7 +109,7 @@ void actionSendFile(const Jid &); void actionSendFiles(const Jid &, const QStringList &); void actionDisco(const Jid &, const QString &); - void actionInvite(const Jid &, const QString &); + void actionInvite(const Jid &, const QString &, const QString &); void actionAssignKey(const Jid &); void actionUnassignKey(const Jid &); @@ -165,7 +167,8 @@ void doContextMenu(ContactViewItem *, const QPoint &); friend class ContactViewItem; - void dragDrop(const QString &, ContactViewItem *); + friend class DroppedJidFunk; + void dragDrop(const Jid &, ContactViewItem *); void dragDropFiles(const QStringList &, ContactViewItem *); }; @@ -329,7 +332,6 @@ void cacheValues(); int rankGroup(int groupType) const; - int rankStatus(int status) const; void drawGroupIcon(); }; diff -Naur psi-0.10-orig/src/customreasondlg.cpp psi-0.10/src/customreasondlg.cpp --- psi-0.10-orig/src/customreasondlg.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/customreasondlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,159 @@ +/* + * customreasondlg.cpp - dialog to prompt the user for a new GC reason + * Copyright (C) 2004, Nolan Eakins + * + * 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 +#include "customreasondlg.h" +#include "common.h" + +class CustomReasonDlg::Private +{ +public: + CustomReasonDlg *parent; + + QLabel *label1; + QPushButton *pb_ok, *pb_cancel; + QCheckBox *ck_save; + QLineEdit *le_reason; + + QString name; + bool was_saved; + Reason::Type type; + + bool save(); +}; + +CustomReasonDlg::CustomReasonDlg(Reason::Type type, QWidget *parent, const char *name) + : QDialog(parent, name, false) +{ + d = new Private; + + d->parent = this; + d->type = type; + d->was_saved = false; + + QVBoxLayout *vLayout = new QVBoxLayout(this, 8); + d->label1 = new QLabel(tr("&Reason:"), this); + vLayout->addWidget(d->label1); + + d->le_reason = new QLineEdit(this); + vLayout->addWidget(d->le_reason); + d->label1->setBuddy(d->le_reason); + + QHBoxLayout *hLayout1 = new QHBoxLayout(vLayout); + d->ck_save = new QCheckBox(tr("&Save?"), this); + d->pb_ok = new QPushButton(tr("&Ok"), this); + d->pb_cancel = new QPushButton(tr("&Cancel"), this); + + connect(d->pb_ok, SIGNAL(clicked()), SLOT(accept())); + connect(d->pb_cancel, SIGNAL(clicked()), SLOT(reject())); + + hLayout1->addWidget(d->ck_save); + hLayout1->addStretch(1); + hLayout1->addWidget(d->pb_ok); + hLayout1->addWidget(d->pb_cancel); + + setMinimumSize(300, 100); + //setMaximumSize(300, 100); + vLayout->setResizeMode(QLayout::FreeResize); + + resize(300, 100); +} + +CustomReasonDlg::~CustomReasonDlg() +{ + delete d; +} + +QString CustomReasonDlg::reasonText() const +{ + return d->le_reason->text(); +} + +QString CustomReasonDlg::reasonName() const +{ + return d->name; +} + +bool CustomReasonDlg::wasSaved() const +{ + return d->was_saved; +} + +void CustomReasonDlg::reject() +{ + QDialog::reject(); + cancelled(); +} + +void CustomReasonDlg::accept() +{ + bool ok = true; + + if(d->ck_save->isChecked()) { + ok = d->save(); + } + + if(ok) { + QDialog::accept(); + accepted(d->le_reason->text()); + } +} + +bool CustomReasonDlg::Private::save() +{ + bool ok = false; + + // loop until the user cancels or the reason gets saved + do { + QString tmp_name = QInputDialog::getText(tr("Reason Name"), tr("Name of reason:"), + QLineEdit::Normal, QString::null, &ok, parent); + if(ok) { + ReasonMap &reasons = option.reasons; + + if(reasons.contains(tmp_name)) { + int result = QMessageBox::question(parent, tr("Name taken"), + tr("A reason already exists with this name. Do you want to update it?"), + QMessageBox::Yes, QMessageBox::No); + if(result == QMessageBox::No) { + continue; + } + } + + Reason reason; + reason.text = le_reason->text(); + reason.type = type; + + reasons.insert(tmp_name, reason); + name = tmp_name; + was_saved = true; + + return true; + } + } while(ok); + + return false; +} diff -Naur psi-0.10-orig/src/customreasondlg.h psi-0.10/src/customreasondlg.h --- psi-0.10-orig/src/customreasondlg.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/customreasondlg.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,52 @@ +/* + * customreasondlg.h - dialog to prompt the user for a new GC reason + * Copyright (C) 2004, Nolan Eakins + * + * 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 CUSTOMREASON_H +#define CUSTOMREASON_H + +#include +#include "reasons.h" + +class CustomReasonDlg: public QDialog +{ + Q_OBJECT; + +public: + CustomReasonDlg(Reason::Type type, QWidget *parent = NULL, const char *name = NULL); + virtual ~CustomReasonDlg(); + + QString reasonName() const; + QString reasonText() const; + bool wasSaved() const; + +signals: + void accepted(const QString &); + void cancelled(); + +private slots: + void reject(); + void accept(); + +private: + class Private; + Private *d; +}; + +#endif diff -Naur psi-0.10-orig/src/dataformdlg.cpp psi-0.10/src/dataformdlg.cpp --- psi-0.10-orig/src/dataformdlg.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/dataformdlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,264 @@ +/* + * dataform.cpp - Data form UI + * Copyright (C) 2004 Nolan Eakins + * + * 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 "common.h" +#include "busywidget.h" +#include "iconwidget.h" +#include "im.h" +#include "xmpp_xdata.h" +#include "xdata_widget.h" +#include "dataformdlg.h" + +class DataFormDlg::Private +{ +public: + XDataWidget *xdataWidget; + XData xdata; + QString help; + + QScrollView *sv_fields; + IconButton *pb_ok, *pb_cancel, *pb_reset, *pb_instruct; + + static QSize savedSize; +}; + +// 620 seems to be mu-conf's favorite width +QSize DataFormDlg::Private::savedSize(620, 350); + +DataFormDlg::DataFormDlg(const QString &caption, const char *name) + : GenericBusyDlg(NULL, name, false, psi_dialog_flags | WDestructiveClose) +{ + d = new Private; + d->xdataWidget = NULL; + + d->pb_ok = new IconButton(this, "pb_ok"); + d->pb_ok->setText(tr("&Ok")); + d->pb_ok->setIcon("psi/ok"); + d->pb_ok->setDefault(true); + + d->pb_cancel = new IconButton(this, "pb_cancel"); + d->pb_cancel->setText(tr("&Cancel")); + d->pb_cancel->setIcon("psi/cancel"); + d->pb_cancel->setDefault(false); + + d->pb_reset = new IconButton(this, "pb_reset"); + d->pb_reset->setText(tr("&Reset")); + d->pb_reset->setIcon("psi/reset"); + d->pb_reset->setDefault(false); + + d->pb_instruct = new IconButton(this, "pb_instruct"); + d->pb_instruct->setText(tr("&Instructions")); + d->pb_instruct->setIcon("psi/help"); + d->pb_instruct->setDefault(false); + + addButton(d->pb_cancel, Right); + addButton(d->pb_ok, Right); + addButton(d->pb_instruct, Left); + addButton(d->pb_reset, Left); + + d->pb_ok->setFocus(); // we can't really focus on the xdata widgets w/o some major work + + connect(d->pb_ok, SIGNAL(clicked()), this, SLOT(accept())); + connect(d->pb_cancel, SIGNAL(clicked()), this, SLOT(reject())); + connect(d->pb_reset, SIGNAL(clicked()), this, SLOT(reset())); + connect(d->pb_instruct, SIGNAL(clicked()), this, SLOT(instructions())); + + d->sv_fields = new QScrollView( this, "sv_fields" ); + d->sv_fields->setSizePolicy(QSizePolicy((QSizePolicy::SizeType)7, + (QSizePolicy::SizeType)7, + 0, 0, + d->sv_fields->sizePolicy().hasHeightForWidth())); + setMainWidget(d->sv_fields); + + setTabOrder(d->sv_fields, d->pb_instruct); + + setCaption(caption); + resize(savedSize()); +} + +DataFormDlg::~DataFormDlg() +{ + delete d->xdataWidget; + delete d; +} + +XData DataFormDlg::xdata() const +{ + XData out; + out.setType(XData::Data_Submit); + out.setFields(d->xdataWidget->fields()); + return out; +} + +void DataFormDlg::setXData(const XData &x) +{ + d->xdata = x; + + reset(); + + d->help = x.instructions(); + + if(!d->help.isEmpty()) { + d->pb_instruct->setEnabled(true); + } + else { + d->pb_instruct->setEnabled(false); + } + + d->sv_fields->setEnabled(true); + d->pb_ok->setEnabled(true); + d->pb_reset->setEnabled(true); +} + +void DataFormDlg::resizeXDataWidget() +{ + if(d->xdataWidget) { + int width = (d->sv_fields->visibleWidth() > d->xdataWidget->minimumWidth()) ? + d->sv_fields->visibleWidth() : d->xdataWidget->minimumWidth(); + d->xdataWidget->resize(width, + d->sv_fields->contentsHeight()); + } +} + +void DataFormDlg::resizeEvent(QResizeEvent *ev) +{ + GenericBusyDlg::resizeEvent(ev); + resizeXDataWidget(); + // remember the size + setSavedSize(ev->size()); +} + +void DataFormDlg::accept() +{ + enableWidgets(false); + busyStart(); + sendForm(); +} + +void DataFormDlg::reject() +{ + cancelForm(); + GenericBusyDlg::reject(); +} + +void DataFormDlg::reset() +{ + if(d->xdataWidget) + delete d->xdataWidget; + + d->xdataWidget = new XDataWidget(d->sv_fields->viewport()); + d->sv_fields->addChild(d->xdataWidget); + d->xdataWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + d->xdataWidget->setFields(d->xdata.fields()); + + d->xdataWidget->show(); + + // this is hack + QTimer::singleShot(100, this, SLOT(resizeXDataWidget())); +} + +void DataFormDlg::instructions() +{ + QMessageBox::information(this, tr("Instructions"), + d->help, QMessageBox::Ok); +} + +void DataFormDlg::polish() +{ + GenericBusyDlg::polish(); + busyStart(); + + getForm(); +} + +void DataFormDlg::enableWidgets(bool yes) +{ + d->sv_fields->setEnabled(yes); + d->pb_ok->setEnabled(yes); + d->pb_reset->setEnabled(yes); + d->pb_instruct->setEnabled(yes); +} + +QSize DataFormDlg::savedSize() +{ + return DataFormDlg::Private::savedSize; +} + +void DataFormDlg::setSavedSize(const QSize &size) +{ + DataFormDlg::Private::savedSize = size; +} + +void DataFormDlg::setSavedSize(int w, int h) +{ + setSavedSize(QSize(w, h)); +} + +void DataFormDlg::getForm() +{ + busyStart(); + enableWidgets(false); + doGetForm(); +} + +void DataFormDlg::sendForm() +{ + busyStart(); + enableWidgets(false); + doSendForm(); +} + +void DataFormDlg::doGetForm() +{ + formReceived(); +} + +void DataFormDlg::doSendForm() +{ + formSent(); +} + +void DataFormDlg::processForm() +{ +} + +void DataFormDlg::cancelForm() +{ +} + +void DataFormDlg::formSent() +{ + // FIXME: check for errors + busyStop(); + GenericBusyDlg::accept(); +} + +void DataFormDlg::formReceived() +{ + busyStop(); + enableWidgets(true); + processForm(); +} diff -Naur psi-0.10-orig/src/dataformdlg.h psi-0.10/src/dataformdlg.h --- psi-0.10-orig/src/dataformdlg.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/dataformdlg.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,144 @@ +/* $Header$ + * dataform.h - Header file for the data form UI + * Copyright (C) 2004 Nolan Eakins + * + * 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 DATAFORM_H +#define DATAFORM_H + +#include "genericbusydlg.h" + +namespace XMPP +{ + class XData; +}; + +using XMPP::XData; +class QString; +class QSize; + +/** Displays an XData form in a dialog. + * To use this class, create a class that inherits DataFormDlg + * and override getForm(), processForm(), cancelForm(), and + * sendForm(). Some signals need to be emitted from those + * methods. getForm() needs to emit formReceived(), and + * sendForm() needs to emit formSent(). + * + * As with other classes that subclass GenericBusyDlg, + * DataFormDlg::accept() and DataFormDlg::reject must to be + * called if those methods are overriden. + */ +class DataFormDlg: public GenericBusyDlg +{ + Q_OBJECT; + +public: + /** Constructs a new DataFormDlg. + * @param caption The text that appears in the dialog's title bar. + * @param name Name of the Qt object. + */ + DataFormDlg(const QString &caption, const char *name = NULL); + virtual ~DataFormDlg(); + + XData xdata() const; + void setXData(const XData &); + + /** This either enables or disables all the widgets + * except the help and cancel buttons. + * @arg yes True if enabled, false if disabled + */ + void enableWidgets(bool); + + /** Call this to retrieve the form. It calls doGetForm() + * which actually does the work after disabling most of + * the widgets and starts the busy widget. doGetForm() MUST + * then send a signal to formReceieved() when the form has been + * receieved. + */ + void getForm(); + /** Call this to send the form. It calls doSendForm() + * which actually does the work of sending form after + * starting the busy widget and disabling most of the + * widgets. doSendForm() MUST then send a signal to + * formSent() when the form has been sent. + */ + void sendForm(); + + /** Returns the saved size of all data form dialogs. + */ + static QSize savedSize(); + /** Sets the sazed size for all data forms dialogs. + * @arg size The size to save. + */ + static void setSavedSize(const QSize &); + /** Sets the sazed size for all data forms dialogs. + * @arg width Sets the saved width. + * @arg width Sets the saved height. + */ + static void setSavedSize(int, int); + +protected: + /** Overide this to retrieve the form. It is called by + * getForm(). Be sure to connect a signal to + * formReceived() to indicate that the form has been received. + */ + virtual void doGetForm(); + /** Override this to send the form. It is called by + * sendForm(). Be sure to connect a signal to formSent() + * to indicate when the form has been sent. + */ + virtual void doSendForm(); + + /** This is called from formReceived() allowing + * any actions to be taken and errors + * need to be checked along with setting the form. + */ + virtual void processForm(); + /** This is called when the dialog is rejected. + */ + virtual void cancelForm(); + + void resizeEvent(QResizeEvent *); + +public slots: + void accept(); + void reject(); + void reset(); + void instructions(); + + void polish(); + +protected slots: + /** This slot is usually connected to a Task::finished() signal. + * This stops the the busy widget and calls processForm(). + */ + void formReceived(); + /** This slot is usually connected to a Task::finished() signal. + * This stops the busy widget and accepts the dialog. + */ + void formSent(); + + void resizeXDataWidget(); + +private: + + class Private; + Private *d; +}; + +#endif diff -Naur psi-0.10-orig/src/destroyroomdlg.cpp psi-0.10/src/destroyroomdlg.cpp --- psi-0.10-orig/src/destroyroomdlg.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/destroyroomdlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,244 @@ +/* $Header$ + * dataform.h - Implementation for the room destruction dialog + * Copyright (C) 2004 Nolan Eakins + * + * 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 "common.h" +#include "im.h" +#include "iconwidget.h" +#include "busywidget.h" +#include "psicon.h" +#include "psiaccount.h" +#include "destroyroomdlg.h" +#include "ui_destroyroom.h" +#include "muc_tasks.h" + +struct DestroyRoomDlg::Private +{ + DestroyRoomUI *destroy_ui; + IconButton *pb_destroy, *pb_close; + Jid room; + PsiAccount *pa; + MT_DestroyRoom *task; + UserProfile::GCList rl; + + Private(const Jid &r, PsiAccount *p) + : room(r), pa(p), task(NULL) + { + } + + ~Private() + { + if(task) + delete task; + } + + inline Client *client() const { + return pa->client(); + } + + inline PsiCon *psi() const { + return pa->psi(); + } +}; + +DestroyRoomDlg::DestroyRoomDlg(const Jid &room, PsiAccount *pa, + const char *name) + : GenericBusyDlg(NULL, name, false, psi_dialog_flags | WDestructiveClose) +{ + setCaption(tr("Destroy Room: %1").arg(room.full())); + + d = new Private(room, pa); + + d->pb_destroy = new IconButton(this, "pb_destroy"); + d->pb_destroy->setText(tr("&Destroy")); + d->pb_destroy->setIcon("psi/muc_destroy_room"); + + d->pb_close = new IconButton(this, "pb_close"); + d->pb_close->setText(tr("&Close")); + d->pb_close->setIcon("psi/close"); + d->pb_close->setDefault(true); + + d->destroy_ui = new DestroyRoomUI(this, "destroy_ui"); + + addButton(d->pb_destroy, Right); + addButton(d->pb_close, Right); + setMainWidget(d->destroy_ui); + + d->destroy_ui->le_reason->setFocus(); + + connect(d->pb_destroy, SIGNAL(clicked()), this, SLOT(destroy())); + connect(d->pb_close, SIGNAL(clicked()), this, SLOT(reject())); + connect(d->destroy_ui->cb_recent, SIGNAL(activated(int)), SLOT(recent_activated(int))); + + d->rl = d->psi()->recentGCList(); + UserProfile::GCList::const_iterator it = d->rl.begin(); + for(; it != d->rl.end(); ++it) { + UserProfile::GroupChat gc(*it); + Jid j(gc.jid); + QString s = tr("%1").arg(j.bare()); + d->destroy_ui->cb_recent->insertItem(s); + } + + if(d->rl.isEmpty()) { + d->destroy_ui->cb_recent->setEnabled(false); + } +} + +DestroyRoomDlg::~DestroyRoomDlg() +{ + delete d; +} + +Jid DestroyRoomDlg::venue() const +{ + Jid venue = d->destroy_ui->le_room->text() + '@' + d->destroy_ui->le_host->text(); + return venue; +} + +bool DestroyRoomDlg::isVenueValid() const +{ + QString room(d->destroy_ui->le_room->text()); + QString host(d->destroy_ui->le_host->text()); + + if(Jid::validNode(room) && Jid::validDomain(host)) + return true; + else + return false; +} + +QString DestroyRoomDlg::reason() const +{ + return d->destroy_ui->le_reason->text(); +} + +const Jid &DestroyRoomDlg::room() const +{ + return d->room; +} + +void DestroyRoomDlg::enableWidgets(bool yes) +{ + d->destroy_ui->le_reason->setEnabled(yes); + d->destroy_ui->cb_recent->setEnabled(yes); + d->destroy_ui->le_room->setEnabled(yes); + d->destroy_ui->le_host->setEnabled(yes); + d->pb_destroy->setEnabled(yes); +} + +void DestroyRoomDlg::recent_activated(int x) +{ + int n = 0; + bool found = false; + UserProfile::GroupChat chat; + UserProfile::GCList::const_iterator it = d->rl.begin(); + for(; it != d->rl.end(); ++it) { + if(n == x) { + found = true; + chat = *it; + break; + } + ++n; + } + if(!found) + return; + + Jid j(chat.jid); + d->destroy_ui->le_host->setText(j.host()); + d->destroy_ui->le_room->setText(j.user()); + + d->pb_destroy->setFocus(); +} + +void DestroyRoomDlg::destroy() +{ + QString ven_room(d->destroy_ui->le_room->text()), ven_host(d->destroy_ui->le_host->text()); + bool room_empty = ven_room.isEmpty(); + bool host_empty = ven_host.isEmpty(); + bool ven_valid = isVenueValid(); + + if(room_empty == host_empty && ven_valid) { + d->task = new MT_DestroyRoom(d->client()->rootTask(), room(), + reason(), venue()); + connect(d->task, SIGNAL(finished()), this, SLOT(taskDone())); + + enableWidgets(false); + busyStart(); + + d->task->go(); + } + else { + QString m; + + if(room_empty && !host_empty) { + m = tr("Sorry, but you did not specify a room."); + d->destroy_ui->le_room->setFocus(); + } + else if(host_empty && !room_empty) { + m = tr("Sorry, but you did not specify a host."); + d->destroy_ui->le_host->setFocus(); + } + else { + if(Jid::validNode(ven_room)) { + m = tr("Sorry, but the room is invalid."); + d->destroy_ui->le_room->setFocus(); + } + else { + m = tr("Sorry, but the host is invalid."); + d->destroy_ui->le_host->setFocus(); + } + } + + QMessageBox::information(this, tr("Invalid Venue"), + m, QMessageBox::Ok); + } +} + +void DestroyRoomDlg::taskDone() +{ + busyStop(); + + if(d->task->success()) { + GenericBusyDlg::accept(); + } + else { + QString m; + + // The statusString needs to be better and more information + if(d->task->statusCode() == 403) + m = tr("You're not allowed to destroy '%1'.").arg(d->room.bare()); + else + m = d->task->statusString(); + + QMessageBox::information(this, tr("Error"), + m, QMessageBox::Ok); + + enableWidgets(); + } +} + +void DestroyRoomDlg::reject() +{ + GenericBusyDlg::reject(); +} diff -Naur psi-0.10-orig/src/destroyroomdlg.h psi-0.10/src/destroyroomdlg.h --- psi-0.10-orig/src/destroyroomdlg.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/destroyroomdlg.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,63 @@ +/* $Header$ + * dataform.h - Header file for the room destruction dialog + * Copyright (C) 2004 Nolan Eakins + * + * 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 DESTROYROOMDLG_H +#define DESTROYROOMDLG_H + +#include +#include "genericbusydlg.h" + +namespace XMPP +{ + class Jid; + class Client; +}; + +using XMPP::Jid; +using XMPP::Client; +class PsiAccount; + +class DestroyRoomDlg: public GenericBusyDlg +{ + Q_OBJECT; + +public: + DestroyRoomDlg(const Jid &, PsiAccount *, const char *name = NULL); + virtual ~DestroyRoomDlg(); + + QString reason() const; + Jid venue() const; + bool isVenueValid() const; + const Jid &room() const; + +protected: + void enableWidgets(bool yes = true); + +protected slots: + virtual void recent_activated(int); + virtual void destroy(); + virtual void reject(); + virtual void taskDone(); + +private: + struct Private; + Private *d; +}; + +#endif diff -Naur psi-0.10-orig/src/eventdlg.cpp psi-0.10/src/eventdlg.cpp --- psi-0.10-orig/src/eventdlg.cpp 2005-08-21 17:44:33.000000000 +0000 +++ psi-0.10/src/eventdlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -38,6 +38,7 @@ #include #include #include +#include #include"psievent.h" #include"psicon.h" #include"psiaccount.h" @@ -254,37 +255,80 @@ class AttachViewItem : public QListViewItem { public: - AttachViewItem(const QString &_url, const QString &_desc, AttachView *par) - :QListViewItem(par) + static const int RTTI = 9200; + + AttachViewItem(AttachView *par, const char *icon) + : QListViewItem(par) + { + if(icon) + setPixmap(0, IconsetFactory::icon(icon)); + + setMultiLinesEnabled(true); + } + + int rtti() const + { + return RTTI; + } + + virtual void contextMenuRequested(const QPoint &pos) = 0; + virtual void doubleClicked() = 0; +}; + +class AttachViewUrl: public AttachViewItem +{ +public: + static const int RTTI = 9201; + + AttachViewUrl(const QString &_url, const QString &_desc, AttachView *par) + : AttachViewItem(par, "psi/www") { - type = 0; url = _url; desc = _desc; - setPixmap(0, IconsetFactory::icon("psi/www")); setText(0, url + " (" + desc + ')'); - setMultiLinesEnabled(true); } - AttachViewItem(const QString &_gc, AttachView *par) - :QListViewItem(par) + int rtti() const { - type = 1; - gc = _gc; + return RTTI; + } - setPixmap(0, IconsetFactory::icon("psi/groupChat")); - setText(0, gc); - setMultiLinesEnabled(true); + void contextMenuRequested(const QPoint &pos) + { + QPopupMenu pm(listView()); + pm.insertItem(AttachView::tr("Go to &URL..."), 0); + pm.insertItem(AttachView::tr("Copy location"), 1); + pm.insertSeparator(); + pm.insertItem(AttachView::tr("Remove"), 2); + + AttachView *lv = (AttachView *)listView(); + if(lv->readOnly()) + pm.setItemEnabled(2, false); + + switch(pm.exec(pos)) { + case 0: + lv->goURL(url); + break; + case 1: + QApplication::clipboard()->setText(url, QClipboard::Clipboard); + if(QApplication::clipboard()->supportsSelection()) + QApplication::clipboard()->setText(url, QClipboard::Selection); + break; + case 2: + delete this; + lv->emitChildCountChanged(); // this signal didn't get emitted for some reason + break; + } } - int rtti() const + void doubleClicked() { - return 9100; + AttachView *lv = (AttachView *)listView(); + lv->goURL(url); } QString url, desc; - QString gc; - int type; }; AttachView::AttachView(QWidget *parent, const char *name) @@ -309,66 +353,21 @@ void AttachView::urlAdd(const QString &url, const QString &desc) { - new AttachViewItem(url, desc, this); - childCountChanged(); -} - -void AttachView::gcAdd(const QString &gc) -{ - new AttachViewItem(gc, this); - childCountChanged(); + new AttachViewUrl(url, desc, this); } void AttachView::qlv_context(QListViewItem *lvi, const QPoint &pos, int) { AttachViewItem *i = (AttachViewItem *)lvi; - if(!i) - return; - - QPopupMenu pm(this); - if(i->type == 0) { - pm.insertItem(tr("Go to &URL..."), 0); - pm.insertItem(tr("Copy location"), 1); - } - else - pm.insertItem(tr("Join &Groupchat..."), 0); - pm.insertSeparator(); - pm.insertItem(tr("Remove"), 2); - - if(v_readOnly) - pm.setItemEnabled(2, false); - - int n = pm.exec(pos); - if(n == -1) - return; - - if(n == 0) { - if(i->type == 0) - goURL(i->url); - else - actionGCJoin(i->gc); - } - else if(n == 1) { - QApplication::clipboard()->setText(i->url, QClipboard::Clipboard); - if(QApplication::clipboard()->supportsSelection()) - QApplication::clipboard()->setText(i->url, QClipboard::Selection); - } - else if(n == 2) { - delete i; - childCountChanged(); - } + if(i) + i->contextMenuRequested(pos); } void AttachView::qlv_doubleClicked(QListViewItem *lvi) { AttachViewItem *i = (AttachViewItem *)lvi; - if(!i) - return; - - if(i->type == 0) - goURL(i->url); - else - actionGCJoin(i->gc); + if(i) + i->doubleClicked(); } void AttachView::goURL(const QString &_url) @@ -387,8 +386,12 @@ { UrlList list; - for(AttachViewItem *i = (AttachViewItem *)firstChild(); i; i = (AttachViewItem *)i->nextSibling()) - list += Url(i->url, i->desc); + for(AttachViewItem *i = (AttachViewItem *)firstChild(); i; i = (AttachViewItem *)i->nextSibling()) { + if(i->rtti() == AttachViewUrl::RTTI) { + AttachViewUrl *i_url = (AttachViewUrl *)i; + list += Url(i_url->url, i_url->desc); + } + } return list; } @@ -401,6 +404,27 @@ } } +bool AttachView::readOnly() const +{ + return v_readOnly; +} + +void AttachView::insertItem(QListViewItem *i) +{ + QListView::insertItem(i); + childCountChanged(); +} + +void AttachView::takeItem(QListViewItem *i) +{ + QListView::takeItem(i); + childCountChanged(); +} + +void AttachView::emitChildCountChanged() +{ + emit childCountChanged(); +} //---------------------------------------------------------------------------- // AddUrlDlg @@ -428,7 +452,9 @@ { Q_OBJECT public: - Private(EventDlg *d) { + Private(EventDlg *d) + : declineRequired(false) + { dlg = d; } @@ -436,7 +462,7 @@ PsiCon *psi; PsiAccount *pa; - bool composing; + bool composing, declineRequired; AccountsComboBox *cb_ident; QComboBox *cb_type; @@ -451,6 +477,7 @@ int transid; IconButton *pb_next; IconButton *pb_close, *pb_quote, *pb_deny, *pb_send, *pb_reply, *pb_chat, *pb_auth; + IconButton *pb_join, *pb_decline; // invitation buttons ChatView *mle; AttachView *attachView; QTimer *whois; @@ -496,6 +523,20 @@ void updateCounter() { lcd_count->display((int)mle->text().length()); } + + void sendDecline(const QString &reason) + { + // this must be done before the send since the dialog will be closed by PsiAccount + declineRequired = false; + pa->actionDeclineInvite(m.invitationFrom(), m.from(), reason); + } + + void sendDecline() + { + if(declineRequired) { + sendDecline(QString::null); + } + } }; EventDlg::EventDlg(const QString &to, PsiCon *psi, PsiAccount *pa) @@ -772,7 +813,6 @@ d->attachView->setFixedHeight(80); d->attachView->hide(); connect(d->attachView, SIGNAL(childCountChanged()), SLOT(showHideAttachView())); - connect(d->attachView, SIGNAL(actionGCJoin(const QString &)), SLOT(actionGCJoin(const QString &))); vb1->addWidget(d->attachView); if(!d->composing) @@ -830,6 +870,19 @@ d->pb_reply->hide(); d->pb_reply->setMinimumWidth(96); hb4->addWidget(d->pb_reply); + d->pb_decline = new IconButton(this); + d->pb_decline->setText(tr("&Decline")); + connect(d->pb_decline, SIGNAL(clicked()), SLOT(doDecline())); + d->pb_decline->hide(); + d->pb_decline->setMinimumWidth(96); + hb4->addWidget(d->pb_decline); + d->pb_join = new IconButton(this); + d->pb_join->setText(tr("&Join")); + d->pb_join->setIcon(IconsetFactory::iconPtr("psi/groupChat")); + connect(d->pb_join, SIGNAL(clicked()), SLOT(doJoin())); + d->pb_join->hide(); + d->pb_join->setMinimumWidth(96); + hb4->addWidget(d->pb_join); setTabOrder(d->mle, d->pb_send); @@ -1257,6 +1310,9 @@ if(!d->mle->isEnabled()) return; + // decline the invite if we're showing one + d->sendDecline(); + e->accept(); } @@ -1322,6 +1378,9 @@ void EventDlg::doReadNext() { + // decline the invite if we're showing one + d->sendDecline(); + aReadNext(d->realJid); } @@ -1400,6 +1459,32 @@ d->pa->actionInfo(d->jid); } +void EventDlg::doDecline() +{ + if(!d->pa->checkConnected(this)) + return; + + bool ok = false; + QString reason = QInputDialog::getText(tr("Decline Inivitation"), + tr("Why are you declining this invitation?"), + QLineEdit::Normal, QString::null, + &ok); + if(ok) { + d->sendDecline(reason); + } +} + +void EventDlg::doJoin() +{ + if(!d->pa->checkConnected(this)) + return; + + d->pa->actionAcceptInvite(d->m.from(), d->m.invitationPword(), d->m.invitationFrom()); + d->declineRequired = false; + + close(); +} + void EventDlg::closeAfterReply() { if(d->nextAmount == 0) @@ -1500,21 +1585,54 @@ setTime(e->timeStamp(), e->late()); d->enc = false; + d->declineRequired = false; if(e->type() == PsiEvent::Message) { MessageEvent *me = (MessageEvent *)e; const Message &m = me->message(); + // might as well reuse this since actions + // on invites need some state + d->m = m; + d->enc = m.wasEncrypted(); d->pb_auth->hide(); d->pb_deny->hide(); + QString txt; + if(m.invitationFrom().isEmpty()) { + // regular message d->pb_chat->show(); d->pb_reply->show(); d->pb_quote->show(); + d->pb_join->hide(); + d->pb_decline->hide(); - QString txt = plain2rich(m.body()); + txt = plain2rich(m.body()); + + d->le_subj->setText(m.subject()); + } + else { + // invitation to a groupchat + d->declineRequired = true; + + d->pb_chat->hide(); + d->pb_reply->hide(); + d->pb_quote->hide(); + d->pb_join->show(); + d->pb_decline->show(); + + QString room = m.from().bare(); + QString temp = tr("%1 is inviting you to %2.\n").arg(m.invitationFrom().bare()).arg(room); + if(!m.invitationReason().isEmpty()) + temp += tr("\nReason: %1").arg(m.invitationReason()); + if(!m.invitationPword().isEmpty()) + temp += tr("\nPassword: %1").arg(m.invitationPword()); + + txt = plain2rich(temp); + d->le_subj->setText(tr("Invitation to %1").arg(room)); + } // show subject line if the incoming message has one if(m.subject() != "" && !option.showSubjects) @@ -1528,16 +1646,12 @@ d->mle->setText("" + linkify(txt) + ""); - d->le_subj->setText(m.subject()); - d->le_subj->setCursorPosition(0); - d->thread = m.thread(); + d->le_subj->setCursorPosition(0); d->attachView->clear(); d->attachView->addUrlList(m.urlList()); - if(!m.invite().isEmpty()) - d->attachView->gcAdd(m.invite()); showHideAttachView(); } else if(e->type() == PsiEvent::Auth) { @@ -1554,6 +1668,8 @@ d->pb_chat->show(); d->pb_reply->hide(); d->pb_quote->hide(); + d->pb_join->hide(); + d->pb_decline->hide(); d->pb_auth->setEnabled(true); d->pb_auth->show(); @@ -1567,6 +1683,8 @@ d->pb_auth->hide(); d->pb_deny->hide(); + d->pb_join->hide(); + d->pb_decline->hide(); d->pb_chat->show(); d->pb_reply->show(); d->pb_quote->show(); @@ -1579,6 +1697,8 @@ d->pb_auth->hide(); d->pb_deny->hide(); + d->pb_join->hide(); + d->pb_decline->hide(); d->pb_chat->show(); d->pb_reply->show(); d->pb_quote->show(); @@ -1627,12 +1747,6 @@ } } -void EventDlg::actionGCJoin(const QString &gc) -{ - Jid j(gc); - d->pa->actionJoin(j.withResource("")); -} - void EventDlg::updatePGP() { if(d->tb_pgp) { diff -Naur psi-0.10-orig/src/eventdlg.h psi-0.10/src/eventdlg.h --- psi-0.10-orig/src/eventdlg.h 2005-08-21 17:44:33.000000000 +0000 +++ psi-0.10/src/eventdlg.h 2006-01-14 00:43:21.000000000 +0000 @@ -74,14 +74,21 @@ void setReadOnly(bool); void urlAdd(const QString &, const QString &); - void gcAdd(const QString &); UrlList urlList() const; void addUrlList(const UrlList &); + void goURL(const QString &); + + bool readOnly() const; + + virtual void insertItem(QListViewItem *); + virtual void takeItem(QListViewItem *); + + void emitChildCountChanged(); + signals: void childCountChanged(); - void actionGCJoin(const QString &); private slots: void qlv_context(QListViewItem *, const QPoint &, int); @@ -89,8 +96,6 @@ private: bool v_readOnly; - - void goURL(const QString &); }; class AddUrlDlg : public AddUrlUI @@ -141,7 +146,6 @@ void updateContact(const Jid &); void updateEvent(PsiEvent *); void updateReadNext(Icon *, int); - void actionGCJoin(const QString &); private slots: void to_textChanged(const QString &); @@ -159,6 +163,8 @@ void doAuth(); void doInfo(); void doHistory(); + void doDecline(); + void doJoin(); void showHideAttachView(); void addUrl(); diff -Naur psi-0.10-orig/src/genericbusydlg.cpp psi-0.10/src/genericbusydlg.cpp --- psi-0.10-orig/src/genericbusydlg.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/genericbusydlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,196 @@ +/* $Header$ + * genericbusydlg.h - header for a generic dialog that has a busy widget + * Copyright (C) 2004 Nolan Eakins + * + * 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 "genericbusydlg.h" +#include "busywidget.h" + +class GenericBusyDlg::Private +{ +public: + Private(GenericBusyDlg *p) + : parent(p), main_widget(NULL) + { + layout = new QBoxLayout(p, QBoxLayout::Up, 11, 6, "layout"); + + // the button area + b_layout = new QHBoxLayout(layout, 6, "b_layout"); + + busy = new BusyWidget(p, "busy"); + b_layout->addWidget(busy); + + b_left = new QHBoxLayout(b_layout, 6, "b_left"); + + b_spacer = new QSpacerItem(32, 16, QSizePolicy::Expanding, QSizePolicy::Minimum); + b_layout->addItem(b_spacer); + + b_right = new QHBoxLayout(b_layout, 6, "b_right"); + + // horizontal seperator line + horiz_line = new QFrame(p, "horiz_line"); + horiz_line->setFrameShape( QFrame::HLine ); + horiz_line->setFrameShadow( QFrame::Sunken ); + horiz_line->setFrameShape( QFrame::HLine ); + layout->addWidget(horiz_line); + + //v_spacer = new QSpacerItem(16, 32, QSizePolicy::Minimum, QSizePolicy::Expanding); + //layout->addItem(v_spacer); + } + + // no destructor since Qt does the clean up + + GenericBusyDlg *parent; + QWidget *main_widget; + BusyWidget *busy; + QFrame *horiz_line; + QSpacerItem *b_spacer; + QHBoxLayout *b_layout, *b_left, *b_right; + QBoxLayout *layout; + + // these lists are used to create the tab order + QPtrList left_buttons, right_buttons; + + void setTabOrder() + { + /* The tab order goes as follows: + * main widget + * left buttons - left to right + * right buttons - left to right + */ + QPtrListIterator left_it(left_buttons); + QPtrListIterator right_it(right_buttons); + + if(main_widget && left_it.current() != NULL) { + QWidget::setTabOrder(main_widget, left_it.current()); + } + else if(main_widget && right_it.current() != NULL) { + QWidget::setTabOrder(main_widget, right_it.current()); + } + + if(left_it.current() != NULL) { + QPtrListIterator next_it(left_it); + ++next_it; + while(next_it.current() != NULL) { + QWidget::setTabOrder(left_it.current(), next_it.current()); + ++left_it; + ++next_it; + } + } + + if(right_it.current() != NULL) { + if(left_it.current() != NULL) + QWidget::setTabOrder(left_it.current(), right_it.current()); + + QPtrListIterator next_it(right_it); + ++next_it; + while(next_it.current() != NULL) { + QWidget::setTabOrder(right_it.current(), next_it.current()); + ++right_it; + ++next_it; + } + } + } +}; + +GenericBusyDlg::GenericBusyDlg(QWidget *parent, const char *name, + bool modal, WFlags f) + : QDialog(parent, name, modal, f) +{ + d = new Private(this); +} + +GenericBusyDlg::GenericBusyDlg(const GenericBusyDlg &) + : QDialog() +{ + // do nothing since this is private + // if it goes public, 'd' needs to be copied +} + +GenericBusyDlg::~GenericBusyDlg() +{ + delete d; +} + +void GenericBusyDlg::addButton(QWidget *button, ButtonAlign side) +{ + if(side == Left) { + d->b_left->addWidget(button); + d->left_buttons.append(button); + } + else { + d->b_right->addWidget(button); + d->right_buttons.append(button); + } + + d->setTabOrder(); +} + +QWidget *GenericBusyDlg::mainWidget() const +{ + return d->main_widget; +} + +void GenericBusyDlg::setMainWidget(QWidget *w) +{ + if(d->main_widget != NULL) { + d->layout->remove(d->main_widget); + } + + d->main_widget = w; + d->layout->addWidget(w); + + d->setTabOrder(); +} + +void GenericBusyDlg::accept() +{ + QDialog::accept(); + + done(true); + accepted(); +} + +void GenericBusyDlg::reject() +{ + QDialog::reject(); + + done(false); + rejected(); +} + +bool GenericBusyDlg::isBusy() const +{ + return d->busy->isActive(); +} + +void GenericBusyDlg::busyStart() +{ + busy(true); +} + +void GenericBusyDlg::busyStop() +{ + busy(false); +} + +void GenericBusyDlg::busy(bool yes) +{ + d->busy->setActive(yes); +} diff -Naur psi-0.10-orig/src/genericbusydlg.h psi-0.10/src/genericbusydlg.h --- psi-0.10-orig/src/genericbusydlg.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/genericbusydlg.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,109 @@ +/* $Header$ + * genericbusydlg.h - header for a generic dialog that has a busy widget + * Copyright (C) 2004 Nolan Eakins + * + * 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 GENERICBUSYDLG_H +#define GENERICBUSYDLG_H + +#include + +/** GenericBusyDlg is an empty dialog with a busy widget. + * Buttons can be added to it along with a main widget. + * Typical usage would be to subclass GenericBusyDlg. The + * subclass' constructor would create and add buttons along + * with creating and setting the main widget. + * + * If the subclass overrides accept() and/or reject(), + * be sure to call GenericBusyDlg::accept() or + * GenericBusyDlg::reject() to ensure that the proper + * signals are emitted. + * + * \sa destroyroomdlg.cpp dataformdlg.cpp + */ +class GenericBusyDlg: public QDialog +{ + Q_OBJECT; + +public: + enum ButtonAlign { Left, Right }; + + GenericBusyDlg(QWidget *parent = NULL, const char *name = NULL, + bool modal = false, WFlags f = 0); + virtual ~GenericBusyDlg(); + + /** Adds a button to the bottom of the dialog. + * Buttons are added left to right, so the last button that + * is added to one side will be the farthest to the right. + * @param button The button or widget to add. + * @param side The side that the button will be on. + */ + void addButton(QWidget *button, ButtonAlign side = Right); + + /** Returns the main widget. + */ + QWidget *mainWidget() const; + /** Sets the main widget. + */ + void setMainWidget(QWidget *); + + /** Accepts the dialog. + * If this is overriden, be sure to call it so that the all + * the signals get emitted. + */ + virtual void accept(); + /** Rejects the dialog. + * If this is overriden, be sure to call it so that the all + * the signals get emitted. + */ + virtual void reject(); + + /** Returns true if the busy widget has been started. + */ + bool isBusy() const; + +public slots: + /** Starts the busy widget. + */ + void busyStart(); + /** Stops the busy widget. + */ + void busyStop(); + /** Starts or stops the busy widget. + * @param yes If true the busy widget is started, otherwise it's stopped. + */ + void busy(bool); + +signals: + /** Emitted when the dialog is accepted or rejected. + * @param accepted True if the dialog was accepted. + */ + void done(bool); + /** Emitted when the dialog is accepted. + */ + void accepted(); + /** Emitted when the dialog is rejected. + */ + void rejected(); + +private: + GenericBusyDlg(const GenericBusyDlg &); + + class Private; + Private *d; +}; + +#endif /* GENERICBUSYDLG_H */ diff -Naur psi-0.10-orig/src/groupchatdlg.cpp psi-0.10/src/groupchatdlg.cpp --- psi-0.10-orig/src/groupchatdlg.cpp 2006-01-07 21:19:01.000000000 +0000 +++ psi-0.10/src/groupchatdlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -17,9 +17,9 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ - #include"groupchatdlg.h" +#include #include #include #include @@ -35,10 +35,12 @@ #include #include #include -#include +#include +#include #include"psicon.h" #include"psiaccount.h" #include"userlist.h" +#include #include"statusdlg.h" #include"busywidget.h" #include"common.h" @@ -47,11 +49,21 @@ #include"iconselect.h" #include"psitoolbar.h" #include"iconaction.h" +#include"xmpp_xdata.h" +#include"roomcfgdlg.h" +#include"muc.h" +#include"muc_tasks.h" +#include"reasons.h" +#include"destroyroomdlg.h" +#include"muc_affileditordlg.h" +#include"psilistview.h" +#include"jiddrag.h" #ifdef Q_WS_WIN #include #endif +using std::for_each; //---------------------------------------------------------------------------- // GCJoinDlg @@ -65,8 +77,9 @@ PsiAccount *pa; AccountsComboBox *cb_ident; BusyWidget *busy; - QStringList rl; - Jid jid; + BookmarkList bookmarks; + Jid jid, inviteFrom; + bool isJoining; }; GCJoinDlg::GCJoinDlg(PsiCon *psi, PsiAccount *pa) @@ -77,32 +90,36 @@ d->pa = 0; d->psi->dialogRegister(this); d->busy = busy; + d->isJoining = false; updateIdentity(pa); + // NOTE: Why must the combo box be created in PsiCon? + // If I could just do "new AccountsComboBox(this, true) + // then the whole replaceWidget hack would be unneeded, + // and would work in a QGridLayout, instead of having to + // resort to an extra QHBoxLayout in the UI file. -- nolan d->cb_ident = d->psi->accountsComboBox(this,true); connect(d->cb_ident, SIGNAL(activated(PsiAccount *)), SLOT(updateIdentity(PsiAccount *))); + + // this will implicitly call to request bookmarks + // because the above signal will go off d->cb_ident->setAccount(pa); - replaceWidget(lb_ident, d->cb_ident); + replaceWidget(lb_ident_placeholder, d->cb_ident); - d->rl = d->psi->recentGCList(); - for(QStringList::ConstIterator it = d->rl.begin(); it != d->rl.end(); ++it) { - Jid j(*it); - QString s = tr("%1 on %2").arg(j.resource()).arg(j.userHost()); - cb_recent->insertItem(s); - } + lb_ident->setBuddy(d->cb_ident); setCaption(CAP(caption())); pb_join->setDefault(true); + connect(pb_close, SIGNAL(clicked()), SLOT(close())); connect(pb_join, SIGNAL(clicked()), SLOT(doJoin())); - connect(cb_recent, SIGNAL(activated(int)), SLOT(recent_activated(int))); - if(d->rl.isEmpty()) { - cb_recent->setEnabled(false); - le_host->setFocus(); - } - else - recent_activated(0); + connect(cb_bookmarks, SIGNAL(activated(int)), SLOT(bookmarks_activated(int))); + connect(cb_remember, SIGNAL(activated(int)), SLOT(rememberActivated(int))); + + le_host->setFocus(); + + setRemember(GC_Remember); } GCJoinDlg::~GCJoinDlg() @@ -114,11 +131,19 @@ delete d; } -/*void GCJoinDlg::closeEvent(QCloseEvent *e) +void GCJoinDlg::closeEvent(QCloseEvent *e) { - e->ignore(); - reject(); -}*/ + if(isInvite()) { + Jid r = room() + "@" + host(); + d->pa->actionDeclineInvite(inviteFrom(), r, QString::null); + } + + // Were these needed at some point, if so why? + //e->ignore(); + //reject(); + + GCJoinUI::closeEvent(e); +} void GCJoinDlg::done(int r) { @@ -133,8 +158,9 @@ void GCJoinDlg::updateIdentity(PsiAccount *pa) { - if(d->pa) - disconnect(d->pa, SIGNAL(disconnected()), this, SLOT(pa_disconnected())); + if(d->pa) { + disconnect(d->pa, NULL, this, NULL); + } d->pa = pa; pb_join->setEnabled(d->pa); @@ -145,6 +171,7 @@ } connect(d->pa, SIGNAL(disconnected()), this, SLOT(pa_disconnected())); + requestBookmarks(); } void GCJoinDlg::pa_disconnected() @@ -154,26 +181,49 @@ } } -void GCJoinDlg::recent_activated(int x) +void GCJoinDlg::bookmarks_activated(int x) { int n = 0; - bool found = false; - QString str; - for(QStringList::ConstIterator it = d->rl.begin(); it != d->rl.end(); ++it) { - if(n == x) { - found = true; - str = *it; - break; + GCBookmark *the_mark = NULL; + + for(BookmarkList::iterator it = d->bookmarks.begin(); + it != d->bookmarks.end(); + ++it) { + Bookmark *bm = *it; + if(bm->rtti() == GCBookmark::RTTI) { + if(x == n) { + the_mark = (GCBookmark *)bm; + break; + } + + n++; } - ++n; } - if(!found) - return; - Jid j(str); - le_host->setText(j.host()); - le_room->setText(j.user()); - le_nick->setText(j.resource()); + if(the_mark != NULL) { + Jid j(the_mark->jid()); + le_host->setText(j.host()); + le_room->setText(j.user()); + le_nick->setText(the_mark->nick()); + le_password->setText(the_mark->password()); + + bool save_pass = the_mark->autojoin() == true || + the_mark->password().isEmpty() == false; + ck_savePword->setChecked(save_pass); + + GCBookmarkState remember = GC_Remember; + if(the_mark->autojoin()) + remember = GC_Autojoin; + setRemember(remember); + + enableWidgets(true); + } +} + +void GCJoinDlg::rememberActivated(int index) +{ + // disable the save checkbox when we want to forget + ck_savePword->setEnabled((GCBookmarkState)index == GC_Remember); } void GCJoinDlg::doJoin() @@ -184,20 +234,38 @@ QString host = le_host->text(); QString room = le_room->text(); QString nick = le_nick->text(); + QString pass = le_password->text(); if(host.isEmpty() || room.isEmpty() || nick.isEmpty()) { QMessageBox::information(this, tr("Error"), tr("You must fill out the fields in order to join.")); return; } - Jid j = room + '@' + host + '/' + nick; - if(!j.isValid()) { + Jid jid = room + '@' + host; + if(!jid.isValid()) { QMessageBox::information(this, tr("Error"), tr("You entered an invalid room name.")); return; } + if(!Jid::validResource(nick)) { + QMessageBox::information(this, tr("Error"), tr("You entered an invalid nick name.")); + return; + } + + if(!d->pa->groupChatJoin(host, room, nick, pass)) { + // see if there's a window open already + QWidget *w = d->pa->dialogFind("GCMainDlg", jid.bare()); + + // if there is, then raise it + if(w != NULL) { + w->raise(); + // and close the join dialog + close(); + } + // else tell the user something informative + else { + QMessageBox::information(this, tr("Error"), tr("You are in or joining this room already!")); + } - if(!d->pa->groupChatJoin(host, room, nick)) { - QMessageBox::information(this, tr("Error"), tr("You are in or joining this room already!")); return; } @@ -205,110 +273,412 @@ d->jid = room + '@' + host + '/' + nick; d->pa->dialogRegister(this, d->jid); + // a bookmark received signal could go off during the join, + // enabling the widgets + d->isJoining = true; + disableWidgets(); + d->busy->start(); } void GCJoinDlg::disableWidgets() { - d->cb_ident->setEnabled(false); - cb_recent->setEnabled(false); - gb_info->setEnabled(false); - pb_join->setEnabled(false); + enableWidgets(false); } -void GCJoinDlg::enableWidgets() -{ - d->cb_ident->setEnabled(true); - if(!d->rl.isEmpty()) - cb_recent->setEnabled(true); - gb_info->setEnabled(true); - pb_join->setEnabled(true); +void GCJoinDlg::enableWidgets(bool yes) +{ + d->cb_ident->setEnabled(yes); + // only enable if there is anything in the list + cb_bookmarks->setEnabled(yes && !d->bookmarks.isEmpty()); + gb_info->setEnabled(yes); + pb_join->setEnabled(yes); + // only enable if we want to remember + ck_savePword->setEnabled(yes && remember() == GC_Remember); + cb_remember->setEnabled(yes); } void GCJoinDlg::joined() { - d->psi->recentGCAdd(d->jid.full()); d->busy->stop(); + d->isJoining = false; + + // update the bookmark + updateBookmarks(); closeDialogs(this); deleteLater(); } -void GCJoinDlg::error(int, const QString &str) +void GCJoinDlg::error(int code, const QString &) { d->busy->stop(); enableWidgets(); + d->isJoining = false; pb_join->setFocus(); d->pa->dialogUnregister(this); d->psi->dialogRegister(this); - QMessageBox::information(this, tr("Error"), tr("Unable to join groupchat.\nReason: %1").arg(str)); + QMessageBox::information(this, tr("Error"), + tr("Unable to join groupchat.\nReason: %1").arg(Client::groupChatErrorToString(code))); +} + +void GCJoinDlg::setHost(const QString &h) +{ + le_host->setText(h); +} + +void GCJoinDlg::setRoom(const QString &r) +{ + le_room->setText(r); +} + +void GCJoinDlg::setNick(const QString &n) +{ + le_nick->setText(n); +} + +void GCJoinDlg::setPassword(const QString &p) +{ + le_password->setText(p); +} + +void GCJoinDlg::setInvite(const Jid &from) +{ + d->inviteFrom = from; +} + +void GCJoinDlg::setSavePassword(bool yes) +{ + ck_savePword->setChecked(yes); +} + +void GCJoinDlg::setRemember(GCBookmarkState r) +{ + cb_remember->setCurrentItem((int)r); + rememberActivated((int)r); // I wish Qt would signal it +} + +QString GCJoinDlg::host() const +{ + return le_host->text(); +} + +QString GCJoinDlg::room() const +{ + return le_room->text(); +} + +QString GCJoinDlg::nick() const +{ + return le_nick->text(); +} + +QString GCJoinDlg::password() const +{ + return le_password->text(); +} + +bool GCJoinDlg::savePassword() const +{ + return ck_savePword->isChecked(); +} + +GCBookmarkState GCJoinDlg::remember() const +{ + return (GCBookmarkState)cb_remember->currentItem(); +} + +bool GCJoinDlg::isInvite() const +{ + return !d->inviteFrom.isEmpty(); +} + +const Jid &GCJoinDlg::inviteFrom() const +{ + return d->inviteFrom; +} + +void GCJoinDlg::updateBookmarks() +{ + Jid bareJid(d->jid.userHost()); + QString nick = le_nick->text(); + QString pass = le_password->text(); + + GCBookmark *bookmark = d->pa->findGCBookmark(bareJid); + if(bookmark) { + if(remember() == GC_Forget) { + // delete it if it needs to be forgotten + d->pa->deleteGCBookmark(bareJid); + } + else { + // otherwise update it + QString pass = QString::null; + if(savePassword()) + pass = password(); + + bookmark->setNick(nick); + bookmark->setPassword(pass); + bookmark->setAutojoin(remember() == GC_Autojoin); + } + + // store the bookmarks + d->pa->pushBookmarks(); + } + else { + // create it + QString pass = QString::null; + if(savePassword()) + pass = password(); + + d->pa->addGCBookmark(bareJid.bare(), bareJid, nick, + remember() == GC_Autojoin, + pass); + + // store the bookmarks + d->pa->pushBookmarks(); + } +} + +void GCJoinDlg::requestBookmarks() +{ + if(!d->pa) + return; + + cb_bookmarks->setEnabled(false); + d->busy->start(); + + connect(d->pa, SIGNAL(bookmarksReceived()), this, SLOT(bookmarksReceived())); + d->pa->requestBookmarks(); } +void GCJoinDlg::bookmarksReceived() +{ + d->bookmarks = d->pa->bookmarks(); + + cb_bookmarks->clear(); + + for(BookmarkList::iterator it = d->bookmarks.begin(); + it != d->bookmarks.end(); + ++it) { + Bookmark *bm = *it; + if(bm->rtti() == GCBookmark::RTTI) { + cb_bookmarks->insertItem(bm->name()); + } + } + + if(!d->isJoining) { + enableWidgets(true); + bookmarks_activated(0); + } + + if(!d->isJoining) + d->busy->stop(); +} //---------------------------------------------------------------------------- // GCUserView //---------------------------------------------------------------------------- +class GCUserView::Private +{ +public: + MUC::RoleMap role_items; + Jid room; +}; + +const int GCUserViewItem::RTTI = PsiListViewItem::RTTI_BASE + 1; + GCUserViewItem::GCUserViewItem(QListView *par) -:QListViewItem(par) + : PsiListViewItem(par) +{ + setDragEnabled(true); +} + +GCUserViewItem::GCUserViewItem(QListViewItem *par) + : PsiListViewItem(par) +{ + setDragEnabled(true); +} + +const Jid &GCUserViewItem::jid() const +{ + return s.mucUserJid(); +} + +int GCUserViewItem::compare(QListViewItem *lvi, int col, bool ascending) const +{ + if(lvi->rtti() != rtti()) + // don't compare this with list items of a different type + return PsiListViewItem::compare(lvi, col, ascending); + else { + GCUserViewItem *i = (GCUserViewItem *)lvi; + int ret; + + if(option.rosterContactSortStyle == Options::ContactSortStyle_Status) { + ret = rankStatus(s) - rankStatus(i->s); + if(ret == 0) + ret = text(col).lower().localeAwareCompare(i->text(col).lower()); + } + else { + ret = text(col).lower().localeAwareCompare(i->text(col).lower()); + } + + return ret; + } +} + +void GCUserViewItem::paintCell(QPainter *p, const QColorGroup &cg, + int column, int width, int align) +{ + QColorGroup my_cg(cg); + + switch(makeSTATUS(s)) { + case STATUS_AWAY: + case STATUS_XA: + my_cg.setColor(QColorGroup::Text, option.color[cAway]); + break; + case STATUS_DND: + my_cg.setColor(QColorGroup::Text, option.color[cDND]); + break; + case STATUS_ONLINE: + case STATUS_CHAT: + my_cg.setColor(QColorGroup::Text, option.color[cOnline]); + break; + default: + my_cg.setColor(QColorGroup::Text, option.color[cOffline]); + } + + PsiListViewItem::paintCell(p, my_cg, column, width, align); +} + +void GCUserViewItem::optionsUpdate() { + setPixmap(0, is->status(s)); } -GCUserView::GCUserView(QWidget *parent, const char *name) -:QListView(parent, name), QToolTip(viewport()) +GCUserView::GCUserView(QWidget *parent, const Jid &room, const char *name) + : PsiListView(parent, name), QToolTip(viewport()) { + d = new Private; + d->room = room; + + setSelectionMode(Extended); + setTreeStepSize(4); setResizeMode(QListView::AllColumns); setSorting(0); header()->hide(); addColumn(""); - connect(this, SIGNAL(doubleClicked(QListViewItem *)), SLOT(qlv_doubleClicked(QListViewItem *))); - connect(this, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)), SLOT(qlv_contextMenuRequested(QListViewItem *, const QPoint &, int))); + addRoles(); } GCUserView::~GCUserView() { + MUC::RoleMap::iterator it, end = d->role_items.end(); + for(it = d->role_items.begin(); it != end; ++it) + delete it.data(); + + delete d; } -void GCUserView::updateAll() +QStringList GCUserView::nickList() const { - for(GCUserViewItem *i = (GCUserViewItem *)firstChild(); i; i = (GCUserViewItem *)i->nextSibling()) - i->setPixmap(0, is->status(i->s)); + QStringList list; + + for(QListViewItem *i = firstChild(); i; i = i->nextSibling()) { + for(QListViewItem *j = i->firstChild(); j; j = j->nextSibling()) + list << j->text(0); + } + + qstringlistisort(list); // caseless sorting + return list; } -QStringList GCUserView::nickList() const +QStringList GCUserView::selectedNickList() const { QStringList list; - for(QListViewItem *lvi = firstChild(); lvi; lvi = lvi->nextSibling()) - list << lvi->text(0); + for(QListViewItem *i = firstChild(); i; i = i->nextSibling()) { + for(QListViewItem *j = i->firstChild(); j; j = j->nextSibling()) + // if the role item is selected, then all the + // children are considered selected + if(j->isSelected() || i->isSelected()) + list << j->text(0); + } qstringlistisort(list); // caseless sorting return list; } +QValueList GCUserView::selectedJidList() const +{ + QValueList list; + Jid jid; + + for(QListViewItem *i = firstChild(); i; i = i->nextSibling()) { + for(QListViewItem *j = i->firstChild(); j; j = j->nextSibling()) + // if the role item is selected, then all the + // children are considered selected + if(j->isSelected() || i->isSelected()) { + if(j->rtti() == GCUserViewItem::RTTI) { + GCUserViewItem *item = (GCUserViewItem *)j; + jid = item->jid(); + + // if there isn't a Jid then we construct + // the occupant's room Jid + if(jid.isEmpty()) { + jid = d->room; + jid.setResource(item->text(0)); + } + + list << jid; + } + } + } + + return list; +} + QListViewItem *GCUserView::findEntry(const QString &nick) { - for(QListViewItem *lvi = firstChild(); lvi; lvi = lvi->nextSibling()) { - if(lvi->text(0) == nick) - return lvi; + // go each role item + for(QListViewItem *i = firstChild(); i; i = i->nextSibling()) { + // go through their children + for(QListViewItem *j = i->firstChild(); j; j = j->nextSibling()) { + if(j->text(0) == nick) + return j; + } } - return 0; + return NULL; } void GCUserView::updateEntry(const QString &nick, const Status &s) { GCUserViewItem *lvi = (GCUserViewItem *)findEntry(nick); - if(!lvi) { - lvi = new GCUserViewItem(this); + if(!lvi && + s.mucUserRole() > MUC::RoleNone) { + QListViewItem *par = roleItem(s.mucUserRole()); + Q_ASSERT(par != NULL); // being safe + lvi = new GCUserViewItem(par); lvi->setText(0, nick); } + else { + if(lvi->s.mucUserRole() != s.mucUserRole() && + s.mucUserRole() > MUC::RoleNone) { + QListViewItem *par = lvi->parent(); + par->takeItem(lvi); + par = roleItem(s.mucUserRole()); + Q_ASSERT(par != NULL); // being safe + par->insertItem(lvi); + } + } lvi->s = s; lvi->setPixmap(0, is->status(lvi->s)); + sort(); } void GCUserView::removeEntry(const QString &nick) @@ -320,10 +690,12 @@ void GCUserView::maybeTip(const QPoint &pos) { - GCUserViewItem *lvi = (GCUserViewItem *)itemAt(pos); - if(!lvi) + QListViewItem *item = itemAt(pos); + if(!item || item->rtti() != GCUserViewItem::RTTI) return; + GCUserViewItem *lvi = (GCUserViewItem *)item; + QRect r(itemRect(lvi)); const QString &nick = lvi->text(0); @@ -342,40 +714,65 @@ tip(r, u.makeTip()); } -void GCUserView::qlv_doubleClicked(QListViewItem *i) +QListViewItem *GCUserView::roleItem(MUC::Role r) { - GCUserViewItem *lvi = (GCUserViewItem *)i; - if(!lvi) - return; + return d->role_items[r]; +} - if(option.defaultAction == 0) - action(lvi->text(0), lvi->s, 0); - else - action(lvi->text(0), lvi->s, 1); +void GCUserView::clear() +{ + PsiListView::clear(); + addRoles(); } -void GCUserView::qlv_contextMenuRequested(QListViewItem *i, const QPoint &pos, int) +void GCUserView::resizeEvent(QResizeEvent *e) { - GCUserViewItem *lvi = (GCUserViewItem *)i; - if(!lvi) - return; + PsiListView::resizeEvent(e); - QPopupMenu *pm = new QPopupMenu; - pm->insertItem(IconsetFactory::icon("psi/sendMessage"), tr("Send &message"), 0); - pm->insertItem(IconsetFactory::icon("psi/start-chat"), tr("Open &chat window"), 1); - pm->insertSeparator(); - //pm->insertItem(tr("Send &file"), 4); - //pm->insertSeparator(); - pm->insertItem(tr("Check &Status"), 2); - pm->insertItem(IconsetFactory::icon("psi/vCard"), tr("User &Info"), 3); - int x = pm->exec(pos); - delete pm; + if(option.keepSizes) + option.sizeOccList = e->size().width(); +} - if(x == -1) - return; - action(lvi->text(0), lvi->s, x); +void GCUserView::addRoles() +{ + // set each item's pointer to null + d->role_items.clear(); + + for(MUC::Role role = MUC::RoleModerator; role > MUC::RoleNone; role--) { + d->role_items[role] = new PsiListViewGroup(this, role.toString()); + d->role_items[role]->setDragEnabled(true); + d->role_items[role]->setOpen(true); + } +} + +Jid GCUserView::room() const +{ + return d->room; } +class GCUserViewDragFunk +{ +public: + JidDrag::List jids; + + GCUserViewDragFunk() + { + } + + void operator () (const Jid &jid) + { + jids << jid; + } +}; + +QDragObject *GCUserView::dragObject() +{ + QValueList jids(selectedJidList()); + + GCUserViewDragFunk funk(std::for_each(jids.begin(), jids.end(), GCUserViewDragFunk())); + + return new JidDrag(funk.jids, this); +} //---------------------------------------------------------------------------- // GCMainDlg @@ -389,13 +786,18 @@ dlg = d; nickSeparator = ":"; typingStatus = Typing_Normal; + roomCfgDlg = NULL; + destroyRoomDlg = NULL; + affilEditorDlg = NULL; + roomLocked = false; } GCMainDlg *dlg; int state; PsiAccount *pa; Jid jid; - QString self; + QString self, password; + Status selfStatus; ChatView *te_log; ChatEdit *mle; QLineEdit *le_topic; @@ -403,6 +805,9 @@ QPushButton *pb_topic; PsiToolBar *toolbar; IconAction *act_find, *act_clear, *act_icon; + IconAction *act_ownerConfig, *act_destroyRoom, *act_affilEditor; + IconAction *act_bmForget, *act_bmRemember, *act_bmAutojoin; + QActionGroup *act_bmGroup; QPopupMenu *pm_settings; bool smallChat; int pending; @@ -419,6 +824,11 @@ QGuardedPtr findDlg; QString lastSearch; + bool roomLocked; + RoomCfgDlg *roomCfgDlg; + DestroyRoomDlg *destroyRoomDlg; + AffilEditorDlg *affilEditorDlg; + public slots: void addEmoticon(const Icon *icon) { if ( !dlg->isActiveWindow() ) @@ -661,16 +1071,64 @@ return QObject::eventFilter( obj, ev ); } + + GCUserViewItem *us() { + return (GCUserViewItem *)lv_users->findEntry(self); + } + + void mucInfoBox(const QString &title, const QString &text, + const Jid &actor, const QString &reason) { + QString m = text; + + if(!actor.isEmpty()) + m += tr(" by %1").arg(actor.full()); + m += tr("."); + + if(!reason.isEmpty()) + m += tr("\nReason: %1").arg(reason); + + QMessageBox::information(dlg, title, m, QMessageBox::Ok); + } + + /** Changes the role or affiliation of a list of occupants. + * @param nick Occupant's nickname + * @param reason Reason to give the occupant + * @param t Either a MUC::Role or MUC::Affiliation + */ + template + void changeOccupants(const List &list, const QString &reason, const Datum &datum) { + MT_ChangeOccupants *task = new MT_ChangeOccupants(pa->client()->rootTask(), + jid, datum); + connect(task, SIGNAL(finished()), + dlg, SLOT(changeOccupantsFinished())); + task->setItems(list, reason); + task->setList(true); + } + + void initBookmarkState() + { + GCBookmark *mark = pa->findGCBookmark(jid); + if(mark) { + if(mark->autojoin()) + act_bmAutojoin->setOn(true); + else + act_bmRemember->setOn(true); + } + else { + act_bmForget->setOn(true); + } + } }; -GCMainDlg::GCMainDlg(PsiAccount *pa, const Jid &j) -:AdvancedWidget(0, 0, WDestructiveClose) +GCMainDlg::GCMainDlg(PsiAccount *pa, const Jid &j, const QString &pass) + : AdvancedWidget(0, 0, WDestructiveClose) { nicknumber=0; d = new Private(this); d->pa = pa; d->jid = j.userHost(); d->self = j.resource(); + d->password = pass; d->pa->dialogRegister(this, d->jid); connect(d->pa, SIGNAL(updatedActivity()), SLOT(pa_updatedActivity())); @@ -741,73 +1199,107 @@ d->te_log->setFocusPolicy(QWidget::NoFocus); #endif - d->lv_users = new GCUserView(hsp); + d->lv_users = new GCUserView(hsp, jid()); d->lv_users->setMinimumWidth(20); - connect(d->lv_users, SIGNAL(action(const QString &, const Status &, int)), SLOT(lv_action(const QString &, const Status &, int))); + connect(d->lv_users, SIGNAL(doubleClicked(QListViewItem *)), SLOT(lv_doubleClicked(QListViewItem *))); + connect(d->lv_users, SIGNAL(contextMenuRequested(QListViewItem *, const QPoint &, int)), SLOT(lv_contextMenuRequested(QListViewItem *, const QPoint &, int))); // --- bottom part --- QWidget *sp_bottom = new QWidget(vsplit); - sp_bottom->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum ); - if ( option.chatLineEdit ) - dlg_layout->addWidget( sp_bottom ); - QVBoxLayout *vb_bottom = new QVBoxLayout(sp_bottom); - - // toolbar - d->act_clear = new IconAction (tr("Clear chat window"), "psi/clearChat", tr("Clear chat window"), 0, this); - connect( d->act_clear, SIGNAL( activated() ), SLOT( doClearButton() ) ); + sp_bottom->setSizePolicy( QSizePolicy::Expanding, QSizePolicy::Maximum ); + if ( option.chatLineEdit ) + dlg_layout->addWidget( sp_bottom ); + QVBoxLayout *vb_bottom = new QVBoxLayout(sp_bottom); + + // toolbar + d->act_clear = new IconAction (tr("Clear chat window"), "psi/clearChat", tr("Clear chat window"), 0, this); + connect( d->act_clear, SIGNAL( activated() ), SLOT( doClearButton() ) ); connect(pa->psi()->iconSelectPopup(), SIGNAL(textSelected(QString)), d, SLOT(addEmoticon(QString))); - d->act_icon = new IconAction( tr( "Select icon" ), "psi/smile", tr( "Select icon" ), 0, this ); - d->act_icon->setPopup( pa->psi()->iconSelectPopup() ); - - d->toolbar = new PsiToolBar( tr("Groupchat toolbar"), 0, sp_bottom ); - d->toolbar->setCustomizeable( false ); // it isn't ready now, and we don't want segfaults - d->toolbar->setFrameShape( QFrame::NoFrame ); - vb_bottom->addWidget( d->toolbar ); - - d->act_clear->addTo( d->toolbar ); - d->toolbar->setStretchableWidget(new StretchWidget(d->toolbar)); - d->act_icon->addTo( d->toolbar ); + d->act_icon = new IconAction( tr( "Select icon" ), "psi/smile", tr( "Select icon" ), 0, this ); + d->act_icon->setPopup( pa->psi()->iconSelectPopup() ); + + d->toolbar = new PsiToolBar( tr("Groupchat toolbar"), 0, sp_bottom ); + d->toolbar->setCustomizeable( false ); // it isn't ready now, and we don't want segfaults + d->toolbar->setFrameShape( QFrame::NoFrame ); + vb_bottom->addWidget( d->toolbar ); + + d->act_ownerConfig = new IconAction(tr("Configure Room"), "psi/muc_configure_room", tr("Configure Room"), 0, this); + connect(d->act_ownerConfig, SIGNAL(activated()), SLOT(fetchConfigForm())); + + d->act_destroyRoom = new IconAction(tr("Destroy Room"), "psi/muc_destroy_room", tr("Destroy Room"), 0, this); + connect(d->act_destroyRoom, SIGNAL(activated()), SLOT(destroyRoom())); + + d->act_affilEditor = new IconAction(tr("Edit Affiliations"), "psi/muc_affiliation_editor", tr("Edit Affiliations"), 0, this); + connect(d->act_affilEditor, SIGNAL(activated()), SLOT(affilEditor())); + + d->act_bmGroup = new QActionGroup(this); + d->act_bmGroup->setExclusive(true); + connect(d->act_bmGroup, SIGNAL(selected(QAction *)), this, SLOT(bookmarksActivated())); + + d->act_bmForget = new IconAction(tr("Forget"), tr("Forget Conference"), 0, d->act_bmGroup, 0, true); + d->act_bmRemember = new IconAction(tr("Remember"), tr("Remember Conference"), 0, d->act_bmGroup, 0, true); + d->act_bmAutojoin = new IconAction(tr("Autojoin"), tr("Autojoin Conference"), 0, d->act_bmGroup, 0, true); + + d->initBookmarkState(); + + IconAction *bmButton = new IconAction(tr("Bookmark"), "psi/bookmark", tr("Bookmark Conference?"), 0, this); + QPopupMenu *bmMenu = new QPopupMenu(this); + d->act_bmGroup->addTo(bmMenu); + bmButton->setPopup(bmMenu); + + d->act_clear->addTo( d->toolbar ); + d->toolbar->setStretchableWidget(new StretchWidget(d->toolbar)); + d->act_affilEditor->addTo(d->toolbar); + d->act_ownerConfig->addTo(d->toolbar); + d->act_destroyRoom->addTo(d->toolbar); + bmButton->addTo(d->toolbar); + d->toolbar->addSeparator(); + d->act_icon->addTo( d->toolbar ); // chat edit - if ( !option.chatLineEdit ) { - d->mle = new ChatEdit(sp_bottom); - vb_bottom->addWidget(d->mle); - } - else { - QHBoxLayout *hb5 = new QHBoxLayout( dlg_layout ); - d->mle = new LineEdit( vsplit ); + if ( !option.chatLineEdit ) { + d->mle = new ChatEdit(sp_bottom); + vb_bottom->addWidget(d->mle); + } + else { + QHBoxLayout *hb5 = new QHBoxLayout( dlg_layout ); + d->mle = new LineEdit( vsplit ); #ifdef Q_WS_MAC - hb5->addSpacing( 16 ); + hb5->addSpacing( 16 ); #endif - hb5->addWidget( d->mle ); + hb5->addWidget( d->mle ); #ifdef Q_WS_MAC - hb5->addSpacing( 16 ); + hb5->addSpacing( 16 ); #endif - } - + } + d->mle->installEventFilter( d ); - d->pm_settings = new QPopupMenu(this); - connect(d->pm_settings, SIGNAL(aboutToShow()), SLOT(buildMenu())); - + d->pm_settings = new QPopupMenu(this); + connect(d->pm_settings, SIGNAL(aboutToShow()), SLOT(buildMenu())); + // resize the horizontal splitter QValueList list; - list << 500; - list << 80; + list << option.sizeGCDlg.width() - option.sizeOccList; + list << option.sizeOccList; hsp->setSizes(list); list.clear(); - list << 324; + list << option.sizeGCDlg.height() - 10; list << 10; - if ( !option.chatLineEdit ) - (( QSplitter *)vsplit)->setSizes(list); + if ( !option.chatLineEdit ) + (( QSplitter *)vsplit)->setSizes(list); - resize(580,420); + resize(option.sizeGCDlg); + + d->smallChat = option.smallChats; - d->smallChat = option.smallChats; X11WM_CLASS("groupchat"); + d->le_topic->setFocusPolicy(QWidget::NoFocus); + d->te_log->setFocusPolicy(QWidget::NoFocus); + d->lv_users->setFocusPolicy(QWidget::NoFocus); d->mle->setFocus(); setLooks(); @@ -823,6 +1315,21 @@ //d->te_log->setMimeSourceFactory(0); //delete m; + if(d->roomCfgDlg) { + d->roomCfgDlg->disconnect(); + d->roomCfgDlg->close(true); + } + + if(d->destroyRoomDlg) { + d->destroyRoomDlg->disconnect(); + d->destroyRoomDlg->close(true); + } + + if(d->affilEditorDlg) { + d->affilEditorDlg->disconnect(); + d->affilEditorDlg->close(true); + } + d->pa->dialogUnregister(this); delete d; } @@ -875,6 +1382,17 @@ #endif } +void GCMainDlg::updateButtons() +{ + bool is_owner = d->selfStatus.mucUserAffiliation() == MUC::AffilOwner; + bool is_admin = d->selfStatus.mucUserAffiliation() >= MUC::AffilAdmin; + + d->act_ownerConfig->setEnabled(is_owner); + d->act_destroyRoom->setEnabled(is_owner); + + d->act_affilEditor->setEnabled(is_admin); +} + void GCMainDlg::mle_returnPressed() { if(d->mle->text().isEmpty()) @@ -952,25 +1470,314 @@ } } -void GCMainDlg::doClear() +void GCMainDlg::lv_doubleClicked(QListViewItem *i) { - d->te_log->setText(""); -} + if(!i || i->rtti() != GCUserViewItem::RTTI) + return; -void GCMainDlg::doClearButton() -{ - int n = QMessageBox::information(this, tr("Warning"), tr("Are you sure you want to clear the chat window?\n(note: does not affect saved history)"), tr("&Yes"), tr("&No")); - if(n == 0) - doClear(); + GCUserViewItem *lvi = (GCUserViewItem *)i; + + if(option.defaultAction == 0) + lv_action(lvi->text(0), lvi->s, GC_CtxSendMsg); + else + lv_action(lvi->text(0), lvi->s, GC_CtxOpenChat); } -void GCMainDlg::openFind() +class AddMucOperation { - if(d->findDlg) - bringToFront(d->findDlg); - else { - d->findDlg = new GCFindDlg(0, 0, d->lastSearch, this); - connect(d->findDlg, SIGNAL(find(int, int, const QString &)), SLOT(doFind(int, int, const QString &))); +private: + QPopupMenu *m_menu; + QPtrList &m_menus; + GCMainDlg *m_dlg; + MUC::Role m_their_role, m_our_role; + MUC::Affiliation m_their_affil, m_our_affil; + GCUserViewItem *m_lvi; + +public: + AddMucOperation(QPopupMenu *menu, QPtrList &menus, GCMainDlg *dlg, + MUC::Role their_role, MUC::Role our_role, + MUC::Affiliation their_affil, MUC::Affiliation our_affil, + GCUserViewItem *lvi) + : m_menu(menu), m_menus(menus), m_dlg(dlg), + m_their_role(their_role), m_our_role(our_role), + m_their_affil(their_affil), m_our_affil(our_affil), + m_lvi(lvi) + { + } + + // Parameters for role items + static QString name(MUC::Role r) + { + static MUC::RoleMap names; + if(names.count() == 0) { + names[MUC::RoleNone] = GCMainDlg::tr("&Kick"); + names[MUC::RoleVisitor] = GCMainDlg::tr("&Visitor"); + names[MUC::RoleParticipant] = GCMainDlg::tr("&Participant"); + names[MUC::RoleModerator] = GCMainDlg::tr("Mo&derator"); + } + + return names[r]; + } + + Reason::Type reasonType(MUC::Role op) + { + if(op == MUC::RoleNone || op == MUC::RoleVisitor) { + return Reason::Negative; + } + + if(m_lvi) { + if((op == MUC::RoleParticipant && m_their_role < MUC::RoleParticipant) || + (op == MUC::RoleModerator && m_their_role < MUC::RoleModerator)) { + return Reason::Positive; + } + else { + return Reason::Negative; + } + } + else { + return Reason::Both; + } + } + + bool checked(MUC::Role op) + { + return (m_their_role == op); + } + + bool enabled(MUC::Role op) + { + if(op == MUC::RoleNone) { + return (m_our_role == MUC::RoleModerator) && + (m_lvi == NULL || m_their_affil < m_our_affil); + } + else if(op == MUC::RoleVisitor) { + return (m_our_role == MUC::RoleModerator) && + (m_lvi == NULL || + ((m_their_affil < m_our_affil) && (m_their_affil < MUC::AffilAdmin))); + } + else if(op == MUC::RoleParticipant) { + if(m_lvi == NULL) + return m_our_role == MUC::RoleModerator; + else + return ((m_our_role == MUC::RoleModerator && m_their_role != MUC::RoleModerator) || + (m_our_affil >= MUC::AffilAdmin && m_their_role == MUC::RoleModerator)) && + (m_their_affil < MUC::AffilAdmin); + } + else if(op == MUC::RoleModerator) { + return (m_our_affil >= MUC::AffilAdmin) && + (m_lvi == NULL || m_their_role < MUC::RoleModerator); + } + else { + return false; + } + } + + // The same methods for affiliations + static QString name(MUC::Affiliation a) + { + static MUC::AffilMap names; + if(names.count() == 0) { + names[MUC::AffilOutcast] = GCMainDlg::tr("&Ban"); + names[MUC::AffilNone] = GCMainDlg::tr("&No Affiliation"); + names[MUC::AffilMember] = GCMainDlg::tr("M&ember"); + names[MUC::AffilAdmin] = GCMainDlg::tr("&Admin"); + names[MUC::AffilOwner] = GCMainDlg::tr("&Owner"); + } + + return names[a]; + } + + Reason::Type reasonType(MUC::Affiliation op) + { + if(op == MUC::AffilOutcast || op == MUC::AffilNone) { + return Reason::Negative; + } + + if(m_lvi) { + if((op == MUC::AffilMember && m_their_affil < MUC::AffilMember) || + (op == MUC::AffilAdmin && m_their_affil < MUC::AffilAdmin) || + (op == MUC::AffilOwner && m_their_affil < MUC::AffilOwner)) { + return Reason::Positive; + } + else { + return Reason::Negative; + } + } + else { + return Reason::Both; + } + } + + bool checked(MUC::Affiliation op) + { + return (m_their_affil == op); + } + + bool enabled(MUC::Affiliation op) + { + if(op == MUC::AffilOutcast) { + return (m_our_affil >= MUC::AffilAdmin) && + (m_lvi == NULL || m_their_affil < MUC::AffilAdmin); + } + else if(op == MUC::AffilNone || op == MUC::AffilMember) { + return m_our_affil >= MUC::AffilAdmin && + (m_lvi == NULL || + (m_their_affil < m_our_affil || m_our_affil == MUC::AffilOwner)); + } + else if(op == MUC::AffilAdmin || op == MUC::AffilOwner) { + return m_our_affil >= MUC::AffilOwner; + } + else { + return false; + } + } + + template + void createItem(T op, const char *slot, + const char *icon = NULL) + { + int item; + + if(!checked(op) && enabled(op)) { + ReasonMenu *temp = new ReasonMenu(reasonType(op), m_dlg); + m_menus.append(temp); + m_dlg->connect(temp, SIGNAL(activated(const QString &)), slot); + item = m_menu->insertItem(name(op), temp); + } + else { + item = m_menu->insertItem(name(op)); + m_menu->setItemChecked(item, checked(op)); + m_menu->setItemEnabled(item, false); + } + + if(icon) { + // leave it to Trolltech to get the popup menu API wrong + // and the name gets set again :-P + m_menu->changeItem(item, IconsetFactory::icon(icon), name(op)); + } + } +}; + +void GCMainDlg::lv_contextMenuRequested(QListViewItem *i, const QPoint &pos, int) +{ + // NOTE: Be sure to add new items to the GC_CtxMenuItems + // enum in groupchatdlg.h + + if(!i) + return; + + const QStringList &nicks(d->lv_users->selectedNickList()); + int num_selected = nicks.count(); + GCUserViewItem *lvi = NULL; + if(num_selected == 1) { + if(i->rtti() == GCUserViewItem::RTTI) + lvi = (GCUserViewItem *)i; + else + lvi = (GCUserViewItem *)d->lv_users->findEntry(nicks[0]); + } + + QPtrList reason_menus; + reason_menus.setAutoDelete(true); + + QPopupMenu *pm = new QPopupMenu; + + pm->insertItem(IconsetFactory::icon("psi/sendMessage"), tr("Send &message"), GC_CtxSendMsg); + pm->insertItem(IconsetFactory::icon("psi/start-chat"), tr("Open &chat window"), GC_CtxOpenChat); + pm->insertSeparator(); + + //pm->insertItem(tr("Send &file"), GC_CtxSendFile); + //pm->insertSeparator(); + pm->insertItem(tr("Check &Status"), GC_CtxCheckStatus); + pm->insertItem(IconsetFactory::icon("psi/vCard"), tr("User &Info"), GC_CtxUserInfo); + + pm->insertSeparator(); + + QString their_nick; + Jid their_jid; + MUC::Role their_role = MUC::RoleUnknown, + our_role = d->selfStatus.mucUserRole(); + MUC::Affiliation their_affil = MUC::AffilUnknown, + our_affil = d->selfStatus.mucUserAffiliation(); + + if(lvi != NULL) { + // if we have an item extract its info + their_nick = lvi->text(0); + their_jid = lvi->s.mucUserJid(); + their_role = lvi->s.mucUserRole(); + their_affil = lvi->s.mucUserAffiliation(); + } + else { + // if we don't or have more than one then disable some items + pm->setItemEnabled(GC_CtxSendMsg, false); + pm->setItemEnabled(GC_CtxOpenChat, false); + pm->setItemEnabled(GC_CtxCheckStatus, false); + pm->setItemEnabled(GC_CtxUserInfo, false); + } + + AddMucOperation amo(pm, reason_menus, this, + their_role, our_role, + their_affil, our_affil, + lvi); + + amo.createItem(MUC::RoleNone, + SLOT(kick(const QString &)), + "psi/muc_kick"); + amo.createItem(MUC::AffilOutcast, + SLOT(ban(const QString &)), + "psi/muc_ban"); + + pm->insertSeparator(); + + // Role changes + amo.createItem(MUC::RoleVisitor, + SLOT(occToVisitor(const QString &))); + amo.createItem(MUC::RoleParticipant, + SLOT(occToParticipant(const QString &))); + amo.createItem(MUC::RoleModerator, + SLOT(occToMod(const QString &))); + + // affiliation changes + pm->insertSeparator(); + + amo.createItem(MUC::AffilNone, + SLOT(occToNoAffil(const QString &))); + amo.createItem(MUC::AffilMember, + SLOT(occToMember(const QString &))); + amo.createItem(MUC::AffilAdmin, + SLOT(occToAdmin(const QString &))); + amo.createItem(MUC::AffilOwner, + SLOT(occToOwner(const QString &))); + + int x = pm->exec(pos); + delete pm; + + if(x == -1) + return; + if(lvi != NULL) + lv_action(lvi->text(0), lvi->s, x); +} + +void GCMainDlg::doClear() +{ + d->te_log->setText(""); +} + +void GCMainDlg::doClearButton() +{ + int n = QMessageBox::information(this, tr("Warning"), + tr("Are you sure you want to clear the chat window?\n(note: does not affect saved history)"), + QMessageBox::Yes, QMessageBox::No); + if(n == QMessageBox::Yes) + doClear(); +} + +void GCMainDlg::openFind() +{ + if(d->findDlg) + bringToFront(d->findDlg); + else { + d->findDlg = new GCFindDlg(0, 0, d->lastSearch, this); + connect(d->findDlg, SIGNAL(find(int, int, const QString &)), SLOT(doFind(int, int, const QString &))); d->findDlg->show(); } } @@ -993,6 +1800,27 @@ d->state = Private::Idle; d->pb_topic->setEnabled(false); appendSysMsg(tr("Disconnected."), true); + d->lv_users->clear(); + d->selfStatus = Status(); + updateButtons(); + + if(d->roomCfgDlg) { + d->roomCfgDlg->disconnect(); + d->roomCfgDlg->close(true); + d->roomCfgDlg = NULL; + } + + if(d->destroyRoomDlg) { + d->destroyRoomDlg->disconnect(); + d->destroyRoomDlg->close(true); + d->destroyRoomDlg = NULL; + } + + if(d->affilEditorDlg) { + d->affilEditorDlg->disconnect(); + d->affilEditorDlg->close(true); + d->affilEditorDlg = NULL; + } } } @@ -1005,8 +1833,9 @@ QString host = d->jid.host(); QString room = d->jid.user(); QString nick = d->self; + QString pass = d->password; - if(!d->pa->groupChatJoin(host, room, nick)) { + if(!d->pa->groupChatJoin(host, room, nick, pass)) { appendSysMsg(tr("Error: You are in or joining this room already!"), true); d->state = Private::Idle; } @@ -1025,6 +1854,11 @@ } } +PsiAccount *GCMainDlg::pa() const +{ + return d->pa; +} + Jid GCMainDlg::jid() const { return d->jid; @@ -1042,21 +1876,256 @@ d->state = Private::Idle; } +QString GCMainDlg::roleToString(MUC::Role r) +{ + static MUC::RoleMap names; + if(names.count() == 0) { + names[MUC::RoleUnknown] = tr("unknown"); + names[MUC::RoleNone] = tr("nothing"); + names[MUC::RoleVisitor] = tr("a visitor"); + names[MUC::RoleParticipant] = tr("a participant"); + names[MUC::RoleModerator] = tr("a moderator"); + } + + return names[r]; +} + +QString GCMainDlg::affilToString(MUC::Affiliation a) +{ + // FIXME: use Affiliation::toString? + static MUC::AffilMap names; + if(names.count() == 0) { + names[MUC::AffilUnknown] = GCMainDlg::tr("unknown"); + names[MUC::AffilOutcast] = GCMainDlg::tr("an outcast"); + names[MUC::AffilNone] = GCMainDlg::tr("unaffiliated"); + names[MUC::AffilMember] = GCMainDlg::tr("a member"); + names[MUC::AffilAdmin] = GCMainDlg::tr("an admin"); + names[MUC::AffilOwner] = GCMainDlg::tr("an owner"); + } + + return names[a]; +} + +QSize GCMainDlg::defaultSize() +{ + return QSize(580, 420); +} + +int GCMainDlg::defaultOccListSize() +{ + return 110; +} + +void GCMainDlg::mucAvailPresence(const QString &nick, const Status &s) +{ + if(s.mucUserStatus() == 201) { + d->roomLocked = true; + int result = QMessageBox::question(this, tr("New Room"), + tr("This is a new room and needs to be configured. Do you want to configure the room?"), + QMessageBox::Yes, QMessageBox::No, QMessageBox::Abort); + if(result == QMessageBox::Yes) + fetchConfigForm(); + else if(result == QMessageBox::No) { + MT_RoomConfig *t = new MT_RoomConfig(d->pa->client()->rootTask()); + t->instant(d->jid); + t->go(true); + d->roomLocked = false; + } + else { + MT_RoomConfig *t = new MT_RoomConfig(d->pa->client()->rootTask()); + t->cancel(d->jid); + t->go(true); + close(); + return; + } + } + + if (!(option.showJoins || option.showStatusChanges || + option.showRoleAffilChanges)) + return; + + QString m; + GCUserViewItem *occupant = (GCUserViewItem *)d->lv_users->findEntry(nick); + + if(occupant == NULL) { + //contact joining + if(!option.showJoins) + return; + + Jid from(s.mucUserJid()); + m = tr("%1 has joined the channel"); + + if(from.isEmpty()) + m = m.arg(nick); + else + m = m.arg(QString("%1 (%2)").arg(nick).arg(from.full())); + + if(s.isMuc()) { + QString role = tr(" as %1").arg(roleToString(s.mucUserRole())); + if(s.mucUserAffiliation() != MUC::AffilNone) + role += tr(" and %1").arg(affilToString(s.mucUserAffiliation())); + m += role; + } + } + else if (option.showStatusChanges && s.isAway()) { + m = tr("%1 is away").arg(nick); + if(!s.status().isEmpty()) + m += QString(" (%1)").arg(s.status()); + } + else if(option.showRoleAffilChanges) { + bool role_changed = occupant->s.mucUserRole() != s.mucUserRole(); + bool affil_changed = occupant->s.mucUserAffiliation() != s.mucUserAffiliation(); + + if(role_changed && affil_changed) { + m = tr("%1 is now %2 and %3").arg(nick); + m = m.arg(roleToString(s.mucUserRole())); + m = m.arg(affilToString(s.mucUserAffiliation())); + } + else if(role_changed) { + m = tr("%1 is now %2").arg(nick); + m = m.arg(roleToString(s.mucUserRole())); + } + else if(affil_changed) { + m = tr("%1 is now %2").arg(nick); + m = m.arg(affilToString(s.mucUserAffiliation())); + } + else if(s.isAway() != occupant->s.isAway() && option.showStatusChanges) { + m = tr("%1 is back").arg(nick); + } + + if((role_changed || affil_changed) && !s.mucReason().isEmpty()) + m += QString(" (%1)").arg(s.mucReason()); + } + else { + return; + } + + if(!m.isEmpty()) + appendSysMsg(m, false, QDateTime::currentDateTime()); +} + +void GCMainDlg::mucUnavailPresence(const QString &nick, const Status &s) +{ + if(s.mucDestroyed()) { + QString m = tr("This room has been destroyed."); + if(!s.mucReason().isEmpty()) { + m += tr("\nReason: %1").arg(s.mucReason()); + } + + const Jid &venue = s.mucDestroyVenue(); + if(!venue.isEmpty()) { + m += tr("\nAlt. Venue: %1").arg(venue.full()); + m += tr("\nDid you want to join the alternate venue?"); + + int ret = QMessageBox::information(this, tr("Room Destroyed"), + m, QMessageBox::Yes, QMessageBox::No); + if(ret == QMessageBox::Yes) { + GCJoinDlg *w = d->pa->actionJoin(venue.full(), QString::null); + w->doJoin(); + } + } + else { + QMessageBox::information(this, tr("Room Destroyed"), + m, QMessageBox::Ok); + } + + close(); + } + + if (option.showJoins) { + QString m; + + switch(s.mucUserStatus()) { + case 301: + m = nick + tr(" was banned"); + if(!s.mucReason().isEmpty()) + m += QString(" (%1)").arg(s.mucReason()); + if(!s.mucActor().isEmpty()) + m += tr(" by %1").arg(s.mucActor().full()); + + if(nick == d->self) { + d->mucInfoBox(tr("Banned"), tr("You got banned from the room"), + s.mucActor(), s.mucReason()); + close(); + } + break; + case 303: + m = nick + tr(" is now known as ") + s.mucUserNick(); + d->lv_users->updateEntry(s.mucUserNick(), s); + break; + case 307: + m = nick + tr(" was kicked"); + if(!s.mucReason().isEmpty()) + m += QString(" (%1)").arg(s.mucReason()); + if(!s.mucActor().isEmpty()) + m += tr(" by %1").arg(s.mucActor().full()); + + if(nick == d->self) { + d->mucInfoBox(tr("Kicked"), tr("You got kicked from the room"), + s.mucActor(), s.mucReason()); + close(); + } + + break; + case 321: + m = nick + tr(" was removed due to an affiliation change."); + if(nick == d->self) { + d->mucInfoBox(tr("Removed"), tr("You got removed from the room"), + s.mucActor(), s.mucReason()); + close(); + } + break; + case 322: + m = tr("The room became members only, and %1 was forced to leave.").arg(nick); + if(nick == d->self) { + d->mucInfoBox(tr("Members Only"), tr("The room became members only."), + s.mucActor(), s.mucReason()); + close(); + } + break; + default: + //contact leaving + m = nick+tr(" has left the channel"); + if(!s.status().isEmpty()) + m += QString(" (%1)").arg(s.status()); + } + appendSysMsg(m, false, QDateTime::currentDateTime()); + } +} + void GCMainDlg::presence(const QString &nick, const Status &s) { + QString m; + + if(s.hasError()) { + if(s.errorCode() == 409) + m = tr("Please choose a different nickname"); + else + m = tr("Unknown error code"); + appendSysMsg(m, true, QDateTime::currentDateTime()); + return; + } + + if(nick == d->self) { + if(s.mucUserAffiliation() != d->selfStatus.mucUserAffiliation()) + ourAffilChanged(s.mucUserAffiliation()); + if(s.mucUserRole() != d->selfStatus.mucUserRole()) + ourRoleChanged(s.mucUserRole()); + + d->selfStatus = s; + updateButtons(); + } + if(s.isAvailable()) { - /*if ((option.showJoins)&&(d->lv_users->findEntry(nick)==0)) { - //contact joining - QString m=nick+tr(" has joined the channel"); - appendSysMsg(m, false, QDateTime::currentDateTime()); - }*/ + if(s.isMuc()) + mucAvailPresence(nick, s); + d->lv_users->updateEntry(nick, s); - } else { - /*if (option.showJoins) { - //contact leaving - QString m=nick+tr(" has left the channel"); - appendSysMsg(m, false, QDateTime::currentDateTime()); - }*/ + } + else { + if(s.isMuc()) + mucUnavailPresence(nick, s); + d->lv_users->removeEntry(nick); } } @@ -1066,10 +2135,20 @@ QString from = m.from().resource(); bool alert = false; + if(m.type() == "error") { + // FIXME: lookup error code + appendSysMsg(tr("Error: %1").arg(m.error().text), true, m.timeStamp()); + return; + } + if(!m.subject().isEmpty()) { d->le_topic->setText(m.subject()); d->le_topic->setCursorPosition(0); QToolTip::add(d->le_topic, QString("

%1

").arg(m.subject())); + + QString text(tr("%1 set the topic to \"%2\".").arg(from).arg(m.subject())); + appendSysMsg(text, true, m.timeStamp()); + return; } // code to determine if the speaker was addressing this client in chat @@ -1097,8 +2176,43 @@ d->pa->playSound(option.onevent[eChat2]); } - if(from.isEmpty()) - appendSysMsg(m.body(), alert, m.timeStamp()); + if(from.isEmpty()) { + QString text; + + Stanza::Error error = m.error(); + if(!error.text.isEmpty()) { + text = tr("Error: %1").arg(error.text); // FIXME: use error code + alert = true; + } + else if(m.mucStatus() != 0) { + int status = m.mucStatus(); + + switch(status) { + case 100: + text = tr("This room is not anonymous!"); + break; + case 101: + text = tr("Your affiliation changed while you were away."); + break; + case 102: + text = tr("The room is now showing unavailable members."); + break; + case 103: + text = tr("The room is not showing unavailable members."); + break; + case 104: + text = tr("The room's configuration has changed."); + break; + default: + text = tr("Unknown status code: %1").arg(m.mucStatus()); + } + } + else { + text = m.body(); + } + + appendSysMsg(text, alert, m.timeStamp()); + } else appendMessage(m, alert); } @@ -1106,13 +2220,84 @@ void GCMainDlg::joined() { if(d->state == Private::Connecting) { - d->lv_users->QListView::clear(); + d->lv_users->clear(); d->state = Private::Connected; d->pb_topic->setEnabled(true); appendSysMsg(tr("Connected."), true); } } +void GCMainDlg::resizeEvent(QResizeEvent *e) +{ + if(option.keepSizes) + option.sizeGCDlg = e->size(); +} + +void GCMainDlg::fetchConfigForm() +{ + if(d->roomCfgDlg) { + d->roomCfgDlg->raise(); + } + else { + d->roomCfgDlg = new RoomCfgDlg(d->jid, d->pa->client()); + connect(d->roomCfgDlg, SIGNAL(done(bool)), this, SLOT(roomCfgDone(bool))); + d->roomCfgDlg->show(); + } +} + +void GCMainDlg::roomCfgDone(bool accepted) +{ + d->roomCfgDlg->disconnect(); + d->roomCfgDlg = NULL; // should be deleted on its own + + if(accepted) { + if(d->roomLocked) + d->roomLocked = false; + } + else { + if(d->roomLocked) + close(); + } +} + +void GCMainDlg::destroyRoom() +{ + if(d->destroyRoomDlg == NULL) { + d->destroyRoomDlg = new DestroyRoomDlg(d->jid, d->pa); + connect(d->destroyRoomDlg, SIGNAL(done(bool)), + this, SLOT(destroyRoomDlgDone(bool))); + d->destroyRoomDlg->show(); + } + else { + d->destroyRoomDlg->raise(); + } +} + +void GCMainDlg::destroyRoomDlgDone(bool) +{ + d->destroyRoomDlg->disconnect(); + d->destroyRoomDlg = NULL; // should be deleted on its own +} + +void GCMainDlg::affilEditor() +{ + if(d->affilEditorDlg == NULL) { + d->affilEditorDlg = new AffilEditorDlg(this, d->selfStatus.mucUserAffiliation(), + d->pa, "affil_editor"); + connect(d->affilEditorDlg, SIGNAL(done(bool)), this, SLOT(affilEditorDone(bool))); + d->affilEditorDlg->show(); + } + else { + d->affilEditorDlg->raise(); + } +} + +void GCMainDlg::affilEditorDone(bool) +{ + d->affilEditorDlg->disconnect(); + d->affilEditorDlg = NULL; // should be deleted by Qt +} + void GCMainDlg::appendSysMsg(const QString &str, bool alert, const QDateTime &ts) { bool atBottom = d->te_log->contentsY() >= d->te_log->contentsHeight() - d->te_log->visibleHeight(); @@ -1149,7 +2334,11 @@ y = 0; bool atBottom = (d->te_log->contentsY() < y - 32) ? false: true;*/ - d->te_log->append(hr + QString("[%1]").arg(timestr) + QString(" *** %1").arg(expandEntities(str))); + QString color = "00A000"; + if(alert) + color = "FF0000"; + + d->te_log->append(hr + QString("[%2]").arg(color).arg(timestr) + QString(" *** %1").arg(expandEntities(str))); if(atBottom) d->deferredScroll(); @@ -1385,8 +2574,114 @@ setLooks(); - // update status icons - d->lv_users->updateAll(); + // update occupant list + d->lv_users->optionsUpdate(); + + // update affiliation editor + if(d->affilEditorDlg) + d->affilEditorDlg->optionsUpdate(); +} + +void GCMainDlg::kick(const QString &reason) +{ + d->changeOccupants(d->lv_users->selectedNickList(), + reason, MUC::RoleNone); +} + +void GCMainDlg::ban(const QString &reason) +{ + int n = QMessageBox::question(this, tr("Ban User?"), + tr("Are you sure you want to ban the selected occupants?"), + QMessageBox::Yes, QMessageBox::No); + + if(n == QMessageBox::Yes) { + d->changeOccupants(d->lv_users->selectedJidList(), + reason, MUC::AffilOutcast); + } +} + +void GCMainDlg::occToVisitor(const QString &reason) +{ + d->changeOccupants(d->lv_users->selectedNickList(), + reason, MUC::RoleVisitor); +} + +void GCMainDlg::occToParticipant(const QString &reason) +{ + d->changeOccupants(d->lv_users->selectedNickList(), + reason, MUC::RoleParticipant); +} + +void GCMainDlg::occToMod(const QString &reason) +{ + d->changeOccupants(d->lv_users->selectedNickList(), + reason, MUC::RoleModerator); +} + +void GCMainDlg::occToAdmin(const QString &reason) +{ + d->changeOccupants(d->lv_users->selectedJidList(), + reason, MUC::AffilAdmin); +} + +void GCMainDlg::occToOwner(const QString &reason) +{ + d->changeOccupants(d->lv_users->selectedJidList(), + reason, MUC::AffilOwner); +} + +void GCMainDlg::occToNoAffil(const QString &reason) +{ + d->changeOccupants(d->lv_users->selectedJidList(), + reason, MUC::AffilNone); +} + +void GCMainDlg::occToMember(const QString &reason) +{ + d->changeOccupants(d->lv_users->selectedJidList(), + reason, MUC::AffilMember); +} + +class ChangeOccupantsFunk +{ +public: + GCMainDlg *par; + + ChangeOccupantsFunk(GCMainDlg *p) + : par(p) + { + } + + void operator () (const MT_ChangeOccupants::Item &item) + { + QString msg = QObject::tr("Sorry, but you're not allowed to change %1 into %2."); + + if(!item.nick().isEmpty()) + msg = msg.arg(item.nick()); + else if(!item.jid().isEmpty()) + msg = msg.arg(item.jid().bare()); + else + msg = msg.arg(QObject::tr("unknown")); + + if(item.role() != MUC::RoleUnknown) + msg = msg.arg(GCMainDlg::roleToString(item.role())); + else + msg = msg.arg(GCMainDlg::affilToString(item.affil())); + + par->appendSysMsg(msg, true); + } +}; + +void GCMainDlg::changeOccupantsFinished() +{ + MT_ChangeOccupants *task = (MT_ChangeOccupants *)sender(); + Q_ASSERT(task != NULL); + Q_ASSERT(task->isA("MT_ChangeOccupants")); + + if(!task->success()) { + const MT_ChangeOccupants::ItemList &items(task->items()); + for_each(items.begin(), items.end(), ChangeOccupantsFunk(this)); + } } void GCMainDlg::lv_action(const QString &nick, const Status &s, int x) @@ -1433,6 +2728,15 @@ d->act_clear->addTo( d->pm_settings ); d->pm_settings->insertSeparator(); + d->act_ownerConfig->addTo(d->pm_settings); + d->act_destroyRoom->addTo(d->pm_settings); + d->act_affilEditor->addTo(d->pm_settings); + + d->pm_settings->insertSeparator(); + + d->act_bmGroup->addTo(d->pm_settings); + + d->pm_settings->insertSeparator(); d->act_icon->addTo( d->pm_settings ); } @@ -1442,6 +2746,79 @@ setLooks(); } +GCBookmarkState GCMainDlg::bookmarkState() const +{ + if(d->act_bmRemember->isOn()) + return GC_Remember; + else if(d->act_bmAutojoin->isOn()) + return GC_Autojoin; + else + return GC_Forget; +} + +void GCMainDlg::setBookmarkState(GCBookmarkState state) +{ + if(state == GC_Autojoin) + d->act_bmAutojoin->setOn(true); + else if(state == GC_Remember) + d->act_bmRemember->setOn(true); + else + d->act_bmForget->setOn(true); +} + +void GCMainDlg::bookmarksActivated() +{ + // refresh the bookmark list + connect(d->pa, SIGNAL(bookmarksReceived()), this, SLOT(bookmarksReceived())); + d->pa->requestBookmarks(); + + // do the actual update in bookmarksReceived +} + +void GCMainDlg::bookmarksReceived() +{ + disconnect(d->pa, SIGNAL(bookmarksReceived()), this, SLOT(bookmarksReceived())); + + GCBookmarkState state = bookmarkState(); + + // update the bookmark + + // gotta find it first + GCBookmark *mark = d->pa->findGCBookmark(d->jid); + // if it exists then + if(mark) { + // delete it if the new state is to forget + if(state == GC_Forget) { + d->pa->deleteGCBookmark(d->jid); + } + else { + // update it for any other + mark->setAutojoin(state == GC_Autojoin); + } + } + else { + // if it doesn't exist then + // don't do anything if it's to be forgotten + if(state == GC_Forget) + return; + + // create it if the new state is remember or autojoin + QString name = d->jid.bare() + "/" + d->self; + d->pa->addGCBookmark(name, d->jid, d->self, state == GC_Autojoin, d->password); + } + + // send to the server + connect(d->pa, SIGNAL(bookmarksSent()), this, SLOT(bookmarksSent())); + d->pa->pushBookmarks(); +} + +void GCMainDlg::bookmarksSent() +{ + // append system message + disconnect(d->pa, SIGNAL(bookmarksSent()), this, SLOT(bookmarksSent())); + appendSysMsg("Your bookmarks have been updated successfully", true); +} + //---------------------------------------------------------------------------- // GCFindDlg //---------------------------------------------------------------------------- diff -Naur psi-0.10-orig/src/groupchatdlg.h psi-0.10/src/groupchatdlg.h --- psi-0.10-orig/src/groupchatdlg.h 2005-08-22 19:52:28.000000000 +0000 +++ psi-0.10/src/groupchatdlg.h 2006-01-14 00:43:21.000000000 +0000 @@ -21,18 +21,27 @@ #include #include #include +#include #include"advwidget.h" #include"im.h" +#include"muc.h" #include"ui_gcjoin.h" +#include"psilistview.h" using namespace XMPP; +class QStringList; +class QAction; class PsiCon; class PsiAccount; +class RoomCfgDlg; +class MT_ChangeOccupants; + +enum GCBookmarkState { GC_Forget, GC_Remember, GC_Autojoin }; class GCJoinDlg : public GCJoinUI { - Q_OBJECT + Q_OBJECT; public: GCJoinDlg(PsiCon *, PsiAccount *); ~GCJoinDlg(); @@ -40,57 +49,103 @@ void joined(); void error(int, const QString &); + void setHost(const QString &); + void setRoom(const QString &); + void setNick(const QString &); + void setPassword(const QString &); + void setInvite(const Jid &); + void setSavePassword(bool); + void setRemember(GCBookmarkState); + + QString host() const; + QString room() const; + QString nick() const; + QString password() const; + bool savePassword() const; + GCBookmarkState remember() const; + + bool isInvite() const; + const Jid &inviteFrom() const; + protected: - //void closeEvent(QCloseEvent *); + void closeEvent(QCloseEvent *); + void requestBookmarks(); + void updateBookmarks(); public slots: void done(int); + void doJoin(); private slots: - void doJoin(); void updateIdentity(PsiAccount *); void pa_disconnected(); - void recent_activated(int); + void bookmarks_activated(int); + void rememberActivated(int); + void bookmarksReceived(); private: class Private; Private *d; void disableWidgets(); - void enableWidgets(); + void enableWidgets(bool yes = true); }; -class GCUserViewItem : public QListViewItem +class GCUserViewItem : public PsiListViewItem { public: + static const int RTTI; + GCUserViewItem(QListView *); + GCUserViewItem(QListViewItem *); + + inline virtual int rtti() const { return RTTI; } + virtual int compare(QListViewItem *, int, bool) const; + virtual void paintCell(QPainter *, const QColorGroup &, + int, int, int); + virtual void optionsUpdate(); + + const Jid &jid() const; Status s; }; -class GCUserView : public QListView, public QToolTip +class GCMainDlg; + +class GCUserView : public PsiListView, public QToolTip { Q_OBJECT public: - GCUserView(QWidget *parent=0, const char *name=0); + GCUserView(QWidget *parent=0, const Jid &room = Jid(), const char *name=0); ~GCUserView(); - void updateAll(); QListViewItem *findEntry(const QString &); void updateEntry(const QString &, const Status &); void removeEntry(const QString &); QStringList nickList() const; + QStringList selectedNickList() const; + QValueList selectedJidList() const; + + virtual void clear(); + + virtual void resizeEvent(QResizeEvent *); + + Jid room() const; protected: // reimplemented void maybeTip(const QPoint &); + QListViewItem *roleItem(MUC::Role); + void addRoles(); + + QDragObject *dragObject(); signals: void action(const QString &, const Status &, int); -private slots: - void qlv_doubleClicked(QListViewItem *); - void qlv_contextMenuRequested(QListViewItem *, const QPoint &, int); +private: + class Private; + Private *d; }; /*class GCLineEdit : public QLineEdit @@ -107,13 +162,26 @@ void keyPressEvent(QKeyEvent *); };*/ +enum GC_CtxMenuItems +{ + GC_CtxSendMsg, + GC_CtxOpenChat, + GC_CtxCheckStatus, + GC_CtxUserInfo, + GC_CtxSendFile, + + GC_CtxEnd // just specifies the end of the enum +}; + class GCMainDlg : public AdvancedWidget { - Q_OBJECT + Q_OBJECT; + public: - GCMainDlg(PsiAccount *, const Jid &); + GCMainDlg(PsiAccount *, const Jid &, const QString &); ~GCMainDlg(); + PsiAccount *pa() const; Jid jid() const; void error(int, const QString &); @@ -121,17 +189,53 @@ void message(const Message &); void joined(); + // sneakin: is this indecent exposure? + void appendSysMsg(const QString &, bool, const QDateTime &ts=QDateTime()); + void appendMessage(const Message &, bool); + + GCBookmarkState bookmarkState() const; + void setBookmarkState(GCBookmarkState); + + static QString roleToString(MUC::Role); + static QString affilToString(MUC::Affiliation); + + static QSize defaultSize(); + static int defaultOccListSize(); + protected: void keyPressEvent(QKeyEvent *); void closeEvent(QCloseEvent *); void windowActivationChange(bool); + void updateButtons(); + void mucAvailPresence(const QString &, const Status &); + void mucUnavailPresence(const QString &, const Status &); signals: void aSend(const Message &); + void ourRoleChanged(MUC::Role); + void ourAffilChanged(MUC::Affiliation); public slots: + virtual void resizeEvent(QResizeEvent *); + + void fetchConfigForm(); + void destroyRoom(); + void affilEditor(); + void optionsUpdate(); + // The arguments for the following are nickname and reason + // in that order. + void kick(const QString &); + void ban(const QString &); + void occToVisitor(const QString &); + void occToParticipant(const QString &); + void occToMod(const QString &); + void occToAdmin(const QString &); + void occToOwner(const QString &); + void occToNoAffil(const QString &); + void occToMember(const QString &); + private slots: void mle_returnPressed(); void doTopic(); @@ -141,11 +245,23 @@ void pa_updatedActivity(); void goDisc(); void goConn(); - void lv_action(const QString &, const Status &, int); void doClear(); void doClearButton(); void toggleSmallChat(); void buildMenu(); + + void lv_doubleClicked(QListViewItem *); + void lv_contextMenuRequested(QListViewItem *, const QPoint &, int); + + void roomCfgDone(bool); + void destroyRoomDlgDone(bool); + void affilEditorDone(bool); + + void changeOccupantsFinished(); + + void bookmarksActivated(); + void bookmarksReceived(); + void bookmarksSent(); void logSelectionChanged(); public: @@ -156,10 +272,10 @@ void doFlash(bool); void doAlert(); void updateCaption(); - void appendSysMsg(const QString &, bool, const QDateTime &ts=QDateTime()); - void appendMessage(const Message &, bool); void setLooks(); + void lv_action(const QString &, const Status &, int); + void contextMenuEvent(QContextMenuEvent *); QString getNickColor(QString); diff -Naur psi-0.10-orig/src/jiddrag.cpp psi-0.10/src/jiddrag.cpp --- psi-0.10-orig/src/jiddrag.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/jiddrag.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,126 @@ +/* + * jiddrag.cpp - A Jid Drag Object + * Copyright (c) 2004 Nolan Eakins + * + * 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include +#include "im.h" +#include "psicon.h" +#include "jiddrag.h" + +const char JidDrag::MimeType[] = "text/psi-jid-list"; + +class JidDrag::Private +{ +public: + JidDrag::List jids; + + QString jidsAsString() const + { + QStringList strlist; + + for(List::const_iterator it = jids.begin(); + it != jids.end(); + ++it) { + strlist << (*it).full(); + } + + return strlist.join("\n"); + } +}; + +JidDrag::JidDrag(QWidget *src, const char *name) + : QTextDrag(src, name) +{ + init(); +} + +JidDrag::JidDrag(const JidDrag::List &jids, QWidget *src, const char *name) + : QTextDrag(src, name) +{ + init(); + setJids(jids); +} + +JidDrag::JidDrag(const Jid &jid, QWidget *src, const char *name) + : QTextDrag(src, name) +{ + init(); + addJid(jid); +} + +void JidDrag::init() +{ + d = new Private; + setPixmap(IconsetFactory::iconPixmap("status/online"), QPoint(8, 8)); +} + +JidDrag::~JidDrag() +{ + delete d; +} + +void JidDrag::setJids(const List &jids) +{ + d->jids = jids; + setText(d->jidsAsString()); +} + +void JidDrag::addJid(const Jid &j) +{ + d->jids << j; + setText(d->jidsAsString()); +} + +const char *JidDrag::format(int i) const +{ + if(i == 0) + return MimeType; + else + return QTextDrag::format(i - 1); +} + +bool JidDrag::provides(const char *mime_type) const +{ + QString mime(mime_type); + + if(mime == MimeType) + return true; + else + return QTextDrag::provides(mime_type); +} + +bool JidDrag::decode(const QMimeSource *src, JidDrag::List &list) +{ + QString data; + if(canDecode(src) && QTextDrag::decode(src, data)) { + list.clear(); + + QStringList strlist = QStringList::split("\n", data); + + for(QStringList::iterator it = strlist.begin(); + it != strlist.end(); + ++it) { + list << Jid(*it); + } + + return true; + } + + return false; +} diff -Naur psi-0.10-orig/src/jiddrag.h psi-0.10/src/jiddrag.h --- psi-0.10-orig/src/jiddrag.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/jiddrag.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,62 @@ +/* + * jiddrag.h - A Jid Drag Object + * Copyright (c) 2004 Nolan Eakins + * + * 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef JIDDRAG_H +#define JIDDRAG_H + +#include +#include + +namespace XMPP { + class Jid; +} + +using XMPP::Jid; + +class JidDrag: public QTextDrag +{ +public: + static const char MimeType[]; + + typedef QValueList List; + + JidDrag(QWidget *src = NULL, const char *name = NULL); + JidDrag(const List &jids, QWidget *src = NULL, const char *name = NULL); + JidDrag(const Jid &jid, QWidget *src = NULL, const char *name = NULL); + virtual ~JidDrag(); + + void setJids(const List &jids); + void addJid(const Jid &j); + + virtual const char *format(int i = 0) const; + virtual bool provides(const char *mime_type) const; + //virtual QByteArray encodedData(const char *mime_type) const; + + //static bool canDecode(const QMimeSource *src); + static bool decode(const QMimeSource *src, List &list); + +protected: + void init(); + +private: + class Private; + Private *d; +}; + +#endif /* JIDDRAG_H */ diff -Naur psi-0.10-orig/src/jidvalidator.cpp psi-0.10/src/jidvalidator.cpp --- psi-0.10-orig/src/jidvalidator.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/jidvalidator.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,44 @@ +/* + * jidvalidator.cpp - A QValidator for JIDs + * Copyright (c) 2004 Nolan Eakins + * + * 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include "xmpp.h" +#include "jidvalidator.h" + +using XMPP::Jid; + +JidValidator::JidValidator(QObject *parent, const char *name) + : QValidator(parent, name) +{ +} + +JidValidator::~JidValidator() +{ +} + +JidValidator::State JidValidator::validate(QString &input, int &pos) const +{ + if(input.isEmpty()) + return Invalid; + + Jid jid(input); + if(jid.isValid()) + return Acceptable; + else + return Invalid; +} diff -Naur psi-0.10-orig/src/jidvalidator.h psi-0.10/src/jidvalidator.h --- psi-0.10-orig/src/jidvalidator.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/jidvalidator.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,36 @@ +/* + * jidvalidator.h - A QValidator for JIDs + * Copyright (c) 2004 Nolan Eakins + * + * 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef JIDVALIDATOR_H +#define JIDVALIDATOR_H + +#include + +class JidValidator: public QValidator +{ +public: + JidValidator(QObject *parent, const char *name = NULL); + virtual ~JidValidator(); + + State validate(QString &input, int &pos) const; + +private: +}; + +#endif /* JIDVALIDATOR_H */ diff -Naur psi-0.10-orig/src/main.cpp psi-0.10/src/main.cpp --- psi-0.10-orig/src/main.cpp 2005-10-06 13:50:35.000000000 +0000 +++ psi-0.10/src/main.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -60,6 +60,16 @@ QString curLang = "en"; QString curLangName = QT_TR_NOOP("language_name"); +QStringList getLangDirList() +{ + QStringList dirs; + QString subdir = "/lang"; + dirs += g.pathHome + subdir; + dirs += g.pathBase + subdir; + + return dirs; +} + void setLang(const QString &lang) { //printf("changing lang: [%s]\n", lang.latin1()); @@ -71,11 +81,7 @@ return; } - QStringList dirs; - QString subdir = ""; - dirs += "." + subdir; - dirs += g.pathHome + subdir; - dirs += g.pathBase + subdir; + QStringList dirs(getLangDirList()); for(QStringList::Iterator it = dirs.begin(); it != dirs.end(); ++it) { if(!QFile::exists(*it)) continue; @@ -116,11 +122,7 @@ // detect available language packs langs.set("en", "English"); - QStringList dirs; - QString subdir = ""; - dirs += "." + subdir; - dirs += g.pathHome + subdir; - dirs += g.pathBase + subdir; + QStringList dirs(getLangDirList()); for(QStringList::Iterator it = dirs.begin(); it != dirs.end(); ++it) { if(!QFile::exists(*it)) diff -Naur psi-0.10-orig/src/muc_affileditordlg.cpp psi-0.10/src/muc_affileditordlg.cpp --- psi-0.10-orig/src/muc_affileditordlg.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/muc_affileditordlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,421 @@ +/* $Header$ + * affiliationeditordlg.cpp - MUC affiliation editor dialog + * Copyright (C) 2004 Nolan Eakins - sneakin@semanticgap.com + * + * 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 +#include +#include "xmpp.h" +#include "common.h" +#include "iconwidget.h" +#include "psiaccount.h" +#include "groupchatdlg.h" +#include "muc_affileditordlg.h" +#include "muc_affileditorpriv.h" +#include "muc.h" +#include "muc_tasks.h" + +using std::for_each; +using namespace XMPP; + +//---------------------------------------------------------------------------- +// AffilEditorDlg +//---------------------------------------------------------------------------- +class AffilEditorDlg::Private +{ +public: + AffilEditorDlg *parent; + GCMainDlg *gc_dlg; + MUC::Affiliation affil; + PsiAccount *pa; + + IconButton *pb_ok, *pb_cancel, *pb_add, *pb_reset; + AffilEditorMainWidget *main; + + int task_counter; + MUC::AffilMap items; + MUC::AffilMap erred; + + Private(AffilEditorDlg *p, GCMainDlg *gc, + MUC::Affiliation a, PsiAccount *acct) + : parent(p), gc_dlg(gc), affil(a), pa(acct) + { + pb_ok = new IconButton(p, "pb_ok"); + pb_ok->setText(tr("&Ok")); + pb_ok->setIcon("psi/ok"); + pb_ok->setDefault(true); + + pb_cancel = new IconButton(p, "pb_cancel"); + pb_cancel->setText(tr("&Cancel")); + pb_cancel->setIcon("psi/cancel"); + + pb_add = new IconButton(p, "pb_add"); + pb_add->setText(tr("&Add JID")); + pb_add->setIcon("psi/addContact"); + + pb_reset = new IconButton(p, "pb_reset"); + pb_reset->setText(tr("&Reset")); + pb_reset->setIcon("psi/reset"); // for the future + + main = new AffilEditorMainWidget(affil, p, "main"); + + task_counter = 0; + + setError(false); + } + + void createTask(MUC::Affiliation a) { + MT_ChangeOccupants *task = new MT_ChangeOccupants(gc_dlg->pa()->client()->rootTask(), + gc_dlg->jid(), a); + connect(task, SIGNAL(finished()), parent, SLOT(getTaskDone())); + task->getList(true); + task_counter++; + } + + bool itemsChanged(const MT_ChangeOccupants::ItemList &items) { + for(MT_ChangeOccupants::ItemList::const_iterator it = items.begin(); + it != items.end(); + ++it) { + const MT_ChangeOccupants::Item &item(*it); + if(item.changed()) + return true; + } + + return false; + } + + bool hasErrored(MUC::Affiliation a = MUC::AffilUnknown) + { + if(!a.isValid()) { + MUC::AffilMap::iterator it, end = erred.end(); + for(it = erred.begin(); it != end; ++it) { + if(it.data()) + return true; + } + + return false; + } + else { + return erred[a]; + } + } + + void setError(bool value = false, MUC::Affiliation a = MUC::AffilUnknown) + { + if(!a.isValid()) { + for(Affiliation a = MUC::AffilOutcast; + a.isValid(); + a++) { + erred[a] = value; + } + } + else { + erred[a] = value; + } + } + + class MakeItemsOrigFunk + { + public: + void operator () (MT_ChangeOccupants::Item &i) + { + if(i.changed()) { + i.setReason(QString::null); + i.setRole(i.role(), true); + i.setAffil(i.affil(), true); + } + } + }; + + void makeItemsOriginal(MT_ChangeOccupants::ItemList &items) + { + for_each(items.begin(), items.end(), MakeItemsOrigFunk()); + } +}; + +AffilEditorDlg::AffilEditorDlg(GCMainDlg *dlg, + MUC::Affiliation affil, + PsiAccount *pa, const char *name) + : GenericBusyDlg(NULL, name, false, psi_dialog_flags | WDestructiveClose) +{ + d = new Private(this, dlg, affil, pa); + + addButton(d->pb_cancel, Right); + addButton(d->pb_ok, Right); + addButton(d->pb_add, Left); + addButton(d->pb_reset, Left); + setMainWidget(d->main); + + connect(d->pb_cancel, SIGNAL(clicked()), this, SLOT(reject())); + connect(d->pb_ok, SIGNAL(clicked()), this, SLOT(accept())); + connect(d->pb_add, SIGNAL(clicked()), this, SLOT(addJid())); + connect(d->pb_reset, SIGNAL(clicked()), d->main, SLOT(reset())); + + setTabOrder(d->main, d->pb_add); + + setCaption(tr("Affiliation Editor: %1").arg(dlg->jid().bare())); + resize(600, 350); + + getItems(); +} + +AffilEditorDlg::~AffilEditorDlg() +{ + delete d; +} + +class AED_HasJidFunk +{ +public: + class Funk + { + public: + const Jid &jid; + bool result; + Funk(const Jid &j) + : jid(j), result(false) + { + } + + void operator () (const MT_ChangeOccupants::Item &item) + { + if(jid.compare(item.jid())) + result = true; + } + }; + + const Jid &jid; + bool result; + AED_HasJidFunk(const Jid &j) + : jid(j), result(false) + { + } + + void operator () (const MT_ChangeOccupants::ItemList &list) + { + Funk funk(std::for_each(list.begin(), list.end(), Funk(jid))); + result = funk.result | result; + } +}; + +bool AffilEditorDlg::hasJid(const Jid &jid) const +{ + AED_HasJidFunk funk(std::for_each(d->items.begin(), d->items.end(), AED_HasJidFunk(jid))); + return funk.result; +} + +bool AffilEditorDlg::addJid(const Jid &jid, MUC::Affiliation affil, + const QString &reason) +{ + if(!jid.isEmpty() && !hasJid(jid)) { + // create an item + MT_ChangeOccupants::Item item; + item.setJid(jid); + item.setAffil(MUC::AffilNone, true); + item.setAffil(affil); + item.setReason(reason); + + // add item to the item list for the affiliation + // create list view item for the item + // but we can't use the 'item' above since its lifetime + // is limited to this block + d->main->addItem(*(d->items[affil].append(item))); + + // and of course I initially forgot to add this + return true; + } + + return false; +} + +void AffilEditorDlg::addJid() +{ + AffilEditorAddDlg dlg(d->affil, this); + + // set the affil combo box's item to the affil of + // the current selected item + MUC::Affiliation affil = d->main->selectedAffil(); + if(affil != MUC::AffilUnknown) { + dlg.setAffil(affil); + } + + if(dlg.exec() == QDialog::Accepted) { + if(!addJid(dlg.jid(), dlg.affil(), dlg.reasonText())) { + QMessageBox::information(this, tr("Error"), + tr("Sorry but \"%1\" is already listed.").arg(dlg.jid().full()), + QMessageBox::Ok); + } + } +} + +void AffilEditorDlg::accept() +{ + bool started = false; + d->setError(false); + + // iterate through item lists + MUC::AffilMap::iterator it, end = d->items.end(); + for(it = d->items.begin(); it != end; ++it) { + MT_ChangeOccupants::ItemList &items(it.data()); + if(items.count() == 0) + continue; + + if(d->itemsChanged(items)) { + started = true; + MT_ChangeOccupants *task = new MT_ChangeOccupants(d->gc_dlg->pa()->client()->rootTask(), + d->gc_dlg->jid(), it.key()); + connect(task, SIGNAL(finished()), + this, SLOT(setTaskDone())); + + d->task_counter++; + task->setItems(items); + task->setList(true); + } + } + + if(started) { + enableWidgets(false); + busyStart(); + } + else + GenericBusyDlg::accept(); +} + +void AffilEditorDlg::reject() +{ + GenericBusyDlg::reject(); +} + +void AffilEditorDlg::getItems() +{ + enableWidgets(false); + busyStart(); + + if(d->affil == MUC::AffilOwner) { + d->createTask(MUC::AffilOwner); + d->createTask(MUC::AffilAdmin); + } + + if(d->affil >= MUC::AffilAdmin) { + d->createTask(MUC::AffilMember); + d->createTask(MUC::AffilOutcast); + d->createTask(MUC::AffilNone); + } +} + +void AffilEditorDlg::getTaskDone() +{ + MT_ChangeOccupants *task = (MT_ChangeOccupants *)sender(); + Q_ASSERT(task != NULL); + Q_ASSERT(task->isA("MT_ChangeOccupants")); + + d->task_counter--; + if(d->task_counter == 0) { + enableWidgets(); + busyStop(); + } + + if(task->success()) { + MUC::Affiliation affil = task->affil(); + + d->items[affil] = task->items(); + d->main->addItems(d->items[affil]); + } + // querying for 'none' isn't specified in the JEP + // and typically causes an error, so ignore it in + // this case. + else if(task->affil() != MUC::AffilNone) { + QMessageBox::information(this, tr("Error"), + tr("Error: %1").arg(task->statusString()), + QMessageBox::Ok); + } +} + +void AffilEditorDlg::setTaskDone() +{ + MT_ChangeOccupants *task = (MT_ChangeOccupants *)sender(); + Q_ASSERT(task != NULL); + Q_ASSERT(task->isA("MT_ChangeOccupants")); + + d->task_counter--; + + if(!task->success()) { + d->setError(true, task->affil()); + } + + if(d->task_counter == 0) { + enableWidgets(); + busyStop(); + + if(d->hasErrored()) { + QString affils("
    "); + d->main->clearSelection(); + + for(MUC::Affiliation a = MUC::AffilOutcast; + a.isValid(); + a++) { + if(d->hasErrored(a)) { + affils += "
  • %1
  • "; + affils = affils.arg(a.toString()); + + d->main->selectChangedItems(a); + } + else { + d->makeItemsOriginal(d->items[a]); + } + } + + d->main->reset(); + + // This is only here for informational purposes. These lines were used + // when each task could show its own message box which could result + // in quite a few. This allowed the reason to be shown. Since this message + // but most is only shown after all the tasks have finished the reason + // can't be shown, but MUC services left it empty, so it shouldn't be missed. + //if(!task->statusString().isEmpty()) + // m += tr("\nReason: %1").arg(task->statusString()); + + affils += "
"; + QString m = tr("

Psi had trouble changing affiliations for:%1\n" + "These items have been reset and selected.

").arg(affils); + QMessageBox::information(this, tr("Error"), + m, QMessageBox::Ok); + } + else { + GenericBusyDlg::accept(); + } + } +} + +void AffilEditorDlg::enableWidgets(bool yes) +{ + d->pb_cancel->setEnabled(yes); + d->pb_ok->setEnabled(yes); + d->pb_add->setEnabled(yes); + d->main->setEnabled(yes); +} + +void AffilEditorDlg::optionsUpdate() +{ + d->main->optionsUpdate(); +} diff -Naur psi-0.10-orig/src/muc_affileditordlg.h psi-0.10/src/muc_affileditordlg.h --- psi-0.10-orig/src/muc_affileditordlg.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/muc_affileditordlg.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,63 @@ +/* $Header$ + * muc_affileditordlg.h - Header for the MUC affiliation editor dialog + * Copyright (C) 2004 Nolan Eakins - sneakin@semanticgap.com + * + * 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 MUC_AFFILEDITORDLG_H +#define MUC_AFFILEDITORDLG_H + +#include +#include "im.h" +#include "genericbusydlg.h" + +using namespace XMPP; +class GCMainDlg; +class PsiAccount; +class MT_ChangeOccupants; + +class AffilEditorDlg: public GenericBusyDlg +{ + Q_OBJECT; + +public: + AffilEditorDlg(GCMainDlg *, MUC::Affiliation, + PsiAccount *, const char *name = NULL); + virtual ~AffilEditorDlg(); + + bool hasJid(const Jid &) const; + + bool addJid(const Jid &, MUC::Affiliation, + const QString &reason = QString::null); + + void enableWidgets(bool yes = true); + +public slots: + void addJid(); + virtual void accept(); + virtual void reject(); + void optionsUpdate(); + +protected slots: + void getItems(); + void getTaskDone(); + void setTaskDone(); + +private: + class Private; + Private *d; +}; + +#endif /* MUC_AFFILEDITORDLG_H */ diff -Naur psi-0.10-orig/src/muc_affileditorpriv.cpp psi-0.10/src/muc_affileditorpriv.cpp --- psi-0.10-orig/src/muc_affileditorpriv.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/muc_affileditorpriv.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,845 @@ +/* $Header$ + * affiliationeditorpriv.cpp - MUC affiliation editor's privates + * Copyright (C) 2004 Nolan Eakins - sneakin@semanticgap.com + * + * 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 "im.h" +#include "common.h" +#include "iconwidget.h" +#include "muc.h" +#include "muc_tasks.h" +#include "muc_affileditorpriv.h" +#include "customreasondlg.h" +#include "psilistview.h" +#include "jiddrag.h" + +using namespace XMPP; + +//---------------------------------------------------------------------------- +// AffilEditorAddDlg +//---------------------------------------------------------------------------- + +AffilEditorAddDlg::AffilEditorAddDlg(MUC::Affiliation our_affil, + QWidget *parent, const char *name) + : QDialog(parent, name), m_our_affil(our_affil) +{ + setCaption(tr("Affiliate a JID")); + + QVBoxLayout *layout = new QVBoxLayout(this, 11, 6, "vlayout"); + + QLabel *label1 = new QLabel(tr("Whose &JID do you want to add?"), this); + layout->addWidget(label1); + + le_jid = new QLineEdit(this, "jid"); + layout->addWidget(le_jid); + label1->setBuddy(le_jid); + + QGridLayout *glayout = new QGridLayout(layout, 2, 2); + + QLabel *label2 = new QLabel(tr("&Affiliation:"), this); + glayout->addWidget(label2, 0, 0); + QLabel *label3 = new QLabel(tr("&Reason:"), this); + glayout->addWidget(label3, 1, 0); + + cb_affil = new QComboBox(this, "cb_affil"); + label2->setBuddy(cb_affil); + glayout->addWidget(cb_affil, 0, 1); + + cb_reason = new ReasonComboBox(Reason::Both, this, "reasons"); + label3->setBuddy(cb_reason); + glayout->addWidget(cb_reason, 1, 1); + + QSpacerItem *spacer = new QSpacerItem(16, 32, QSizePolicy::Minimum, QSizePolicy::Expanding); + layout->addItem(spacer); + + // horizontal seperator line + QFrame *horiz_line = new QFrame(this, "horiz_line"); + horiz_line->setFrameShape(QFrame::HLine); + horiz_line->setFrameShadow(QFrame::Sunken); + horiz_line->setFrameShape(QFrame::HLine); + layout->addWidget(horiz_line); + + // the button area + QHBoxLayout *hlayout = new QHBoxLayout(layout, 6, "hlayout"); + spacer = new QSpacerItem(16, 32, QSizePolicy::Expanding, QSizePolicy::Minimum); + hlayout->addItem(spacer); + + pb_cancel = new IconButton(this, "pb_cancel"); + pb_cancel->setText(tr("&Cancel")); + pb_cancel->setIcon("psi/cancel"); + hlayout->addWidget(pb_cancel); + + pb_ok = new IconButton(this, "pb_ok"); + pb_ok->setText(tr("&Ok")); + pb_ok->setIcon("psi/ok"); + pb_ok->setDefault(true); + hlayout->addWidget(pb_ok); + + connect(pb_ok, SIGNAL(clicked()), SLOT(accept())); + connect(pb_cancel, SIGNAL(clicked()), SLOT(reject())); + connect(cb_affil, SIGNAL(activated(int)), SLOT(cbAffilActivated(int))); + + resize(280, 180); + + populateAffils(); +} + +AffilEditorAddDlg::~AffilEditorAddDlg() +{ + // Qt should handle all the widget allocs, + // if not then Psi's in for some real dodo +} + +const Jid AffilEditorAddDlg::jid() const +{ + return Jid(le_jid->text()); +} + +void AffilEditorAddDlg::setAffil(MUC::Affiliation a) +{ + cb_affil->setCurrentItem(affil_table[a]); +} + +MUC::Affiliation AffilEditorAddDlg::affil() const +{ + int current_item = cb_affil->currentItem(); + + MUC::AffilMap::const_iterator it, end = affil_table.end(); + for(it = affil_table.begin(); it != end; ++it) { + if(it.data() == current_item) + return it.key(); + } + + return MUC::AffilUnknown; +} + +QString AffilEditorAddDlg::reasonName() const +{ + return cb_reason->currentReasonName(); +} + +QString AffilEditorAddDlg::reasonText() const +{ + return cb_reason->currentReasonText(); +} + +void AffilEditorAddDlg::accept() +{ + // make sure the JID is valid + const Jid &j(jid()); + if(j.isEmpty() || !j.isValid()) { + // display a message box if it isn't + QMessageBox::information(this, tr("Invalid JID"), + tr("Sorry, but you entered an invalid JID."), + QMessageBox::Ok); + le_jid->setFocus(); + } + else { + QDialog::accept(); + } +} + +void AffilEditorAddDlg::cbAffilActivated(int index) +{ + // if the selected affiliation is greater than None, + // then the reasons should be positive + Reason::Type type = Reason::Negative; + + MUC::AffilMap::iterator it, end = affil_table.end(); + for(it = affil_table.begin(); it != end; ++it) { + if(it.data() == index) { + type = Reason::Positive; + break; + } + } + + cb_reason->setType(type); +} + +void AffilEditorAddDlg::populateAffils() +{ + int index = 0; + + for(MUC::Affiliation a = MUC::AffilOutcast; + a.isValid(); + a++) { + if(m_our_affil != MUC::AffilOwner && + a == MUC::AffilAdmin) + break; + else if(a != MUC::AffilNone) { + cb_affil->insertItem(a.toString(), index); + affil_table[a] = index++; + } + } +} + +//---------------------------------------------------------------------------- +// AffilEditorListViewAffil +//---------------------------------------------------------------------------- +class AffilEditorListViewAffil: public PsiListViewGroup +{ +public: + static const int RTTI = 7001; + + AffilEditorListViewAffil(QListView *parent, MUC::Affiliation affil) + : PsiListViewGroup(parent, affil.toString()), + m_affil(affil) + { + setOpen(true); + setDragEnabled(true); + } + + virtual ~AffilEditorListViewAffil() + { + } + + int rtti() const + { + return RTTI; + } + + MUC::Affiliation affil() const + { + return m_affil; + } + + int compare(QListViewItem *i, int col, bool ascending) const + { + if(i->rtti() != RTTI || col != 0) + return QListViewItem::compare(i, col, ascending); + + AffilEditorListViewAffil *affil_item((AffilEditorListViewAffil *)i); + MUC::Affiliation this_affil = affil(); + MUC::Affiliation i_affil = affil_item->affil(); + + if(this_affil > i_affil) + return -1; + else if(this_affil < i_affil) + return 1; + else + return 0; + } + +private: + MUC::Affiliation m_affil; +}; + +//---------------------------------------------------------------------------- +// AffilEditorListViewItem +//---------------------------------------------------------------------------- +class AffilEditorListViewItem: public PsiListViewItem +{ +public: + static const int RTTI = PsiListViewItem::RTTI_BASE + 2; + + AffilEditorListViewItem(PsiListView *parent, MT_ChangeOccupants::Item &item) + : PsiListViewItem(parent), m_item(item) + { + updateIcon(); + setDragEnabled(true); + } + + AffilEditorListViewItem(PsiListViewItem *parent, MT_ChangeOccupants::Item &item) + : PsiListViewItem(parent), m_item(item) + { + updateIcon(); + setDragEnabled(true); + } + + virtual ~AffilEditorListViewItem() + { + } + + int rtti() const + { + return RTTI; + } + + QString key(int col, bool asc) const + { + if(col == 0) + return m_item.jid().full(); + else if(col == 1) + return m_item.reason(); + else + return PsiListViewItem::key(col, asc); + } + + QString text(int col) const + { + if(col == 0) { + QString t = m_item.jid().full(); + if(changed()) + t = "* " + t; + return t; + } + else if(col == 1) + return m_item.reason(); + else + return QString::null; + } + + void optionsUpdate() + { + updateIcon(); + } + + void updateIcon() + { + QString icon = "psi/muc_affiliation_" + affil().toProtoString(); + setPixmap(0, IconsetFactory::icon(icon)); + } + + inline MT_ChangeOccupants::Item &item() + { + return m_item; + } + + inline const MT_ChangeOccupants::Item &item() const + { + return m_item; + } + + inline const Jid &jid() const + { + return m_item.jid(); + } + + inline MUC::Affiliation affil() const + { + return m_item.affil(); + } + + inline MUC::Affiliation origAffil() const + { + return m_item.origAffil(); + } + + inline void setAffil(MUC::Affiliation a, bool orig = false) + { + m_item.setAffil(a, orig); + } + + inline void setReason(const QString &reason) + { + m_item.setReason(reason); + + // in case reasons are used for sorting + QListView *lv = listView(); + if(lv) { + int sc = lv->sortColumn(); + if(sc == 1) // resort the parent + parent()->sort(); + else + lv->triggerUpdate(); + } + } + + inline bool changed() const + { + return m_item.changed(); + } + + inline const QString reason() const + { + return m_item.reason(); + } + + virtual void paintCell(QPainter *p, const QColorGroup &cg, + int column, int width, int align) + { + if(changed()) { + QFont font(p->font()); + font.setBold(true); + font.setItalic(true); + p->setFont(font); + } + + PsiListViewItem::paintCell(p, cg, column, width, align); + } + +private: + MT_ChangeOccupants::Item &m_item; +}; + +//---------------------------------------------------------------------------- +// AffilEditorListView +//---------------------------------------------------------------------------- +class AffilEditorListView::Private +{ +public: + MUC::AffilMap affil_items; +}; + +AffilEditorListView::AffilEditorListView(MUC::Affiliation affil, + QWidget *parent, const char *name) + : PsiListView(parent, name) +{ + d = new Private; + + addColumn(tr("JID"), 200); + addColumn(tr("Reason")); + + setColumnWidthMode(2, Maximum); + setResizeMode(LastColumn); + setSelectionMode(Extended); + setAllColumnsShowFocus(true); + setShowSortIndicator(true); + setTreeStepSize(4); + + setAcceptDrops(true); + viewport()->setAcceptDrops(true); + + for(MUC::Affiliation a = MUC::AffilOutcast; + a.isValid(); + a++) { + if(affil == MUC::AffilAdmin && a == MUC::AffilAdmin) + break; + d->affil_items[a] = new AffilEditorListViewAffil(this, a); + } +} + +AffilEditorListView::~AffilEditorListView() +{ + delete d; +} + +AffilEditorListViewAffil *AffilEditorListView::findAffilItem(MUC::Affiliation a) +{ + if(a >= MUC::AffilOutcast) + return d->affil_items[a]; + else + return NULL; +} + +template +void appendIfNotContainsRef(QPtrList &list, T *t) +{ + if(list.containsRef(t) == 0) + list.append(t); +} + +void AffilEditorListView::selectedItems(QPtrList &items) +{ + for(QListViewItemIterator it(this, QListViewItemIterator::Selected); + it.current(); + ++it) { + int rtti = (*it)->rtti(); + + switch(rtti) { + case AffilEditorListViewItem::RTTI: + appendIfNotContainsRef(items, (AffilEditorListViewItem *)it.current()); + //items.append((AffilEditorListViewItem *)it.current()); + break; + case AffilEditorListViewAffil::RTTI: + for(QListViewItem *child = (*it)->firstChild(); + child != NULL; + child = child->nextSibling()) { + appendIfNotContainsRef(items, + (AffilEditorListViewItem *)child); + } + break; + } + } +} + +void AffilEditorListView::deselectAffils() +{ + for(QListViewItem *i = this->firstChild(); + i != NULL; + i = i->nextSibling()) { + i->setSelected(false); + } +} + +void AffilEditorListView::selectedAffils(QPtrList &affils) +{ + for(QListViewItemIterator it(this, QListViewItemIterator::Selected); + it.current(); + ++it) { + int rtti = (*it)->rtti(); + + switch(rtti) { + case AffilEditorListViewItem::RTTI: + appendIfNotContainsRef(affils, (AffilEditorListViewAffil *)it.current()->parent()); + break; + case AffilEditorListViewAffil::RTTI: + appendIfNotContainsRef(affils, (AffilEditorListViewAffil *)it.current()); + break; + } + } +} + +void AffilEditorListView::selectItems(QPtrList &items) +{ + for(QPtrListIterator it(items); + it.current(); + ++it) { + (*it)->setSelected(true); + } +} + +void AffilEditorListView::selectChangedItems(MUC::Affiliation orig_affil) +{ + for(QListViewItemIterator it(this); + it.current(); + ++it) { + QListViewItem *lvi = it.current(); + if(lvi->rtti() == AffilEditorListViewItem::RTTI) { + AffilEditorListViewItem *i = (AffilEditorListViewItem *)lvi; + if(i->origAffil() == orig_affil && + i->changed()) { + setSelected(i, true); + } + } + } +} + +void AffilEditorListView::reset() +{ + // although reset doesn't reset the selection, it needs + // to reset the current item because it'll possibly get set + // to unselected item since some have moved + setCurrentItem(NULL); + + // get any items that have changed + QPtrList items; + for(QListViewItemIterator it(this); + it.current(); + ++it) { + QListViewItem *lvi = it.current(); + if(lvi->rtti() == AffilEditorListViewItem::RTTI) { + AffilEditorListViewItem *i = (AffilEditorListViewItem *)lvi; + if(i->changed()) { + items.append(i); + } + } + } + + // reparent each item to its original affil + for(QPtrListIterator it(items); + it.current(); + ++it) { + AffilEditorListViewItem *i = it.current(); + AffilEditorListViewAffil *ai = findAffilItem(i->origAffil()); + if(ai != NULL) { + i->parent()->takeItem(i); + ai->insertItem(i); + + // reset the affil so it doesn't appear changed + i->setAffil(i->origAffil()); + } + } + + emit(selectionChanged()); +} + +class AELV_DragFunk +{ +public: + JidDrag::List jids; + + void operator() (const AffilEditorListViewItem *item) + { + jids << item->jid(); + } +}; + +QDragObject *AffilEditorListView::dragObject() +{ + QPtrList items; + selectedItems(items); + + AELV_DragFunk funk(std::for_each(items.begin(), items.end(), AELV_DragFunk())); + + return new JidDrag(funk.jids, this); +} + +//---------------------------------------------------------------------------- +// AffilEditorMainWidget +//---------------------------------------------------------------------------- +AffilEditorMainWidget::AffilEditorMainWidget(MUC::Affiliation affil, + QWidget *parent, const char *name) + : QWidget(parent, name), our_affil(affil) +{ + QHBoxLayout *layout = new QHBoxLayout(this, 0, 6, "layout"); + + lv = new AffilEditorListView(affil, this, "lv"); + lv->setFocus(); + layout->addWidget(lv); + + QVBoxLayout *right = new QVBoxLayout(layout, 6, "right_layout"); + + QGroupBox *gb_edit = new QGroupBox(tr("Edit JID"), this, "gb_edit"); + gb_edit->setColumnLayout(0, Qt::Vertical); + gb_edit->layout()->setSpacing(6); + gb_edit->layout()->setMargin(11); + right->addWidget(gb_edit); + + QGridLayout *gb_layout = new QGridLayout(gb_edit->layout()); + + lb_affil = new QLabel(tr("Affi&liation:"), gb_edit); + gb_layout->addWidget(lb_affil, 0, 0); + + cb_affils = new QComboBox(gb_edit, "cb_affils"); + cb_affils->setEnabled(false); + for(MUC::Affiliation a = MUC::AffilOutcast; + a.isValid(); + a++) { + if(affil == MUC::AffilAdmin && a == MUC::AffilAdmin) + break; + + cb_affils->insertItem(a.toString(), a.index()); + } + + gb_layout->addWidget(cb_affils, 0, 1); + lb_affil->setBuddy(cb_affils); + + lb_reason = new QLabel(tr("&Reason:"), gb_edit); + gb_layout->addWidget(lb_reason, 1, 0); + + cb_reasons = new ReasonComboBox(Reason::Negative, gb_edit, "cb_reasons"); + cb_reasons->setEnabled(false); + gb_layout->addWidget(cb_reasons, 1, 1); + lb_reason->setBuddy(cb_reasons); + + lb_del_note = new QLabel(tr("

To delete a JID, set its affiliation to 'None'.

"), this); + right->addWidget(lb_del_note); + + QSpacerItem *spacer = new QSpacerItem(16, 32, QSizePolicy::Minimum, QSizePolicy::Expanding); + right->addItem(spacer); + + setTabOrder(lv, cb_affils); + setTabOrder(cb_affils, cb_reasons); + + connect(lv, SIGNAL(selectionChanged()), SLOT(lvSelectionChanged())); + connect(cb_affils, SIGNAL(activated(int)), SLOT(cbAffilsActivated(int))); + connect(cb_reasons, SIGNAL(activated(const QString &, const QString &)), + SLOT(cbReasonsActivated(const QString &, const QString &))); +} + +AffilEditorMainWidget::~AffilEditorMainWidget() +{ +} + +void AffilEditorMainWidget::ourAffilChanged(MUC::Affiliation) +{ +} + +MUC::Affiliation AffilEditorMainWidget::selectedAffil() const +{ + return static_cast(cb_affils->currentItem() + 1); +} + +class AEMW_UpdateReasonsFunk +{ +public: + bool have_items_changed, same_reason; + Reason::Type reason_type; + QString last_reason; + + AEMW_UpdateReasonsFunk() + : have_items_changed(false), + same_reason(true), + reason_type(Reason::Both) + { + } + + void operator () (AffilEditorListViewItem *item) + { + MUC::Affiliation affil = item->affil(), orig_affil = item->origAffil(); + + if(item->changed()) { + Reason::Type type = Reason::Both; + if(affil > orig_affil) + type = Reason::Positive; + else if(affil < orig_affil) + type = Reason::Negative; + + // only the 1st item should enter this block + if(have_items_changed == false) { + reason_type = type; + } + else { + if((reason_type == Reason::Positive && + type != Reason::Positive) || + (reason_type == Reason::Negative && + type != Reason::Negative)) + reason_type = Reason::Both; + + if(last_reason != item->reason()) + same_reason = false; + } + + last_reason = item->reason(); + have_items_changed = true; + } + } +}; + +void AffilEditorMainWidget::updateReasons(QPtrList &items) +{ + int num_items = items.count(); + + // if items are selected + if(num_items) { + AffilEditorListViewItem *first_item = items.first(); + + // if any items have changed + AEMW_UpdateReasonsFunk funk(std::for_each(items.begin(), items.end(), + AEMW_UpdateReasonsFunk())); + + // enable the reason CB if the items have changed + cb_reasons->setEnabled(funk.have_items_changed); + + // set the available reasons + if(funk.have_items_changed) { + cb_reasons->setType(funk.reason_type); + } + + // if all the items have the same reason, then set the CB + if(funk.same_reason) + cb_reasons->setCurrentItem(first_item->reason(), false); + // otherwise set the CB to the separator + else + cb_reasons->setCurrentItem((int)ReasonComboBox::Separator); + } +} + +void AffilEditorMainWidget::lvSelectionChanged() +{ + QPtrList items; + lv->selectedItems(items); + + int num_items = items.count(); + + // if items are selected + if(num_items) { + updateReasons(items); + + // enable the affil CB + cb_affils->setEnabled(true); + } + // else disable affil & reason CBs + else { + cb_affils->setEnabled(false); + cb_reasons->setEnabled(false); + } + + // if one affil is selected then update the affil CB + // NOTE: the affil CB must be kept updated to ensure selectedAffil() + // always returns the correct affil + QPtrList affils; + lv->selectedAffils(affils); + if(affils.count() == 1) { + // FIXME: decouple + cb_affils->setCurrentItem(affils.first()->affil().index()); + } +} + +void AffilEditorMainWidget::cbAffilsActivated(int index) +{ + QPtrList items; + lv->selectedItems(items); + + MUC::Affiliation affil = MUC::Affiliation(index); + AffilEditorListViewAffil *parent = lv->findAffilItem(affil); + + for(QPtrListIterator it(items); + it.current(); + ++it) { + AffilEditorListViewItem *lv_item = it.current(); + lv_item->parent()->takeItem(lv_item); + parent->insertItem(lv_item); + + lv_item->setAffil(affil); + + // unset the reason if the item is back to its original + if(lv_item->changed() == false) + lv_item->setReason(QString::null); + + // set the item to current after the affiliation has been updated + lv->setCurrentItem(lv_item); + } + + lv->deselectAffils(); + lv->selectItems(items); + updateReasons(items); +} + +void AffilEditorMainWidget::cbReasonsActivated(const QString &name, const QString &body) +{ + Q_UNUSED(name); + + QPtrList items; + lv->selectedItems(items); + Q_ASSERT(!items.isEmpty()); + + for(QPtrListIterator it(items); + it.current(); + ++it) { + AffilEditorListViewItem *lv_item = it.current(); + + if(lv_item->changed()) { + lv_item->setReason(body); + } + } +} + +void AffilEditorMainWidget::optionsUpdate() +{ + lv->optionsUpdate(); + cb_reasons->optionsUpdate(); +} + +void AffilEditorMainWidget::addItems(MT_ChangeOccupants::ItemList &list) +{ + for(MT_ChangeOccupants::ItemList::iterator it(list.begin()); + it != list.end(); + ++it) { + MT_ChangeOccupants::Item &item(*it); + addItem(item); + } +} + +void AffilEditorMainWidget::addItem(MT_ChangeOccupants::Item &item) +{ + AffilEditorListViewAffil *parent = lv->findAffilItem(item.affil()); + Q_ASSERT(parent != NULL); + (void)new AffilEditorListViewItem(parent, item); +} + +void AffilEditorMainWidget::reset() +{ + lv->reset(); +} + +void AffilEditorMainWidget::clearSelection() +{ + lv->clearSelection(); +} + +void AffilEditorMainWidget::selectChangedItems(MUC::Affiliation a) +{ + lv->selectChangedItems(a); +} diff -Naur psi-0.10-orig/src/muc_affileditorpriv.h psi-0.10/src/muc_affileditorpriv.h --- psi-0.10-orig/src/muc_affileditorpriv.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/muc_affileditorpriv.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,137 @@ +/* $Header$ + * affiliationeditorprivate.h - Header for the affiliation editor's privates + * Copyright (C) 2004 Nolan Eakins - sneakin@semanticgap.com + * + * 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 MUC_AFFILEDITORPRIV_H +#define MUC_AFFILEDITORPRIV_H + +#include +#include +#include +#include +#include +#include "im.h" +#include "muc.h" +#include "muc_tasks.h" +#include "psilistview.h" + +using namespace XMPP; +class AffilEditorListViewAffil; +class AffilEditorListViewItem; +class IconButton; +class ReasonComboBox; + +class QDragObject; + +class AffilEditorAddDlg: public QDialog +{ + Q_OBJECT; + +public: + AffilEditorAddDlg(MUC::Affiliation, QWidget *parent = NULL, const char *name = NULL); + virtual ~AffilEditorAddDlg(); + + const Jid jid() const; + + void setAffil(MUC::Affiliation a); + MUC::Affiliation affil() const; + + QString reasonText() const; + QString reasonName() const; + +public slots: + virtual void accept(); + +protected slots: + void cbAffilActivated(int); + +protected: + void populateAffils(); + +private: + MUC::Affiliation m_our_affil; + + QLineEdit *le_jid; + QComboBox *cb_affil; + ReasonComboBox *cb_reason; + IconButton *pb_ok, *pb_cancel; + MUC::AffilMap affil_table; +}; + +class AffilEditorListView: public PsiListView +{ + Q_OBJECT; + +public: + AffilEditorListView(MUC::Affiliation, QWidget *parent, const char *name); + virtual ~AffilEditorListView(); + + AffilEditorListViewAffil *findAffilItem(MUC::Affiliation); + + void selectedItems(QPtrList &); + void selectedAffils(QPtrList &); + void deselectAffils(); + void selectItems(QPtrList &); + void selectChangedItems(MUC::Affiliation); + + void reset(); + +protected: + QDragObject *dragObject(); + +private: + class Private; + Private *d; +}; + +class AffilEditorMainWidget: public QWidget +{ + Q_OBJECT; + +public: + AffilEditorMainWidget(MUC::Affiliation affil, + QWidget *parent, const char *name); + virtual ~AffilEditorMainWidget(); + + void addItems(MT_ChangeOccupants::ItemList &); + void addItem(MT_ChangeOccupants::Item &); + + void clearSelection(); + void selectChangedItems(MUC::Affiliation); + + MUC::Affiliation selectedAffil() const; + + void updateReasons(QPtrList &); + +public slots: + void reset(); + void ourAffilChanged(MUC::Affiliation); + void lvSelectionChanged(); + void cbAffilsActivated(int); + void cbReasonsActivated(const QString &, const QString &); + void optionsUpdate(); + +private: + MUC::Affiliation our_affil; + AffilEditorListView *lv; + QComboBox *cb_affils; + ReasonComboBox *cb_reasons; + QGroupBox *gb_edit; + QLabel *lb_del_note, *lb_affil, *lb_reason; +}; + +#endif /* MUC_AFFILEDITORPRIV_H */ diff -Naur psi-0.10-orig/src/options/opt_general_groupchat.ui psi-0.10/src/options/opt_general_groupchat.ui --- psi-0.10-orig/src/options/opt_general_groupchat.ui 2005-08-21 17:44:28.000000000 +0000 +++ psi-0.10/src/options/opt_general_groupchat.ui 2006-01-14 00:43:21.000000000 +0000 @@ -1,4 +1,4 @@ - + GeneralGroupchatUI @@ -8,8 +8,8 @@ 0 0 - 420 - 186 + 496 + 390 @@ -22,313 +22,967 @@ 0 - + - layout5 + tabWidget2 - + - unnamed + tab - + + General + + - ck_gcHighlights + unnamed - - Enable word highlighting - - - - - alertGroupBox - - - Word Highlighting - - - Highlight lines and alert the user if this text is included in chat - - + - unnamed + layout17 - - - lb_highlightWords - - - - 7 - 7 - 0 - 0 - - - - Words which will cause a notification in group chat. - - - This is the list of words which when said in group chat will cause the user to be notified. - - - + - le_newHighlightWord - - - LineEditPanel - - - Sunken - - - Type a new word here and press "Add Word" for it to be added to the highlight list - - - Type a new word here and press "Add Word" for it to be added to the highlight list + unnamed - - + + + ck_showStatusChanges + + + Show &status changes? + + + Alt+S + + + Check this if you want groupchats to tell you when an occupant goes away or comes back. + + + + + ck_showJoins + + + Show &joins and parts? + + + Alt+J + + + Check this if you want groupchats to tell you when someone enters or leaves the room. + + + + + ck_showRoleAffilChanges + + + Show role and &affiliation changes? + + + Alt+A + + + Check this if you want groupchats to tell you when an occupant's role or affiliation changes. + + + + + + + groupBox3 + + + Discussion History Management + + + Check this if you want to limit the discussion history to a maximum number of characters. + + - layout1 + unnamed - + - unnamed + layout26 - + - spacer3 - - - Horizontal - - - Expanding + unnamed - - - 40 - 20 - - - - + + + ck_histMaxChars + + + Max &Characters: + + + Alt+C + + + + + spacer11 + + + Horizontal + + + Expanding + + + + 21 + 20 + + + + + + sb_histMaxChars + + + false + + + + + + + + + + layout28 + + - pb_addHighlightWord - - - Add + unnamed - - Adds the new word to the list - - - Adds the new word to the list - - - + + + ck_histSeconds + + + Se&conds: + + + Alt+C + + + Check this if you want to limit the discussion history to <i>N</i> seconds in the past. + + + + + spacer13 + + + Horizontal + + + Expanding + + + + 41 + 20 + + + + + + sb_histSeconds + + + false + + + + + + + layout27 + + - pb_removeHighlightWord - - - Remove - - - Removes the selected word from the highlight list - - - Removes the selected word from the highlight list + unnamed - - + + + ck_histMaxStanzas + + + Max &Messages + + + Alt+M + + + Check this if you want to limit the discussion history to <i>N</i> messages. + + + + + spacer12 + + + Horizontal + + + Expanding + + + + 31 + 20 + + + + + + sb_histMaxStanzas + + + false + + + + + + + layout44 + + - spacer4 - - - Horizontal + unnamed - - Expanding + + + ck_histSince + + + &Since: + + + Alt+S + + + Check this if you want to limit the discussion history to anything after a certain date. + + + + + spacer27 + + + Horizontal + + + Expanding + + + + 41 + 20 + + + + + + dte_histSince + + + false + + + + + + + + + groupBox9 + + + Reasons + + + + unnamed + + + + layout15 + + + + unnamed - - - 40 - 20 - + + + + Name + + + true + + + true + + + + + Type + + + true + + + true + + + + lv_reasons + + + + 3 + 7 + 0 + 0 + + + + + 150 + 0 + + + + true + + + true + + + This is your list of stored reasons. + + + + + layout13 + + + + unnamed + + + + pb_addReason + + + &Add + + + Alt+A + + + Create a new reason + + + + + pb_rmReason + + + false + + + &Remove + + + Alt+R + + + Deletes the selected reason. + + + + + + + + + layout32 + + + + unnamed - - - - - - - - - - layout4 - - + + + layout18 + + + + unnamed + + + + textLabel2 + + + Name: + + + + + spacer10 + + + Horizontal + + + Expanding + + + + 51 + 20 + + + + + + le_reasonName + + + false + + + + 1 + 0 + 0 + 0 + + + + + 200 + 0 + + + + + + + Type the name for the selected reason here. + + + + + + + layout17 + + + + unnamed + + + + textLabel4 + + + Text: + + + + + spacer11_2 + + + Horizontal + + + Expanding + + + + 61 + 20 + + + + + + le_reasonContent + + + false + + + + 1 + 0 + 0 + 0 + + + + + 200 + 0 + + + + Type what you want the reason to say here. + + + + + + + layout30 + + + + unnamed + + + + textLabel1 + + + Used for: + + + + + spacer23 + + + Horizontal + + + Expanding + + + + 31 + 20 + + + + + + + Positive Operations + + + + + Negative Operations + + + + + Both + + + + cb_reasonUsedFor + + + false + + + + 1 + 0 + 0 + 0 + + + + + 200 + 0 + + + + + + + + spacer9 + + + Vertical + + + Expanding + + + + 20 + 28 + + + + + + + + + + - unnamed + TabPage - - - ck_gcNickColoring - - - Enable Nick Coloring - - - + + Highlighting + + - ncGroupBox + unnamed - - Nick Coloring - - + - unnamed + layout5 - - - lb_nickColors - - - Colors used for nicks in groupchat - - - This is the list of colours with which nicks will be drawn in group chat. - - - + - layout2 + unnamed - + - unnamed + ck_gcHighlights - - - le_newNickColor - - - Type a colourcode here, or press the pallette to the right - - - Type a colourcode here, or press the pallette to the right - - - + + Enable word highlighting + + + + + alertGroupBox + + + Word Highlighting + + + Highlight lines and alert the user if this text is included in chat + + - pb_nickColor + unnamed - - - 4 - 4 - 0 - 0 - - - - - - - - - + + + lb_highlightWords + + + + 7 + 7 + 0 + 0 + + + + Words which will cause a notification in group chat. + + + This is the list of words which when said in group chat will cause the user to be notified. + + + + + le_newHighlightWord + + + LineEditPanel + + + Sunken + + + Type a new word here and press "Add Word" for it to be added to the highlight list + + + Type a new word here and press "Add Word" for it to be added to the highlight list + + + + + layout1 + + + + unnamed + + + + spacer3 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + pb_addHighlightWord + + + Add + + + Adds the new word to the list + + + Adds the new word to the list + + + + + pb_removeHighlightWord + + + Remove + + + Removes the selected word from the highlight list + + + Removes the selected word from the highlight list + + + + + spacer4 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + + + layout4 + + - layout3 + unnamed - + - unnamed + ck_gcNickColoring - - - spacer5 - - - Horizontal - - - Expanding - - - - 40 - 20 - - - - - - pb_addNickColor - - - Add - - - Adds the color to the list - - - Adds the new color to the list - - - - - pb_removeNickColor - - - Remove - - - Removes the selected color from the list - - - Removes the selected color from the list - - - + + Enable Nick Coloring + + + + + ncGroupBox + + + Nick Coloring + + - spacer7 - - - Horizontal + unnamed - - Expanding - - - - 40 - 20 - - - - - - - - + + + lb_nickColors + + + Colors used for nicks in groupchat + + + This is the list of colours with which nicks will be drawn in group chat. + + + + + layout2 + + + + unnamed + + + + le_newNickColor + + + Type a colourcode here, or press the pallette to the right + + + Type a colourcode here, or press the pallette to the right + + + + + pb_nickColor + + + + 4 + 4 + 0 + 0 + + + + + + + + + + + layout3 + + + + unnamed + + + + spacer5 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + pb_addNickColor + + + Add + + + Adds the color to the list + + + Adds the new color to the list + + + + + pb_removeNickColor + + + Remove + + + Removes the selected color from the list + + + Removes the selected color from the list + + + + + spacer7 + + + Horizontal + + + Expanding + + + + 40 + 20 + + + + + + + + + + + + ck_showJoins + ck_histMaxChars + sb_histMaxChars + ck_histSeconds + sb_histSeconds + ck_histMaxStanzas + sb_histMaxStanzas + ck_histSince + dte_histSince + lv_reasons + pb_addReason + pb_rmReason + le_reasonName + le_reasonContent ck_gcHighlights lb_highlightWords le_newHighlightWord - pb_removeHighlightWord pb_addHighlightWord + pb_removeHighlightWord ck_gcNickColoring lb_nickColors le_newNickColor pb_nickColor - pb_removeNickColor pb_addNickColor + pb_removeNickColor + tabWidget2 + + qdatetimeedit.h + qspinbox.h + + + class QSpinBox + class QDateTimeEdit + diff -Naur psi-0.10-orig/src/options/opt_groupchat.cpp psi-0.10/src/options/opt_groupchat.cpp --- psi-0.10-orig/src/options/opt_groupchat.cpp 2005-08-21 17:44:28.000000000 +0000 +++ psi-0.10/src/options/opt_groupchat.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -12,7 +12,11 @@ #include #include #include - +#include +#include +#include +#include +#include #include "opt_general_groupchat.h" //---------------------------------------------------------------------------- @@ -38,6 +42,11 @@ w = new GeneralGroupchatUI(); GeneralGroupchatUI *d = (GeneralGroupchatUI *)w; + connect(d->ck_histMaxChars, SIGNAL(toggled(bool)), d->sb_histMaxChars, SLOT(setEnabled(bool))); + connect(d->ck_histMaxStanzas, SIGNAL(toggled(bool)), d->sb_histMaxStanzas, SLOT(setEnabled(bool))); + connect(d->ck_histSeconds, SIGNAL(toggled(bool)), d->sb_histSeconds, SLOT(setEnabled(bool))); + connect(d->ck_histSince, SIGNAL(toggled(bool)), d->dte_histSince, SLOT(setEnabled(bool))); + connect(d->pb_nickColor, SIGNAL(clicked()), SLOT(chooseGCNickColor())); connect(d->lb_nickColors, SIGNAL(highlighted(int)), SLOT(selectedGCNickColor())); @@ -47,6 +56,14 @@ connect(d->pb_addNickColor, SIGNAL(clicked()), SLOT(addGCNickColor())); connect(d->pb_removeNickColor, SIGNAL(clicked()), SLOT(removeGCNickColor())); + // Reason controls + connect(d->pb_addReason, SIGNAL(clicked()), SLOT(addReason())); + connect(d->pb_rmReason, SIGNAL(clicked()), SLOT(rmReason())); + connect(d->lv_reasons, SIGNAL(selectionChanged(QListViewItem *)), SLOT(reasonSelected(QListViewItem *))); + connect(d->lv_reasons, SIGNAL(currentChanged(QListViewItem *)), SLOT(reasonSelected(QListViewItem *))); + + d->lv_reasons->setColumnWidthMode(0, QListView::Maximum); + // TODO: add QWhatsThis for all controls on widget return w; @@ -58,6 +75,31 @@ return; GeneralGroupchatUI *d = (GeneralGroupchatUI *)w; + + opt->showJoins = d->ck_showJoins->isChecked(); + opt->showStatusChanges = d->ck_showStatusChanges->isChecked(); + opt->showRoleAffilChanges = d->ck_showRoleAffilChanges->isChecked(); + + if(d->ck_histMaxChars->isChecked()) + opt->gcHistMaxChars = d->sb_histMaxChars->value(); + else + opt->gcHistMaxChars = -1; + + if(d->ck_histMaxStanzas->isChecked()) + opt->gcHistMaxStanzas = d->sb_histMaxStanzas->value(); + else + opt->gcHistMaxStanzas = -1; + + if(d->ck_histSeconds->isChecked()) + opt->gcHistSeconds = d->sb_histSeconds->value(); + else + opt->gcHistSeconds = -1; + + if(d->ck_histSince->isChecked()) + opt->gcHistSince = d->dte_histSince->dateTime(); + else + opt->gcHistSince = QDateTime(); + opt->gcHighlighting = d->ck_gcHighlights->isChecked(); opt->gcNickColoring = d->ck_gcNickColoring->isChecked(); @@ -71,6 +113,8 @@ for (i = 0; i < (int)d->lb_nickColors->count(); i++) colors << d->lb_nickColors->item(i)->text(); opt->gcNickColors = colors; + + opt->reasons = reasons; } void OptionsTabGroupchat::restoreOptions(const Options *opt) @@ -80,6 +124,46 @@ GeneralGroupchatUI *d = (GeneralGroupchatUI *)w; + d->ck_showJoins->setChecked(opt->showJoins); + d->ck_showStatusChanges->setChecked(opt->showStatusChanges); + d->ck_showRoleAffilChanges->setChecked(opt->showRoleAffilChanges); + + if(opt->gcHistMaxChars > -1) { + d->ck_histMaxChars->setChecked(true); + d->sb_histMaxChars->setValue(opt->gcHistMaxChars); + } + else { + d->sb_histMaxChars->setValue(0); + d->ck_histMaxChars->setChecked(false); + } + + if(opt->gcHistMaxStanzas > -1) { + d->ck_histMaxStanzas->setChecked(true); + d->sb_histMaxStanzas->setValue(opt->gcHistMaxStanzas); + } + else { + d->sb_histMaxStanzas->setValue(0); + d->ck_histMaxStanzas->setChecked(false); + } + + if(opt->gcHistSeconds > -1) { + d->ck_histSeconds->setChecked(true); + d->sb_histSeconds->setValue(opt->gcHistSeconds); + } + else { + d->sb_histSeconds->setValue(0); + d->ck_histSeconds->setChecked(false); + } + + if(opt->gcHistSince.isValid()) { + d->ck_histSince->setChecked(true); + d->dte_histSince->setDateTime(opt->gcHistSince); + } + else { + d->ck_histSince->setChecked(false); + d->dte_histSince->setDateTime(QDateTime::currentDateTime()); + } + // no need to call dataChanged() when these widgets are modified disconnect(d->le_newNickColor, SIGNAL(textChanged(const QString &)), 0, 0); disconnect(d->le_newHighlightWord, SIGNAL(textChanged(const QString &)), 0, 0); @@ -99,6 +183,18 @@ d->le_newHighlightWord->setText(""); d->le_newNickColor->setText("#FFFFFF"); + + // populate reason list + { + reasons = opt->reasons; + + ReasonMap::const_iterator it(reasons.begin()), + end(reasons.end()); + for(; it != end; ++it) { + const Reason &r(it.data()); + createReasonItem(it.key(), r); + } + } } static QPixmap name2color(QString name) @@ -196,3 +292,171 @@ GeneralGroupchatUI *d = (GeneralGroupchatUI *)w; d->pb_nickColor->setPixmap( name2color(d->le_newNickColor->text()) ); } + +void OptionsTabGroupchat::addReason() +{ + bool ok = false, contains = false; + QString name; + QListViewItem *item = NULL; + QListView *list = ui()->lv_reasons; + + // loop until a unique name is found + do { + name = QInputDialog::getText(tr("Add Reason"), tr("What do you want to call this reason?"), + QLineEdit::Normal, QString::null, &ok, w); + + // return if cancelled + if(!ok) + return; + + contains = reasons.contains(name); + if(contains) { + int result = QMessageBox::information(w, tr("Add Reason"), + tr("A reason already exists with this name. Did you want to try entering another name?"), + QMessageBox::Yes, QMessageBox::No); + + // didn't want to try again, so select the item and return + if(result == QMessageBox::No) { + item = list->findItem(name, 0); + Q_ASSERT(item != NULL); + + list->setSelected(item, true); + reasonSelected(item); + return; + } + + continue; + } + } while(contains); + + // create an empty reason + Reason reason; + reason.text = QString(""); + reason.type = Reason::Both; + reasons.insert(name, reason); + + item = createReasonItem(name, reason); + list->setSelected(item, true); + + // update everything else + reasonSelected(item); + emit dataChanged(); +} + +void OptionsTabGroupchat::rmReason() +{ + QListViewItem *item = ui()->lv_reasons->selectedItem(); + if(!item) { + QMessageBox::information(w, + tr("Remove Reason"), + tr("Sorry, but you must selected a reason first."), + QMessageBox::Ok); + return; + } + + int result = QMessageBox::question(w, + tr("Remove Reason"), + tr("Are you sure you want to delete the reason \"%1\"?").arg(item->text(0)), + QMessageBox::Yes, QMessageBox::No); + if(result == QMessageBox::Yes) { + QString name(item->text(0)); + Q_ASSERT(reasons.contains(name)); + + reasons.remove(name); + delete item; + emit dataChanged(); + } +} + +void OptionsTabGroupchat::reasonSelected(QListViewItem *item) +{ + bool item_not_null = (item != NULL); + + ui()->le_reasonName->disconnect(); + ui()->le_reasonContent->disconnect(); + ui()->cb_reasonUsedFor->disconnect(); + + if(item_not_null) { + QString name(item->text(0)); + Reason &r = reasons[item->text(0)]; + + ui()->le_reasonName->setText(name); + ui()->le_reasonContent->setText(r.text); + + ui()->cb_reasonUsedFor->setCurrentItem((int)r.type); + + connect(ui()->le_reasonName, SIGNAL(textChanged(const QString &)), SLOT(reasonNameChanged(const QString &))); + connect(ui()->le_reasonContent, SIGNAL(textChanged(const QString &)), SLOT(reasonContentChanged(const QString &))); + connect(ui()->cb_reasonUsedFor, SIGNAL(activated(int)), SLOT(reasonUsedFor(int))); + } + else { + ui()->le_reasonName->setText(QString::null); + ui()->le_reasonContent->setText(QString::null); + } + + ui()->pb_rmReason->setEnabled(item_not_null); + ui()->le_reasonName->setEnabled(item_not_null); + ui()->le_reasonContent->setEnabled(item_not_null); + ui()->cb_reasonUsedFor->setEnabled(item_not_null); +} + +void OptionsTabGroupchat::reasonNameChanged(const QString &text) +{ + QListViewItem *item = ui()->lv_reasons->currentItem(); + if(item) { + QString old_name = item->text(0); + item->setText(0, text); + + ReasonMap::iterator it = reasons.find(old_name); + Reason r = it.data(); + reasons.remove(it); + reasons.insert(text, r); + + emit dataChanged(); + } +} + +void OptionsTabGroupchat::reasonContentChanged(const QString &text) +{ + QListViewItem *item = ui()->lv_reasons->currentItem(); + if(item) { + ReasonMap::iterator it = reasons.find(item->text(0)); + Reason &r = it.data(); + r.text = text; + + emit dataChanged(); + } +} + +QString OptionsTabGroupchat::reasonTypeToCol(const Reason &r) +{ + switch(r.type) { + case Reason::Positive: + return tr("P"); + case Reason::Negative: + return tr("N"); + case Reason::Both: + return tr("P/N"); + default: + return tr("---"); + } +} + +void OptionsTabGroupchat::reasonUsedFor(int n) +{ + QListViewItem *item = ui()->lv_reasons->currentItem(); + if(item) { + ReasonMap::iterator it = reasons.find(item->text(0)); + Reason &r = it.data(); + r.type = (Reason::Type)n; + item->setText(1, reasonTypeToCol(r)); + + emit dataChanged(); + } +} + +QListViewItem *OptionsTabGroupchat::createReasonItem(const QString &name, const Reason &r) +{ + QString pos_neg = reasonTypeToCol(r); + return new QListViewItem(ui()->lv_reasons, name, pos_neg); +} diff -Naur psi-0.10-orig/src/options/opt_groupchat.h psi-0.10/src/options/opt_groupchat.h --- psi-0.10-orig/src/options/opt_groupchat.h 2005-08-21 17:44:28.000000000 +0000 +++ psi-0.10/src/options/opt_groupchat.h 2006-01-14 00:43:21.000000000 +0000 @@ -1,10 +1,12 @@ #ifndef OPT_GROUPCHAT_H #define OPT_GROUPCHAT_H +#include "common.h" #include "optionstab.h" class QWidget; -struct Options; +class GeneralGroupchatUI; +class QListViewItem; class OptionsTabGroupchat : public OptionsTab { @@ -29,8 +31,22 @@ void displayGCNickColor(); void selectedGCNickColor(); + void addReason(); + void rmReason(); + void reasonSelected(QListViewItem *); + void reasonNameChanged(const QString &); + void reasonContentChanged(const QString &); + void reasonUsedFor(int); + +protected: + inline GeneralGroupchatUI *ui() { return (GeneralGroupchatUI *)w; } + private: + QString reasonTypeToCol(const Reason &); + QListViewItem *createReasonItem(const QString &, const Reason &); + QWidget *w, *dlg; + ReasonMap reasons; }; #endif diff -Naur psi-0.10-orig/src/profiles.h psi-0.10/src/profiles.h --- psi-0.10-orig/src/profiles.h 2005-08-21 17:44:34.000000000 +0000 +++ psi-0.10/src/profiles.h 2006-01-14 00:43:21.000000000 +0000 @@ -24,10 +24,13 @@ #include #include #include +#include"xmpp.h" #include"im.h" #include"proxy.h" #include"common.h" +using XMPP::Jid; + class UserAccount { public: @@ -82,7 +85,25 @@ Options prefs; QRect mwgeom; - QStringList recentGCList; + + struct GroupChat { + GroupChat(const Jid &j, const QString &pw, bool save) + : jid(j), save_pword(save) { + if(save) + pword = pw; + } + GroupChat() + : save_pword(false) { + } + + Jid jid; + QString pword; + bool save_pword; + }; + + typedef QValueList GCList; + QValueList recentGCList; + QStringList recentBrowseList; QString lastStatusString; bool useSound; diff -Naur psi-0.10-orig/src/psiaccount.cpp psi-0.10/src/psiaccount.cpp --- psi-0.10-orig/src/psiaccount.cpp 2006-01-07 21:19:01.000000000 +0000 +++ psi-0.10/src/psiaccount.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -78,6 +78,7 @@ #include"filetransdlg.h" #include"avatars.h" #include"tabdlg.h" +#include"bookmarks.h" #if defined(Q_WS_MAC) && defined(HAVE_GROWL) #include "psigrowlnotifier.h" @@ -335,6 +336,8 @@ bool usingSSL; bool doPopups; + BookmarkList bookmarks; + QHostAddress localAddress; QString pathToProfileEvents() @@ -491,7 +494,7 @@ 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(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(actionInvite(const Jid &, const QString &, const QString &)),SLOT(actionInvite(const Jid &, const QString &, const QString &))); connect(d->cp, SIGNAL(actionAssignKey(const Jid &)),SLOT(actionAssignKey(const Jid &))); connect(d->cp, SIGNAL(actionUnassignKey(const Jid &)),SLOT(actionUnassignKey(const Jid &))); @@ -1521,7 +1524,8 @@ void PsiAccount::processIncomingMessage(const Message &_m) { // skip empty messages - if(_m.body().isEmpty() && _m.urlList().isEmpty() && _m.invite().isEmpty() && !_m.containsEvents()) + if(_m.body().isEmpty() && _m.urlList().isEmpty() && !_m.containsEvents() && + _m.invitationFrom().isEmpty()) return; // skip headlines? @@ -1530,15 +1534,18 @@ //skip if on ignore list? KEVIN, must not forget - if(_m.type() == "groupchat") { - GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(_m.from().userHost())); - if(w) + + GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(_m.from().userHost())); + if(w) { + if(_m.type() == "groupchat" || _m.type() == "error") { w->message(_m); - return; + return; + } } // only toggle if not an invite or body is not empty - if(_m.invite().isEmpty() && !_m.body().isEmpty()) + // or if the invite's from nobody + if(_m.invitationFrom().isEmpty() || !_m.body().isEmpty()) toggleSecurity(_m.from(), _m.wasEncrypted()); UserListItem *u = findFirstRelavent(_m.from()); @@ -1586,7 +1593,8 @@ m.setBody(m.error().text + "\n------\n" + m.body()); // change the type? - if(m.type() != "headline" && m.invite().isEmpty()) { + if(m.type() != "headline" && + m.invitationFrom().isEmpty()) { if(option.incomingAs == 1) m.setType(""); else if(option.incomingAs == 2) @@ -1786,6 +1794,12 @@ d->nickFromVCard = true; setNick( vcard->nickName() ); } + + // do this here, otherwise autojoining will try the wrong account + + // we'll need to autojoin some chats, so... + connect(this, SIGNAL(bookmarksReceived()), SLOT(autoJoinBookmarkedGCs())); + requestBookmarks(); } } @@ -2002,7 +2016,7 @@ else if ( f.canSearch() ) actionSearch(jid); else if ( f.canGroupchat() ) - actionJoin(jid); + actionJoin(jid, QString::null); else if ( f.canDisco() ) actionDisco(jid, node); else if ( f.isGateway() ) @@ -2015,14 +2029,18 @@ } } -void PsiAccount::actionJoin(const Jid &j) +GCJoinDlg *PsiAccount::actionJoin(const Jid &j, const QString &pass) { GCJoinDlg *w = new GCJoinDlg(psi(), this); - w->le_host->setText ( j.host() ); - w->le_room->setText ( j.user() ); + w->setHost(j.host()); + w->setRoom(j.user()); + w->setPassword(pass); + w->setNick(jid().node()); w->show(); + + return w; } void PsiAccount::stateChanged() @@ -2603,14 +2621,29 @@ } } -void PsiAccount::actionInvite(const Jid &j, const QString &gc) +void PsiAccount::actionInvite(const Jid &j, const QString &gc, const QString &reason) { Message m; - m.setTo(j); - m.setInvite(gc); - m.setBody(tr("You have been invited to %1").arg(gc)); - m.setTimeStamp(QDateTime::currentDateTime()); - dj_sendMessage(m); + m.setTo(gc); + m.setInvitationTo(j); + m.setInvitationReason(reason); + dj_sendMessage(m, false); +} + +void PsiAccount::actionAcceptInvite(const Jid &gc, const QString &pword, const Jid &from) +{ + GCJoinDlg *dlg = actionJoin(gc, pword); + dlg->setInvite(from); +} + +void PsiAccount::actionDeclineInvite(const Jid &j, const Jid &gc, const QString &reason) +{ + Message decline(gc); + decline.setDeclineInvite(true); + decline.setInvitationTo(j); + decline.setInvitationReason(reason); + + dj_sendMessage(decline, false); } void PsiAccount::actionAssignKey(const Jid &j) @@ -2648,7 +2681,8 @@ d->client->sendMessage(nm); // only toggle if not an invite or body is not empty - if(m.invite().isEmpty() && !m.body().isEmpty()) + // or if the invite's from nobody + if(m.invitationFrom().isEmpty() || !m.body().isEmpty()) toggleSecurity(m.to(), m.wasEncrypted()); // don't log groupchat, private messages, or encrypted messages @@ -3217,7 +3251,7 @@ delete h; } -void PsiAccount::openGroupChat(const Jid &j) +void PsiAccount::openGroupChat(const Jid &j, const QString &pass) { QString str = j.userHost(); bool found = false; @@ -3230,15 +3264,20 @@ if(!found) d->groupchats += str; - GCMainDlg *w = new GCMainDlg(this, j); + GCMainDlg *w = new GCMainDlg(this, j, pass); connect(w, SIGNAL(aSend(const Message &)), SLOT(dj_sendMessage(const Message &))); connect(d->psi, SIGNAL(emitOptionsUpdate()), w, SLOT(optionsUpdate())); w->show(); } -bool PsiAccount::groupChatJoin(const QString &host, const QString &room, const QString &nick) +bool PsiAccount::groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &pass) { - return d->client->groupChatJoin(host, room, nick); + Status s; + s.setMucHistoryMaxChars(option.gcHistMaxChars); + s.setMucHistoryMaxStanzas(option.gcHistMaxStanzas); + s.setMucHistorySeconds(option.gcHistSeconds); + s.setMucHistorySince(option.gcHistSince); + return d->client->groupChatJoin(host, room, nick, pass, s); } void PsiAccount::groupChatChangeNick(const QString &host, const QString &room, const QString& nick, const Status &s) @@ -3282,11 +3321,21 @@ return; } GCJoinDlg *w = (GCJoinDlg *)dialogFind("GCJoinDlg", j); - if(!w) - return; - w->joined(); + //if(!w) + // return; + + QString password; + if(w) { + password = w->password(); + w->joined(); + } + else { + GCBookmark *mark = findGCBookmark(j); + if(mark) + password = mark->password(); + } - openGroupChat(j); + openGroupChat(j, password); } void PsiAccount::client_groupChatLeft(const Jid &j) @@ -3951,6 +4000,99 @@ return true; } +BookmarkList &PsiAccount::bookmarks() +{ + return d->bookmarks; +} + +void PsiAccount::requestBookmarks() +{ + JT_Bookmarks *task = new JT_Bookmarks(d->client->rootTask()); + connect(task, SIGNAL(finished()), SLOT(slotBookmarksReceived())); + task->get(true); +} + +void PsiAccount::pushBookmarks() +{ + JT_Bookmarks *task = new JT_Bookmarks(d->client->rootTask()); + connect(task, SIGNAL(finished()), SIGNAL(bookmarksSent())); + task->setBookmarks(d->bookmarks); + task->set(true); +} + +void PsiAccount::slotBookmarksReceived() +{ + JT_Bookmarks *task = (JT_Bookmarks *)sender(); + Q_ASSERT(task != NULL); + Q_ASSERT(task->isA("XMPP::JT_Bookmarks")); + d->bookmarks = task->bookmarks(); + + if(!task->success()) { + QMessageBox::information(NULL, tr("Error"), + tr("Psi was unable to retrieve your bookmarks.")); + return; + } + + bookmarksReceived(); +} + +void PsiAccount::autoJoinBookmarkedGCs() +{ + for(BookmarkList::iterator it = d->bookmarks.begin(); + it != d->bookmarks.end(); + ++it) { + Bookmark *bm = *it; + if(bm->rtti() == GCBookmark::RTTI) { + GCBookmark *gc = (GCBookmark *)bm; + if(!gc->autojoin()) + continue; + + Jid room = gc->jid(); + room.setResource(gc->nick()); + + // FIXME: what if nick is nil? or the Jid is invalid? + // how to handle these errors? + + // if the window is already open, then don't try to rejoin + GCMainDlg *w = (GCMainDlg *)dialogFind("GCMainDlg", Jid(room.userHost())); + if(!w) { + GCJoinDlg *join_dlg = actionJoin(room, gc->password()); + join_dlg->setRemember(GC_Autojoin); // this ideally should not be needed, but... + join_dlg->doJoin(); + //groupChatJoin(room.host(), room.node(), gc->nick(), gc->password()); + } + } + } + + QObject::disconnect(this, SIGNAL(bookmarksReceived()), this, SLOT(autoJoinBookmarkedGCs())); +} + +GCBookmark *PsiAccount::findGCBookmark(const Jid &jid) +{ + QPtrList marks = d->bookmarks.findGCBookmarks(jid); + if(marks.count()) + return marks.first(); + else + return NULL; +} + +bool PsiAccount::deleteGCBookmark(const Jid &jid) +{ + QPtrList marks = d->bookmarks.findGCBookmarks(jid); + if(marks.count()) { + return d->bookmarks.removeRef(marks.first()); + } + + return false; +} + +void PsiAccount::addGCBookmark(const QString &name, const Jid &room, + const QString &nick, + bool autojoin, + const QString &password) +{ + d->bookmarks.append(new GCBookmark(name, room, nick, autojoin, password)); +} //---------------------------------------------------------------------------- // PGPKeyDlg diff -Naur psi-0.10-orig/src/psiaccount.h psi-0.10/src/psiaccount.h --- psi-0.10-orig/src/psiaccount.h 2005-08-22 19:52:28.000000000 +0000 +++ psi-0.10/src/psiaccount.h 2006-01-14 00:43:21.000000000 +0000 @@ -33,7 +33,7 @@ #include"openpgp.h" #include"ui_pgpkey.h" #include"common.h" - +#include"bookmarks.h" namespace XMPP { @@ -63,6 +63,7 @@ class QSSLCert; class QHostAddress; class AvatarFactory; +class GCJoinDlg; class PGPKeyDlg : public PGPKeyUI { @@ -176,8 +177,8 @@ void showXmlConsole(); void openAddUserDlg(); - void openGroupChat(const Jid &); - bool groupChatJoin(const QString &host, const QString &room, const QString &nick); + void openGroupChat(const Jid &, const QString &); + bool groupChatJoin(const QString &host, const QString &room, const QString &nick, const QString &pass = ""); void groupChatSetStatus(const QString &host, const QString &room, const Status &); void groupChatChangeNick(const QString &host, const QString &room, const QString& nick, const Status &); void groupChatLeave(const QString &host, const QString &room); @@ -208,6 +209,16 @@ void deleteQueueFile(); + BookmarkList &bookmarks(); + void requestBookmarks(); + void pushBookmarks(); + GCBookmark *findGCBookmark(const Jid &); + bool deleteGCBookmark(const Jid &); + void addGCBookmark(const QString &, const Jid &, + const QString & = QString::null, + bool = false, + const QString & = QString::null); + signals: void disconnected(); void updatedActivity(); @@ -221,6 +232,9 @@ void encryptedMessageSent(int, bool); void enabledChanged(); + void bookmarksReceived(); + void bookmarksSent(); + public slots: void setStatus(const Status &); @@ -261,9 +275,11 @@ void actionTest(const Jid &); void actionRegister(const Jid &); void actionSearch(const Jid &); - void actionJoin(const Jid &); + GCJoinDlg *actionJoin(const Jid &, const QString &); void actionDisco(const Jid &, const QString &); - void actionInvite(const Jid &, const QString &); + void actionInvite(const Jid &, const QString &, const QString &); + void actionAcceptInvite(const Jid &, const QString &, const Jid &); + void actionDeclineInvite(const Jid &, const Jid &, const QString &); void actionSendFile(const Jid &); void actionSendFiles(const Jid &, const QStringList&); void featureActivated(QString feature, Jid jid, QString node); @@ -332,6 +348,9 @@ void processReadNext(const UserListItem &); void processReadNext(const Jid &); + void slotBookmarksReceived(); + void autoJoinBookmarkedGCs(); + private slots: void handleEvent(PsiEvent *); diff -Naur psi-0.10-orig/src/psicon.cpp psi-0.10/src/psicon.cpp --- psi-0.10-orig/src/psicon.cpp 2005-10-06 14:19:01.000000000 +0000 +++ psi-0.10/src/psicon.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -310,7 +310,6 @@ void saveProfile(UserAccountList acc = UserAccountList()) { - pro.recentGCList = recentGCList; pro.recentBrowseList = recentBrowseList; pro.lastStatusString = lastStatusString; pro.useSound = useSound; @@ -375,7 +374,7 @@ QPtrList tabs; QPtrList tabControlledChats; int eventId; - QStringList recentGCList, recentBrowseList, recentNodeList; + QStringList recentBrowseList, recentNodeList; EDB *edb; OpenPGP::Engine *pgp; bool pgpReady; @@ -436,7 +435,6 @@ QDir profileDir( pathToProfile( activeProfile ) ); profileDir.rmdir( "info" ); // remove unused dir - d->recentGCList = d->pro.recentGCList; d->recentBrowseList = d->pro.recentBrowseList; d->lastStatusString = d->pro.lastStatusString; useSound = d->pro.useSound; @@ -1265,27 +1263,36 @@ d->mainwin->showNoFocus(); } -const QStringList & PsiCon::recentGCList() const +const UserProfile::GCList &PsiCon::recentGCList() const { - return d->recentGCList; + return d->pro.recentGCList; } -void PsiCon::recentGCAdd(const QString &str) +UserProfile::GCList &PsiCon::recentGCList() +{ + return d->pro.recentGCList; +} + +void PsiCon::recentGCAdd(const Jid &jid, const QString &pword, bool save) { // remove it if we have it - for(QStringList::Iterator it = d->recentGCList.begin(); it != d->recentGCList.end(); ++it) { - if(*it == str) { - d->recentGCList.remove(it); + UserProfile::GCList &gcs(recentGCList()); + for(UserProfile::GCList::iterator it = gcs.begin(); + it != gcs.end(); ++it) { + UserProfile::GroupChat gc = *it; + if(gc.jid.full() == jid.full()) { + gcs.remove(it); break; } } // put it in the front - d->recentGCList.prepend(str); + UserProfile::GroupChat chat(jid, pword, save); + gcs.prepend(chat); // trim the list if bigger than 10 - while(d->recentGCList.count() > 10) - d->recentGCList.remove(d->recentGCList.fromLast()); + while(gcs.count() > 10) + gcs.remove(gcs.fromLast()); } const QStringList & PsiCon::recentBrowseList() const diff -Naur psi-0.10-orig/src/psicon.h psi-0.10/src/psicon.h --- psi-0.10-orig/src/psicon.h 2005-10-06 14:19:01.000000000 +0000 +++ psi-0.10/src/psicon.h 2006-01-14 00:43:21.000000000 +0000 @@ -122,8 +122,11 @@ AccountsComboBox *accountsComboBox(QWidget *parent=0, bool online_only = false); - const QStringList & recentGCList() const; - void recentGCAdd(const QString &); + const UserProfile::GCList & recentGCList() const; + UserProfile::GCList & recentGCList(); + void recentGCAdd(const Jid &, + const QString &pword = QString::null, + bool save = false); const QStringList & recentBrowseList() const; void recentBrowseAdd(const QString &); const QStringList & recentNodeList() const; diff -Naur psi-0.10-orig/src/psilistview.cpp psi-0.10/src/psilistview.cpp --- psi-0.10-orig/src/psilistview.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/psilistview.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,242 @@ +/* + * psilistview.cpp - Some common QListView derivations + * Copyright (c) 2004 Nolan Eakins + * + * 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#include +#include "psilistview.h" +#include "common.h" +#include "iconset.h" + +PsiListViewItem::PsiListViewItem( QListView * parent ) + : QListViewItem(parent) +{ +} + +PsiListViewItem::PsiListViewItem( QListViewItem * parent ) + : QListViewItem(parent) +{ +} + +PsiListViewItem::PsiListViewItem( QListView * parent, QListViewItem * after ) + : QListViewItem(parent, after) +{ +} + +PsiListViewItem::PsiListViewItem( QListViewItem * parent, QListViewItem * after ) + : QListViewItem(parent, after) +{ +} + +PsiListViewItem::PsiListViewItem( QListView * parent, + QString a, QString b, + QString c, QString d, + QString e, QString f, + QString g, QString h ) + : QListViewItem(parent, a, b, c, d, e, f, g, h) +{ +} + +PsiListViewItem::PsiListViewItem( QListViewItem * parent, + QString a, QString b, + QString c, QString d, + QString e, QString f, + QString g, QString h ) + : QListViewItem(parent, a, b, c, d, e, f, g, h) +{ +} + +PsiListViewItem::PsiListViewItem( QListView * parent, QListViewItem * after, + QString a, QString b, + QString c, QString d, + QString e, QString f, + QString g, QString h ) + : QListViewItem(parent, after, a, b, c, d, e, f, g, h) +{ +} + +PsiListViewItem::PsiListViewItem( QListViewItem * parent, QListViewItem * after, + QString a, QString b, + QString c, QString d, + QString e, QString f, + QString g, QString h ) + : QListViewItem(parent, after, a, b, c, d, e, f, g, h) +{ +} + +PsiListViewItem::~PsiListViewItem() +{ +} + +int PsiListViewItem::rtti() const +{ + return RTTI_BASE; +} + +void PsiListViewItem::optionsUpdate() +{ + // do nothing, this is for you +} + +PsiListViewGroup::PsiListViewGroup( QListView * parent, QString name ) + : PsiListViewItem(parent, name) +{ + updateIcon(); +} + +PsiListViewGroup::PsiListViewGroup( QListViewItem * parent, QString name ) + : PsiListViewItem(parent, name) +{ + updateIcon(); +} + +PsiListViewGroup::PsiListViewGroup( QListView * parent, QListViewItem * after, + QString name ) + : PsiListViewItem(parent, after, name) +{ + updateIcon(); +} + +PsiListViewGroup::PsiListViewGroup( QListViewItem * parent, QListViewItem * after, + QString name ) + : PsiListViewItem(parent, after, name) +{ + updateIcon(); +} + +PsiListViewGroup::~PsiListViewGroup() +{ +} + +void PsiListViewGroup::setOpen(bool yes) +{ + QListViewItem::setOpen(yes); + updateIcon(); +} + +void PsiListViewGroup::insertItem(QListViewItem *item) +{ + QListViewItem::insertItem(item); + updateIcon(); +} + +void PsiListViewGroup::takeItem(QListViewItem *item) +{ + QListViewItem::takeItem(item); + updateIcon(); +} + +void PsiListViewGroup::optionsUpdate() +{ + updateIcon(); +} + +void PsiListViewGroup::updateIcon() +{ + if(childCount() == 0) + setPixmap(0, IconsetFactory::icon("psi/groupEmpty")); + else if(isOpen()) + setPixmap(0, IconsetFactory::icon("psi/groupOpen")); + else + setPixmap(0, IconsetFactory::icon("psi/groupClosed")); +} + +void PsiListViewGroup::paintBranches(QPainter *p, const QColorGroup &cg, int w, int, int h) +{ + // paint a square of nothing + p->fillRect(0, 0, w, h, cg.base()); +} + +void PsiListViewGroup::paintCell(QPainter *p, const QColorGroup &cg, + int column, int width, int align) +{ + QColorGroup xcg = cg; + xcg.setColor(QColorGroup::Text, option.color[cGroupFore]); + + QFont f = p->font(); + //f.setPointSize(option.smallFontSize); + p->setFont(f); + + QListViewItem::paintCell(p, xcg, column, width, align); + + QFontMetrics fm(p->font()); + const QPixmap *pix = pixmap(column); + int x = fm.width(text(column)) + (pix ? pix->width() : 0); + if(column == 0) + x += 8; + + if(isSelected()) + p->setPen(xcg.highlightedText()); + else + p->setPen(xcg.text()); + + //int y = ((height() - fm.height()) / 2) + fm.ascent(); + +// if(!isSelected()) { + if(x < width - 8) { + int h = (height() / 2) - 1, w = width; + int num_cols = listView()->columns(); + if(column == (num_cols - 1)) + w -= 8; + + p->setPen(QPen(option.color[cGroupBack])); + p->drawLine(x, h, w, h); + h++; + p->setPen(QPen(option.color[cGroupFore])); + /*int h = height() / 2; + + p->setPen(QPen(option.color[cGroupBack], 2));*/ + p->drawLine(x, h, w, h); + } + //} +} + +PsiListView::PsiListView(QWidget *parent, const char *name, WFlags f) + : QListView(parent, name, f) +{ + optionsUpdate(); +} + +PsiListView::~PsiListView() +{ +} + +void PsiListView::optionsUpdate() +{ + // set the font + QFont f; + f.fromString(option.font[fRoster]); + QListView::setFont(f); + + // set the text and background colors + QPalette mypal = QListView::palette(); + mypal.setColor(QColorGroup::Text, option.color[cOnline]); + mypal.setColor(QColorGroup::Base, option.color[cListBack]); + QListView::setPalette(mypal); + + // update the items + QListViewItemIterator it(this); + PsiListViewItem *item; + for(; it.current() ; ++it) { + if(it.current()->rtti() >= PsiListViewItem::RTTI_BASE) { + item = (PsiListViewItem *)it.current(); + item->optionsUpdate(); + } + } + + update(); +} diff -Naur psi-0.10-orig/src/psilistview.h psi-0.10/src/psilistview.h --- psi-0.10-orig/src/psilistview.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/psilistview.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,115 @@ +/* + * psilistview.h - Some common QListView derivations + * Copyright (c) 2004 Nolan Eakins + * + * 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 program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ +#ifndef PSILISTVIEW_H +#define PSILISTVIEW_H + +#include + +/** A QListView that uses the same colors as the contact view. + */ +class PsiListView: public QListView +{ + Q_OBJECT; + +public: + PsiListView(QWidget *parent = NULL, const char *name = NULL, WFlags f = 0); + virtual ~PsiListView(); + +public slots: + virtual void optionsUpdate(); + +private: +}; + + +/** A list item that adds optionsUpdate and an RTTI base + */ +class PsiListViewItem: public QListViewItem +{ +public: + /** Ensure that all classes derived from this class use this base. + */ + static const int RTTI_BASE = 10000; + + PsiListViewItem( QListView * parent ); + PsiListViewItem( QListViewItem * parent ); + PsiListViewItem( QListView * parent, QListViewItem * after ); + PsiListViewItem( QListViewItem * parent, QListViewItem * after ); + + PsiListViewItem( QListView * parent, + QString, QString = QString::null, + QString = QString::null, QString = QString::null, + QString = QString::null, QString = QString::null, + QString = QString::null, QString = QString::null ); + PsiListViewItem( QListViewItem * parent, + QString, QString = QString::null, + QString = QString::null, QString = QString::null, + QString = QString::null, QString = QString::null, + QString = QString::null, QString = QString::null ); + + PsiListViewItem( QListView * parent, QListViewItem * after, + QString, QString = QString::null, + QString = QString::null, QString = QString::null, + QString = QString::null, QString = QString::null, + QString = QString::null, QString = QString::null ); + PsiListViewItem( QListViewItem * parent, QListViewItem * after, + QString, QString = QString::null, + QString = QString::null, QString = QString::null, + QString = QString::null, QString = QString::null, + QString = QString::null, QString = QString::null ); + virtual ~PsiListViewItem(); + + virtual int rtti() const; + + /** This is called by PsiListView for when the options get updated. + */ + virtual void optionsUpdate(); +}; + + +/** A QListViewItem that has an icon similiar to the contact view's groups. + */ +class PsiListViewGroup: public PsiListViewItem +{ +public: + PsiListViewGroup( QListView * parent, QString ); + PsiListViewGroup( QListViewItem * parent, QString ); + + PsiListViewGroup( QListView * parent, QListViewItem * after, QString ); + PsiListViewGroup( QListViewItem * parent, QListViewItem * after, QString ); + + virtual ~PsiListViewGroup(); + + virtual void setOpen(bool); + virtual void insertItem(QListViewItem *); + virtual void takeItem(QListViewItem *); + + virtual void paintBranches(QPainter *, const QColorGroup &, int, int, int); + virtual void paintCell(QPainter *, const QColorGroup &, int, int, int); + + virtual void optionsUpdate(); + +protected: + virtual void updateIcon(); + +private: +}; + +#endif /* PSILISTVIEW_H */ diff -Naur psi-0.10-orig/src/psi_profiles.cpp psi-0.10/src/psi_profiles.cpp --- psi-0.10-orig/src/psi_profiles.cpp 2006-01-07 21:19:01.000000000 +0000 +++ psi-0.10/src/psi_profiles.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -25,12 +25,15 @@ #include #include +#include"common.h" #include"eventdlg.h" #include"chatdlg.h" +#include"groupchatdlg.h" #include"contactview.h" #include"xmpp_xmlcommon.h" #include"fancylabel.h" #include"advwidget.h" +#include"dataformdlg.h" using namespace XMPP; using namespace XMLHelper; @@ -337,7 +340,6 @@ prefs.hideMenubar = TRUE; prefs.defaultAction = 1; prefs.delChats = dcHour; - prefs.showJoins = TRUE; prefs.browser = 0; prefs.customBrowser = ""; prefs.customMailer = ""; @@ -460,6 +462,8 @@ prefs.sizeEventDlg = EventDlg::defaultSize(); prefs.sizeChatDlg = ChatDlg::defaultSize(); + prefs.sizeGCDlg = GCMainDlg::defaultSize(); + prefs.sizeOccList = GCMainDlg::defaultOccListSize(); prefs.sizeTabDlg = ChatDlg::defaultSize(); //TODO: no! prefs.jidComplete = true; @@ -525,6 +529,16 @@ } // groupchat + prefs.showJoins = true; + prefs.showStatusChanges = true; + prefs.showRoleAffilChanges = true; + + // groupchat history + prefs.gcHistMaxChars = prefs.gcHistMaxStanzas = + prefs.gcHistSeconds = -1; + prefs.gcHistSince = QDateTime(); + + // groupchat coloring prefs.gcHighlighting = true; prefs.gcHighlights.clear(); @@ -537,6 +551,12 @@ prefs.gcNickColors << "Purple"; prefs.gcNickColors << "Red"; + // groupchat reasons + prefs.reasons.insert(QObject::tr("Bad Psi"), + Reason(Reason::Negative, QObject::tr("You got impaled with a Psi."))); + prefs.reasons.insert(QObject::tr("Good Psi"), + Reason(Reason::Positive, QObject::tr("You've been handed a Psi."))); + // popups prefs.ppIsOn = false; prefs.ppOnline = true; @@ -686,7 +706,24 @@ base.appendChild(textTag(doc, "progver", PROG_VERSION)); base.appendChild(textTag(doc, "geom", mwgeom)); - base.appendChild(stringListToXml(doc, "recentGCList", recentGCList)); + + { + QDomElement gcs = doc.createElement("recentGCList"); + base.appendChild(gcs); + + GCList::const_iterator end = recentGCList.end(); + for(GCList::const_iterator it = recentGCList.begin(); + it != end; + ++it) { + const GroupChat &gc(*it); + QDomElement chat = textTag(doc, "item", + gc.jid.full()); + if(gc.save_pword) + chat.setAttribute("password", gc.pword); + gcs.appendChild(chat); + } + } + base.appendChild(stringListToXml(doc, "recentBrowseList", recentBrowseList)); base.appendChild(textTag(doc, "lastStatusString", lastStatusString)); base.appendChild(textTag(doc, "useSound", useSound)); @@ -768,7 +805,6 @@ p_misc.appendChild(textTag(doc, "autoRosterSizeGrowTop", prefs.autoRosterSizeGrowTop)); p_misc.appendChild(textTag(doc, "autoResolveNicksOnAdd", prefs.autoResolveNicksOnAdd)); p_misc.appendChild(textTag(doc, "delChats", prefs.delChats)); - p_misc.appendChild(textTag(doc, "showJoins", prefs.showJoins)); p_misc.appendChild(textTag(doc, "browser", prefs.browser)); p_misc.appendChild(textTag(doc, "customBrowser", prefs.customBrowser)); p_misc.appendChild(textTag(doc, "customMailer", prefs.customMailer)); @@ -965,6 +1001,9 @@ p_sizes.appendChild(textTag(doc, "eventdlg", prefs.sizeEventDlg)); p_sizes.appendChild(textTag(doc, "chatdlg", prefs.sizeChatDlg)); + p_sizes.appendChild(textTag(doc, "gcdlg", prefs.sizeGCDlg)); + + p_sizes.appendChild(textTag(doc, "occupants", prefs.sizeOccList)); p_sizes.appendChild(textTag(doc, "tabdlg", prefs.sizeTabDlg)); } @@ -1063,10 +1102,50 @@ //group chat QDomElement gc = doc.createElement("groupchat"); p.appendChild(gc); + gc.appendChild(textTag(doc, "showJoins", prefs.showJoins)); + gc.appendChild(textTag(doc, "showStatusChanges", prefs.showStatusChanges)); + gc.appendChild(textTag(doc, "showRoleAffilChanges", prefs.showRoleAffilChanges)); gc.appendChild(stringListToXml(doc, "highlightwords", prefs.gcHighlights)); gc.appendChild(stringListToXml(doc, "nickcolors", prefs.gcNickColors)); gc.appendChild(textTag(doc, "nickcoloring", prefs.gcNickColoring)); gc.appendChild(textTag(doc, "highlighting", prefs.gcHighlighting)); + + QDomElement hist = doc.createElement("history"); + gc.appendChild(hist); + hist.appendChild(textTag(doc, "maxchars", prefs.gcHistMaxChars)); + hist.appendChild(textTag(doc, "maxstanzas", prefs.gcHistMaxStanzas)); + hist.appendChild(textTag(doc, "seconds", prefs.gcHistSeconds)); + hist.appendChild(textTag(doc, "since", prefs.gcHistSince.toString(Qt::ISODate))); + + QDomElement reasons = doc.createElement("reasons"); + gc.appendChild(reasons); + + ReasonMap::const_iterator it = prefs.reasons.begin(), + end = prefs.reasons.end(); + for(; it != end; ++it) { + const Reason &r = it.data(); + + QDomElement reason_elem = doc.createElement("reason"); + reasons.appendChild(reason_elem); + + reason_elem.appendChild(textTag(doc, "name", it.key())); + reason_elem.appendChild(textTag(doc, "text", r.text)); + + QString type; + switch(r.type) { + case Reason::Positive: + type = "positive"; + break; + case Reason::Negative: + type = "negative"; + break; + default: + type = "both"; + break; + } + + reason_elem.appendChild(textTag(doc, "type", type)); + } } { @@ -1191,6 +1270,15 @@ stick.appendChild( textTag(doc, "stickToWindows", GAdvancedWidget::stickToWindows()) ); } + // DataFormDlg + { + QDomElement e = doc.createElement("dataforms"); + QSize size = DataFormDlg::savedSize(); + e.appendChild(textTag(doc, "width", size.width())); + e.appendChild(textTag(doc, "height", size.height())); + p.appendChild(e); + } + QFile f(fname); if(!f.open(IO_WriteOnly)) return FALSE; @@ -1226,12 +1314,34 @@ readEntry(base, "progver", &progver); readRectEntry(base, "geom", &mwgeom); - xmlToStringList(base, "recentGCList", &recentGCList); + + bool found; + QDomElement gcs = findSubTag(base, "recentGCList", &found); + if(found) { + for(QDomNode n = gcs.firstChild(); + !n.isNull(); + n = n.nextSibling()) { + if(n.isElement()) { + GroupChat gc; + QDomElement item = n.toElement(); + gc.pword = item.attribute("password"); + if(!gc.pword.isEmpty()) + gc.save_pword = true; + + QDomNode text = item.firstChild(); + if(text.isCharacterData()) { + gc.jid = Jid(text.toCharacterData().data()); + } + + recentGCList.push_back(gc); + } + } + } + xmlToStringList(base, "recentBrowseList", &recentBrowseList); readEntry(base, "lastStatusString", &lastStatusString); readBoolEntry(base, "useSound", &useSound); - bool found; QDomElement accs = findSubTag(base, "accounts", &found); if(found) { for(QDomNode n = accs.firstChild(); !n.isNull(); n = n.nextSibling()) { @@ -1535,6 +1645,9 @@ if(found) { readSizeEntry(p_sizes, "eventdlg", &prefs.sizeEventDlg); readSizeEntry(p_sizes, "chatdlg", &prefs.sizeChatDlg); + readSizeEntry(p_sizes, "gcdlg", &prefs.sizeGCDlg); + + readNumEntry(p_sizes, "occupants", &prefs.sizeOccList); readSizeEntry(p_sizes, "tabdlg", &prefs.sizeTabDlg); } @@ -1626,13 +1739,64 @@ } } + // DataFormDlg + QDomElement p_dataform = findSubTag(p, "dataforms", &found); + if(found) { + int width = -1, height = -1; + readNumEntry(p_dataform, "width", &width); + readNumEntry(p_dataform, "height", &height); + + if(width != -1 && height != -1) { + DataFormDlg::setSavedSize(width, height); + } + } + //group chat QDomElement p_groupchat = findSubTag(p, "groupchat", &found); if (found) { + readBoolEntry(p_groupchat, "showJoins", &prefs.showJoins); + readBoolEntry(p_groupchat, "showStatusChanges", &prefs.showStatusChanges); + readBoolEntry(p_groupchat, "showRoleAffilChanges", &prefs.showRoleAffilChanges); readBoolEntry(p_groupchat, "nickcoloring", &prefs.gcNickColoring); readBoolEntry(p_groupchat, "highlighting", &prefs.gcHighlighting); xmlToStringList(p_groupchat, "highlightwords", &prefs.gcHighlights); xmlToStringList(p_groupchat, "nickcolors", &prefs.gcNickColors); + + QDomElement hist = findSubTag(p_groupchat, "history", &found); + if(found) { + readNumEntry(hist, "maxchars", &prefs.gcHistMaxChars); + readNumEntry(hist, "maxstanzas", &prefs.gcHistMaxStanzas); + readNumEntry(hist, "seconds", &prefs.gcHistSeconds); + + QString since; + readEntry(hist, "since", &since); + if(!since.isEmpty()) + prefs.gcHistSince = QDateTime::fromString(since, Qt::ISODate); + } + + QDomElement reasons = findSubTag(p_groupchat, "reasons", &found); + if(found) { + for (QDomNode n = reasons.firstChild(); !n.isNull(); n = n.nextSibling()) { + QDomElement i = n.toElement(); + if ( i.isNull() ) + continue; + + QString name, text, type_str; + readEntry(i, "name", &name); + readEntry(i, "text", &text); + readEntry(i, "type", &type_str); + + Reason::Type type; + if(type_str == "positive") + type = Reason::Positive; + else if(type_str == "negative") + type = Reason::Negative; + else + type = Reason::Both; + + prefs.reasons.insert(name, Reason(type, text)); + } + } } // Bouncing dock icon (Mac OS X) diff -Naur psi-0.10-orig/src/reasons.cpp psi-0.10/src/reasons.cpp --- psi-0.10-orig/src/reasons.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/reasons.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,290 @@ +/* + * reasons.cpp - Groupchat reason menu + * Copyright (C) 2004, Nolan Eakins + * + * 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 "common.h" +#include "groupchatdlg.h" // need GCContextMenuItems enum +#include "reasons.h" +#include "customreasondlg.h" + +//////////////////////////////////////////////////////////// +// ReasonMenu +//////////////////////////////////////////////////////////// + +class ReasonMenu::Private +{ +public: + Reason::Type type; + QPtrList itemMap; +}; + +ReasonMenu::ReasonMenu(Reason::Type type, QWidget *parent, const char *name) + : QPopupMenu(parent, name) +{ + d = new Private; + d->type = type; + + connect(this, SIGNAL(activated(int)), SLOT(activated(int))); + + insertItem(tr("&No Reason"), GC_CtxEnd); + insertItem(tr("&Custom Reason..."), GC_CtxEnd + 1); + insertSeparator(); + + QString text; + ReasonMap::iterator it = option.reasons.begin(), + end = option.reasons.end(); + for(int i = 0; it != end; ++it) { + Reason &r = it.data(); + + if(d->type == Reason::Both || + r.type == Reason::Both || + r.type == d->type) { + d->itemMap.append(&r); + if(i < 10) + // mod 10 should make (i + 1) zero when it equals 10 + text = tr("&%1. %2").arg((i + 1) % 10).arg(it.key()); + else + text = it.key(); + + // add (GC_CtxEnd + 2) so it doesn't interfere w/ the parent menu + // or the top two items + insertItem(text, i + (GC_CtxEnd + 2)); + + i++; + } + } +} + +ReasonMenu::~ReasonMenu() +{ + delete d; +} + +void ReasonMenu::activated(int i) +{ + QString reason; + + if(i == (GC_CtxEnd + 1)) { + CustomReasonDlg dlg(d->type, this); + if(dlg.exec() != QDialog::Accepted) + return; + + reason = dlg.reasonText(); + } + else if(i > (GC_CtxEnd + 1)) { + Reason *r = d->itemMap.at(i - (GC_CtxEnd + 2)); + reason = r->text; + } + + activated(reason); +} + +//////////////////////////////////////////////////////////// +// ReasonComboBox +//////////////////////////////////////////////////////////// +const int ReasonComboBox::NoReason = 0; +const int ReasonComboBox::CustomReason = 1; +const int ReasonComboBox::Separator = 2; +const int ReasonComboBox::ItemOffset = 3; + +class ReasonComboBox::Private +{ +public: + Private(ReasonMap &r) + : reasons(r) + { + } + + ReasonMap &reasons; + QString reasonText; + Reason::Type type; + int lastItem; + + Reason *getReasonByIndex(int index) { + if(index < ReasonComboBox::ItemOffset) + return NULL; + + ReasonMap::iterator it = reasons.begin(), + end = reasons.end(); + for(int i = ReasonComboBox::ItemOffset; it != end; ++it) { + Reason &r = it.data(); + + if(type == Reason::Both || + r.type == Reason::Both || + r.type == type) { + if(i == index) + return &r; + + i++; + } + } + + return NULL; + } +}; + +ReasonComboBox::ReasonComboBox(Reason::Type type, QWidget *parent, const char *name) + : QComboBox(parent, name) +{ + init(option.reasons, type); +} + +ReasonComboBox::ReasonComboBox(ReasonMap &reasons, Reason::Type type, QWidget *parent, const char *name) + : QComboBox(parent, name) +{ + init(reasons, type); +} + +void ReasonComboBox::init(ReasonMap &reasons, Reason::Type type) +{ + d = new Private(reasons); + d->type = type; + populate(); + + connect(this, SIGNAL(activated(int)), SLOT(cbActivated(int))); +} + +ReasonComboBox::~ReasonComboBox() +{ + delete d; +} + +Reason::Type ReasonComboBox::type() const +{ + return d->type; +} + +void ReasonComboBox::setType(Reason::Type type) +{ + d->type = type; + populate(); +} + +void ReasonComboBox::setCurrentItem(const QString &key, bool name) +{ + int index = findReason(key, name); + QComboBox::setCurrentItem(index); // C++ or g++ is stupid +} + +QString ReasonComboBox::currentReasonName() const +{ + Reason *r = d->getReasonByIndex(currentItem()); + if(r) + return r->text; + else + return QString::null; +} + +QString ReasonComboBox::currentReasonText() const +{ + return d->reasonText; +} + +void ReasonComboBox::optionsUpdate() +{ + populate(); +} + +void ReasonComboBox::populate() +{ + clear(); + + insertItem(tr("No Reason"), NoReason); + insertItem(tr("Custom Reason..."), CustomReason); + insertItem("----", Separator); + + ReasonMap::iterator it = d->reasons.begin(), + end = d->reasons.end(); + for(int i = ItemOffset; it != end; ++it) { + Reason &r = it.data(); + + if(d->type == Reason::Both || + r.type == Reason::Both || + r.type == d->type) { + insertItem(it.key(), i); + i++; + } + } +} + +int ReasonComboBox::findReason(const QString &key, bool name) +{ + ReasonMap::iterator it = d->reasons.begin(), + end = d->reasons.end(); + for(int i = ItemOffset; it != end; ++it) { + Reason &r = it.data(); + + if(d->type == Reason::Both || + r.type == Reason::Both || + r.type == d->type) { + if((name && it.key() == key) || + (!name && r.text == key)) + return i; + + i++; + } + } + + if(key.isEmpty()) { + return NoReason; + } + else { + return CustomReason; + } +} + +void ReasonComboBox::cbActivated(int item) +{ + QString body, name; + + // no reason should get passed straight down until the signal + + if(item == CustomReason) { + CustomReasonDlg dlg(d->type, this); + if(dlg.exec() != QDialog::Accepted) + return; + + body = dlg.reasonText(); + name = dlg.reasonName(); + + populate(); + + if(dlg.wasSaved()) + setCurrentItem(name); + else + // NoReason would get selected since the + // combo box got repopulated + QComboBox::setCurrentItem(CustomReason); + } + else if(item == Separator) { + QComboBox::setCurrentItem(d->lastItem); + return; + } + else if(item != NoReason) { // an actual reason + name = text(item); + if(!d->reasons.contains(name)) + return; + + body = option.reasons[name].text; + } + + d->lastItem = item; + d->reasonText = body; + activated(name, body); +} diff -Naur psi-0.10-orig/src/reasons.h psi-0.10/src/reasons.h --- psi-0.10-orig/src/reasons.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/reasons.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,150 @@ +/* + * reasons.h - Header for the groupchat reason menu + * Copyright (C) 2004, Nolan Eakins + * + * 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 REASONS_H +#define REASONS_H + +#include +#include + +struct Reason +{ + enum Type { Positive, Negative, Both }; + + Reason(Type t, const QString &s) + : type(t), text(s) { + // nothing needed here + } + + Reason() : type(Both) { } + + Type type; + QString text; +}; + +typedef QMap ReasonMap; + +/** This is the popup menu used for each role listed in + * context menu of GCMainDlg's occupant list. It displays + * a list of reasons to be used for the role change. + */ +class ReasonMenu: public QPopupMenu +{ + Q_OBJECT; + +public: + /** Contructs the ReasonMenu. + * @arg positive True if the menu should display positive reasons. + * @arg parent The parent widget. + * @arg name The name used for QObject. + */ + ReasonMenu(Reason::Type type, QWidget *parent = NULL, const char *name = NULL); + virtual ~ReasonMenu(); + +signals: + /** Emitted when an item is selected. + * @arg reason The text of the reason. + */ + void activated(const QString &); + +private slots: + /** Catches the original activated signal, and emits + * the activated(const QString &) signal instead. + */ + void activated(int); + +private: + class Private; + Private *d; +}; + +/** A combo box that displays the stored MUC reasons. + */ +class ReasonComboBox: public QComboBox +{ + Q_OBJECT; + +public: + static const int NoReason, CustomReason, Separator, ItemOffset; + + /** Constructs the combo box. + * @arg type The type of reasons to populate the CB with + * @arg parent The parent widget. + * @arg name The name of the widget. + */ + ReasonComboBox(Reason::Type, QWidget *parent, const char *name = NULL); + /** Constructs the combo box. + * @arg reasons The reason map used to populate the combo box. + * @arg type The type of reasons to populate the CB with + * @arg parent The parent widget. + * @arg name The name of the widget. + */ + ReasonComboBox(ReasonMap &, Reason::Type, QWidget *parent, const char *name = NULL); + ~ReasonComboBox(); + + Reason::Type type() const; + void setType(Reason::Type); + + /** Sets the current item. + * @arg key Either the name or text of the reason + * @arg name Sets whether to search by name (true) or text (false) + */ + virtual void setCurrentItem(const QString &, bool name = true); + inline virtual void setCurrentItem(int i) { QComboBox::setCurrentItem(i); } + + /** Returns the current reason. + */ + QString currentReasonName() const; + QString currentReasonText() const; + +public slots: + /** Call this when Psi's options get updated. + * It'll cause the combo box to be repopulated. + */ + virtual void optionsUpdate(); + +protected: + void populate(); + /** Returns the index for a reason. + * @arg key Either the name or text of the reason. + * @arg name Sets whether to search by name (true) or text (false) + * @return The combo box index if the reason is found, otherwise + * either CustomReason if the key is not null and + * name is false or NoReason. + */ + int findReason(const QString &, bool name = true); + +protected slots: + void cbActivated(int); + +signals: + /** This is emitted when an item is selected. + * @arg name The name of the reason + * @arg text The text of the reason + */ + void activated(const QString &, const QString &); + +private: + void init(ReasonMap &, Reason::Type); + + class Private; + Private *d; +}; + +#endif diff -Naur psi-0.10-orig/src/roomcfgdlg.cpp psi-0.10/src/roomcfgdlg.cpp --- psi-0.10-orig/src/roomcfgdlg.cpp 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/roomcfgdlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,111 @@ +/* + * roomcfgdlg.cpp - Room configuration dialog + * Copyright (C) 2004 Nolan Eakins + * + * 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 "common.h" +#include "busywidget.h" +#include "iconwidget.h" +#include "im.h" +#include "roomcfgdlg.h" +#include "xmpp_xdata.h" +#include "xdata_widget.h" +#include "muc_tasks.h" + +class RoomCfgDlg::Private +{ +public: + MT_RoomConfig *task; + Jid jid; + Client *client; +}; + +RoomCfgDlg::RoomCfgDlg(const Jid &room, Client *c, const char *name) + : DataFormDlg(tr("Configuring %1").arg(room.full()), name) +{ + d = new Private; + + d->jid = room; + d->task = NULL; + d->client = c; +} + +RoomCfgDlg::~RoomCfgDlg() +{ + if(d->task) + delete d->task; + + delete d; +} + +void RoomCfgDlg::doSendForm() +{ + Q_ASSERT(d->task == NULL); // formReceived should have been called + + d->task = new MT_RoomConfig(d->client->rootTask()); + connect(d->task, SIGNAL(finished()), this, SLOT(formSent())); + d->task->setXData(d->jid, xdata()); + + d->task->go(); +} + +void RoomCfgDlg::cancelForm() +{ + if(d->task) { + d->task->disconnect(); + } + + MT_RoomConfig *t = new MT_RoomConfig(d->client->rootTask()); + t->cancel(d->jid); + t->go(); +} + +void RoomCfgDlg::doGetForm() +{ + d->task = new MT_RoomConfig(d->client->rootTask()); + connect(d->task, SIGNAL(finished()), this, SLOT(formReceived())); + + d->task->getXData(d->jid); + d->task->go(); +} + +void RoomCfgDlg::processForm() +{ + if(d->task->success()) { + setXData(d->task->xdata()); + } + else { + QString m; + + if(d->task->statusCode() == MT_RoomConfig::NoConfig) { + m = tr("The room didn't have a configuration form."); + } + else { + m = d->task->statusString(); + } + + QMessageBox::information(this, tr("Room Config"), + m, QMessageBox::Ok); + reject(); // does the rest of the method finish? + } + + d->task->deleteLater(); + d->task = NULL; +} diff -Naur psi-0.10-orig/src/roomcfgdlg.h psi-0.10/src/roomcfgdlg.h --- psi-0.10-orig/src/roomcfgdlg.h 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/roomcfgdlg.h 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,56 @@ +/* $Header: /home/cvsroot/psi/psi/src/roomcfgdlg.h,v 1.9 2004/10/18 14:01:21 nolan Exp $ + * roomcfgdlg.h - Header file for the Room configuration dialog + * Copyright (C) 2004 Nolan Eakins + * + * 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 ROOMCFGDLG_H +#define ROOMCFGDLG_H + +#include "dataformdlg.h" + +namespace XMPP +{ + class XData; + class Jid; + class Client; +}; + +using XMPP::XData; +using XMPP::Jid; +using XMPP::Client; + +class RoomCfgDlg: public DataFormDlg +{ + Q_OBJECT; + +public: + RoomCfgDlg(const Jid &, Client *, const char *name = NULL); + virtual ~RoomCfgDlg(); + +protected: + void doSendForm(); + void cancelForm(); + void doGetForm(); + void processForm(); + +private: + class Private; + Private *d; +}; + +#endif diff -Naur psi-0.10-orig/src/src.pro psi-0.10/src/src.pro --- psi-0.10-orig/src/src.pro 2006-01-14 00:42:35.000000000 +0000 +++ psi-0.10/src/src.pro 2006-01-14 00:43:21.000000000 +0000 @@ -9,6 +9,13 @@ #CONFIG += use_crash #DEFINES += GPG_DEBUG +# Uncomment this if you want Psi to work with mu-conference +DEFINES += MUCONF_HACK + +# Uncomment this if you want Psi to use the muc#owner namespace +# for changing occupants to/from admins and/or owners +# DEFINES += OLD_JEP_0045 + # Executable name TARGET = psi @@ -91,7 +98,7 @@ exists(../iris) { IRIS_BASE = ../iris } -include(../iris.pri) +include($$IRIS_BASE/iris.pri) # include Psi library CONFIG += libpsi @@ -153,7 +160,18 @@ $$PSI_CPP/actionlist.h \ $$PSI_CPP/psiactionlist.h \ $$PSI_CPP/xdata_widget.h \ - $$PSI_CPP/xmpp_xdata.h + $$PSI_CPP/xmpp_xdata.h \ + $$PSI_CPP/roomcfgdlg.h \ + $$PSI_CPP/customreasondlg.h \ + $$PSI_CPP/reasons.h \ + $$PSI_CPP/dataformdlg.h \ + $$PSI_CPP/destroyroomdlg.h \ + $$PSI_CPP/genericbusydlg.h \ + $$PSI_CPP/muc_affileditordlg.h \ + $$PSI_CPP/muc_affileditorpriv.h \ + $$PSI_CPP/jidvalidator.h \ + $$PSI_CPP/psilistview.h \ + $$PSI_CPP/jiddrag.h # Source files SOURCES += \ @@ -203,7 +221,19 @@ $$PSI_CPP/actionlist.cpp \ $$PSI_CPP/psiactionlist.cpp \ $$PSI_CPP/xdata_widget.cpp \ - $$PSI_CPP/xmpp_xdata.cpp + $$PSI_CPP/xmpp_xdata.cpp \ + $$PSI_CPP/roomcfgdlg.cpp \ + $$PSI_CPP/customreasondlg.cpp \ + $$PSI_CPP/reasons.cpp \ + $$PSI_CPP/dataformdlg.cpp \ + $$PSI_CPP/destroyroomdlg.cpp \ + $$PSI_CPP/genericbusydlg.cpp \ + $$PSI_CPP/muc_affileditordlg.cpp \ + $$PSI_CPP/muc_affileditorpriv.cpp \ + $$PSI_CPP/jidvalidator.cpp \ + $$PSI_CPP/psilistview.cpp \ + $$PSI_CPP/jiddrag.cpp + mac { contains( DEFINES, HAVE_GROWL ) { HEADERS += $$PSI_CPP/psigrowlnotifier.h @@ -235,7 +265,8 @@ $$PSI_CPP/ui_sslcert.ui \ $$PSI_CPP/ui_disco.ui \ $$PSI_CPP/ui_tip.ui \ - $$PSI_CPP/ui_filetrans.ui + $$PSI_CPP/ui_filetrans.ui \ + $$PSI_CPP/ui_destroyroom.ui # options dialog include(options/options.pri) diff -Naur psi-0.10-orig/src/src.pro.orig psi-0.10/src/src.pro.orig --- psi-0.10-orig/src/src.pro.orig 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/src.pro.orig 2006-01-14 00:42:35.000000000 +0000 @@ -0,0 +1,286 @@ +# +# Psi qmake profile +# + +# Configuration +TEMPLATE = app +CONFIG += qt thread x11 no_fixpath + +#CONFIG += use_crash +#DEFINES += GPG_DEBUG + +# Executable name +TARGET = psi + +MOC_DIR = .moc +OBJECTS_DIR = .obj +UI_DIR = .ui + +# qconf +exists(../conf.pri) { + include(../conf.pri) + + # Shared files + sharedfiles.path = $$DATADIR + sharedfiles.files = ../README ../COPYING ../iconsets ../sound ../certs + INSTALLS += sharedfiles + + # Widgets + #widgets.path = $$DATADIR/designer + #widgets.files = ../libpsi/psiwidgets/libpsiwidgets.so + #INSTALLS += widgets + + # icons and desktop files + dt.path=$$PREFIX/share/applications/ + dt.files = ../psi.desktop + icon1.path=$$PREFIX/share/icons/hicolor/16x16/apps + icon1.extra = cp -f ../iconsets/system/default/icon_16.png $(INSTALL_ROOT)$$icon1.path/psi.png + icon2.path=$$PREFIX/share/icons/hicolor/32x32/apps + icon2.extra = cp -f ../iconsets/system/default/icon_32.png $(INSTALL_ROOT)$$icon2.path/psi.png + icon3.path=$$PREFIX/share/icons/hicolor/48x48/apps + icon3.extra = cp -f ../iconsets/system/default/icon_48.png $(INSTALL_ROOT)$$icon3.path/psi.png + INSTALLS += dt icon1 icon2 icon3 +} + +win32: { + include(../conf_win32.pri) +} + +# qxml +!isEmpty(QXML_STATIC) { + unix: { + INCLUDEPATH += tools/qxml + HEADERS += tools/qxml/qxml.h tools/qxml/qdom.h + SOURCES += tools/qxml/qxml.cpp tools/qxml/qdom.cpp + } +} + +# IPv6 ? +#DEFINES += NO_NDNS + +# include some of cutestuff +CONFIG += psics +IRISCS_CPP = ../../cutestuff +exists(../cutestuff) { + IRISCS_CPP = ../cutestuff +} +PSICS_CPP = tools +include(../psics.pri) + +# qca +#DEFINES += QCA_STATIC +#QCA_PREFIX = ../qca +#!exists(../qca) { +# QCA_PREFIX = ../../qca/src +#} +#INCLUDEPATH += $$QCA_PREFIX +#HEADERS += $$QCA_PREFIX/qca.h $$QCA_PREFIX/qcaprovider.h +#SOURCES += $$QCA_PREFIX/qca.cpp + +# libxmpp +#CONFIG += xmpp +#XMPP_BASE = ../../xmpp +#exists(../xmpp) { +# XMPP_BASE = ../xmpp +#} +#include($$XMPP_BASE/xmpp.pri) + +# include Iris XMPP library +#CONFIG += iris +IRIS_BASE = ../../iris +exists(../iris) { + IRIS_BASE = ../iris +} +include(../iris.pri) + +# include Psi library +CONFIG += libpsi +LIBPSI_CPP = ../../libpsi +exists(../libpsi) { + LIBPSI_CPP = ../libpsi +} +include(../libpsi.pri) + +# Psi sources +PSI_CPP = . + +# Header files +HEADERS += \ + $$PSI_CPP/varlist.h \ + $$PSI_CPP/showtextdlg.h \ + $$PSI_CPP/profiles.h \ + $$PSI_CPP/profiledlg.h \ + $$PSI_CPP/main.h \ + $$PSI_CPP/psiaccount.h \ + $$PSI_CPP/psicon.h \ + $$PSI_CPP/psievent.h \ + $$PSI_CPP/xmlconsole.h \ + $$PSI_CPP/contactview.h \ + $$PSI_CPP/psiiconset.h \ + $$PSI_CPP/userlist.h \ + $$PSI_CPP/mainwin.h \ + $$PSI_CPP/mainwin_p.h \ + $$PSI_CPP/rtparse.h \ + $$PSI_CPP/common.h \ + $$PSI_CPP/proxy.h \ + $$PSI_CPP/accountdlg.h \ + $$PSI_CPP/changepwdlg.h \ + $$PSI_CPP/msgmle.h \ + $$PSI_CPP/statusdlg.h \ + $$PSI_CPP/eventdlg.h \ + $$PSI_CPP/chatdlg.h \ + $$PSI_CPP/tabdlg.h \ + $$PSI_CPP/adduserdlg.h \ + $$PSI_CPP/groupchatdlg.h \ + $$PSI_CPP/infodlg.h \ + $$PSI_CPP/eventdb.h \ + $$PSI_CPP/historydlg.h \ + $$PSI_CPP/servicesdlg.h \ + $$PSI_CPP/jltest.h \ +# $$PSI_CPP/browserdlg.h \ + $$PSI_CPP/psitoolbar.h \ + $$PSI_CPP/passphrasedlg.h \ + $$PSI_CPP/vcardfactory.h \ + $$PSI_CPP/sslcertdlg.h \ + $$PSI_CPP/qwextend.h \ + $$PSI_CPP/tasklist.h \ + $$PSI_CPP/discodlg.h \ + $$PSI_CPP/alerticon.h \ + $$PSI_CPP/psipopup.h \ + $$PSI_CPP/psiapplication.h \ + $$PSI_CPP/filetransdlg.h \ + $$PSI_CPP/avatars.h \ + $$PSI_CPP/actionlist.h \ + $$PSI_CPP/psiactionlist.h \ + $$PSI_CPP/xdata_widget.h \ + $$PSI_CPP/xmpp_xdata.h + +# Source files +SOURCES += \ + $$PSI_CPP/varlist.cpp \ + $$PSI_CPP/showtextdlg.cpp \ + $$PSI_CPP/psi_profiles.cpp \ + $$PSI_CPP/profiledlg.cpp \ + $$PSI_CPP/main.cpp \ + $$PSI_CPP/psiaccount.cpp \ + $$PSI_CPP/psicon.cpp \ + $$PSI_CPP/psievent.cpp \ + $$PSI_CPP/xmlconsole.cpp \ + $$PSI_CPP/contactview.cpp \ + $$PSI_CPP/psiiconset.cpp \ + $$PSI_CPP/userlist.cpp \ + $$PSI_CPP/mainwin.cpp \ + $$PSI_CPP/mainwin_p.cpp \ + $$PSI_CPP/rtparse.cpp \ + $$PSI_CPP/common.cpp \ + $$PSI_CPP/proxy.cpp \ + $$PSI_CPP/accountdlg.cpp \ + $$PSI_CPP/changepwdlg.cpp \ + $$PSI_CPP/msgmle.cpp \ + $$PSI_CPP/statusdlg.cpp \ + $$PSI_CPP/eventdlg.cpp \ + $$PSI_CPP/chatdlg.cpp \ + $$PSI_CPP/tabdlg.cpp \ + $$PSI_CPP/adduserdlg.cpp \ + $$PSI_CPP/groupchatdlg.cpp \ + $$PSI_CPP/infodlg.cpp \ + $$PSI_CPP/eventdb.cpp \ + $$PSI_CPP/historydlg.cpp \ + $$PSI_CPP/servicesdlg.cpp \ + $$PSI_CPP/jltest.cpp \ +# $$PSI_CPP/browserdlg.cpp \ + $$PSI_CPP/psitoolbar.cpp \ + $$PSI_CPP/passphrasedlg.cpp \ + $$PSI_CPP/vcardfactory.cpp \ + $$PSI_CPP/sslcertdlg.cpp \ + $$PSI_CPP/qwextend.cpp \ + $$PSI_CPP/discodlg.cpp \ + $$PSI_CPP/alerticon.cpp \ + $$PSI_CPP/psipopup.cpp \ + $$PSI_CPP/psiapplication.cpp \ + $$PSI_CPP/filetransdlg.cpp \ + $$PSI_CPP/avatars.cpp \ + $$PSI_CPP/actionlist.cpp \ + $$PSI_CPP/psiactionlist.cpp \ + $$PSI_CPP/xdata_widget.cpp \ + $$PSI_CPP/xmpp_xdata.cpp +mac { + contains( DEFINES, HAVE_GROWL ) { + HEADERS += $$PSI_CPP/psigrowlnotifier.h + SOURCES += $$PSI_CPP/psigrowlnotifier.cpp + } +} + +# Qt Designer interfaces +INTERFACES = \ + $$PSI_CPP/ui_profileopen.ui \ + $$PSI_CPP/ui_profilemanage.ui \ + $$PSI_CPP/ui_profilenew.ui \ + $$PSI_CPP/ui_proxy.ui \ + $$PSI_CPP/ui_pgpkey.ui \ + $$PSI_CPP/ui_accountmanage.ui \ + $$PSI_CPP/ui_accountadd.ui \ + $$PSI_CPP/ui_accountreg.ui \ + $$PSI_CPP/ui_accountremove.ui \ + $$PSI_CPP/ui_accountmodify.ui \ + $$PSI_CPP/ui_changepw.ui \ + $$PSI_CPP/ui_addurl.ui \ + $$PSI_CPP/ui_adduser.ui \ + $$PSI_CPP/ui_gcjoin.ui \ + $$PSI_CPP/ui_info.ui \ + $$PSI_CPP/ui_search.ui \ + $$PSI_CPP/ui_about.ui \ +# $$PSI_CPP/ui_browser.ui \ + $$PSI_CPP/ui_passphrase.ui \ + $$PSI_CPP/ui_sslcert.ui \ + $$PSI_CPP/ui_disco.ui \ + $$PSI_CPP/ui_tip.ui \ + $$PSI_CPP/ui_filetrans.ui + +# options dialog +include(options/options.pri) + +LANG_PATH = ../lang + +# Translations +TRANSLATIONS = \ + $$LANG_PATH/psi_ar.ts \ + $$LANG_PATH/psi_ca.ts \ + $$LANG_PATH/psi_cs.ts \ + $$LANG_PATH/psi_da.ts \ + $$LANG_PATH/psi_de.ts \ + $$LANG_PATH/psi_el.ts \ + $$LANG_PATH/psi_eo.ts \ + $$LANG_PATH/psi_es.ts \ + $$LANG_PATH/psi_fi.ts \ + $$LANG_PATH/psi_fr.ts \ + $$LANG_PATH/psi_it.ts \ + $$LANG_PATH/psi_jp.ts \ + $$LANG_PATH/psi_mk.ts \ + $$LANG_PATH/psi_nl.ts \ + $$LANG_PATH/psi_pl.ts \ + $$LANG_PATH/psi_pt.ts \ + $$LANG_PATH/psi_ptbr.ts \ + $$LANG_PATH/psi_ru.ts \ + $$LANG_PATH/psi_se.ts \ + $$LANG_PATH/psi_sk.ts \ + $$LANG_PATH/psi_sr.ts \ + $$LANG_PATH/psi_zh.ts + +# Platform specifics +unix:!mac { + QMAKE_POST_LINK = rm -f ../psi ; ln -s src/psi ../psi +} +win32: { + RC_FILE = ../win32/psi_win32.rc + + # buggy MSVC workaround + contains(MAKEFILE_GENERATOR, MSVC): QMAKE_LFLAGS += /FORCE:MULTIPLE + contains(MAKEFILE_GENERATOR, MSVC.NET): QMAKE_LFLAGS += /FORCE:MULTIPLE +} +mac: { + QMAKE_LFLAGS += -framework Carbon -framework IOKit + QMAKE_INFO_PLIST = ../mac/Info.plist + RC_FILE = ../mac/application.icns + QMAKE_POST_LINK = cp -R ../COPYING ../README ../certs ../iconsets ../sound $(DESTDIR)../Resources ; echo "APPLpsi " > $(DESTDIR)../PkgInfo +} diff -Naur psi-0.10-orig/src/statusdlg.cpp psi-0.10/src/statusdlg.cpp --- psi-0.10-orig/src/statusdlg.cpp 2005-08-21 17:44:32.000000000 +0000 +++ psi-0.10/src/statusdlg.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -150,10 +150,9 @@ d->save = new QCheckBox(this); d->save->setText(tr("Sa&ve as Preset")); d->save->setChecked(false); - hb->addWidget(pb1); - hb->addStretch(1); hb->addWidget(d->save); hb->addStretch(1); + hb->addWidget(pb1); hb->addWidget(pb2); // set the rest up diff -Naur psi-0.10-orig/src/ui_about.ui.h psi-0.10/src/ui_about.ui.h --- psi-0.10-orig/src/ui_about.ui.h 2006-01-07 21:19:02.000000000 +0000 +++ psi-0.10/src/ui_about.ui.h 2006-01-14 00:43:34.000000000 +0000 @@ -69,6 +69,11 @@ thanks += details(QString::fromUtf8("Jacek Tomasiak"), "", "", "", tr("Patches")); + thanks += details(QString::fromUtf8("Nolan Eakins"), + "sneakin@semanticgap.com", + "sneakin@semanticgap.com", + "http://www.semanticgap.com/people/sneakin/", + tr("MUC, Patches")); //thanks += tr("Thanks to many others.\n" // "The above list only reflects the contributors I managed to keep track of.\n" diff -Naur psi-0.10-orig/src/ui_destroyroom.ui psi-0.10/src/ui_destroyroom.ui --- psi-0.10-orig/src/ui_destroyroom.ui 1970-01-01 00:00:00.000000000 +0000 +++ psi-0.10/src/ui_destroyroom.ui 2006-01-14 00:43:21.000000000 +0000 @@ -0,0 +1,165 @@ + +DestroyRoomUI + + + DestroyRoomUI + + + + 0 + 0 + 300 + 228 + + + + + 3 + 3 + 0 + 0 + + + + + 300 + 165 + + + + Destroy Room + + + + unnamed + + + 0 + + + + textLabel1 + + + &Why are you destroying the room? + + + le_reason + + + + + le_reason + + + This is an explanation that gets sent to the occupants of the room. + + + + + groupBox1 + + + Alternate Venue + + + The alternate venue is a room that occupants can goto when the room is destroyed. + + + + unnamed + + + + textLabel3 + + + &Recent: + + + PlainText + + + cb_recent + + + + + cb_recent + + + + 7 + 0 + 0 + 0 + + + + + + textLabel1_2 + + + R&oom: + + + le_room + + + + + le_room + + + This is the room for the alternate venue. + + + + + textLabel2 + + + &Host: + + + le_host + + + + + le_host + + + This is the server that the alternate venue is on. + + + + + + + spacer2 + + + Vertical + + + Expanding + + + + 20 + 1 + + + + + + + le_reason + cb_recent + le_room + le_host + + + diff -Naur psi-0.10-orig/src/ui_gcjoin.ui psi-0.10/src/ui_gcjoin.ui --- psi-0.10-orig/src/ui_gcjoin.ui 2005-11-06 20:19:59.000000000 +0000 +++ psi-0.10/src/ui_gcjoin.ui 2006-01-14 00:43:21.000000000 +0000 @@ -9,7 +9,7 @@ 0 0 280 - 235 + 300 @@ -29,59 +29,47 @@ Layout3 - + unnamed - - 0 - - - 6 - - + - TextLabel1 + lb_ident - Identity: + &Identity: - + - lb_ident - - - ident + layout_hack + + + + lb_ident_placeholder + + + ident + + + - - - - - Layout3 - - - - unnamed - - - 0 - - - 6 - - + TextLabel1_2 - Recent: + &Bookmarks: + + + cb_bookmarks - + - cb_recent + cb_bookmarks @@ -92,7 +80,7 @@ - + @@ -101,7 +89,7 @@ Room information: - + unnamed @@ -111,46 +99,120 @@ 6 - - - TextLabel2 - - - Host: - - - - - le_host - - - - - TextLabel3 - - - Room: - - - + - le_room + Layout5 + + + unnamed + + + + TextLabel2 + + + &Host: + + + le_host + + + + + le_host + + + + + TextLabel3 + + + R&oom: + + + le_room + + + + + le_room + + + + + TextLabel4 + + + &Nickname: + + + le_nick + + + + + le_nick + + + + + TextLabel15 + + + &Password: + + + le_password + + + + + le_password + + + Password + + + + + ck_savePword + + + &Save Password? + + + - + + + + Forget this groupchat + + + + + Remember this groupchat + + + + + Auto-join this groupchat + + - TextLabel4 - - - Nickname: + cb_remember - - - - le_nick + + + 7 + 0 + 0 + 0 + - + diff -Naur psi-0.10-orig/src/xdata_widget.cpp psi-0.10/src/xdata_widget.cpp --- psi-0.10-orig/src/xdata_widget.cpp 2005-08-21 17:44:27.000000000 +0000 +++ psi-0.10/src/xdata_widget.cpp 2006-01-14 00:43:21.000000000 +0000 @@ -30,6 +30,7 @@ #include #include #include +#include //---------------------------------------------------------------------------- // XDataField @@ -511,5 +512,7 @@ } d->fields.append(f); } + + setMinimumSize(grid->minimumSize()); } }