/* * discodlg.cpp - main dialog for the Service Discovery protocol * Copyright (C) 2003 Michail Pishchagin * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #include "discodlg.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "im.h" #include "xmpp_tasks.h" #include "psitoolbar.h" #include "tasklist.h" #include "psiaccount.h" #include "psicon.h" #include "busywidget.h" #include "common.h" #include "iconaction.h" //---------------------------------------------------------------------------- // DiscoData -- a shared data struct //---------------------------------------------------------------------------- class DiscoListItem; class DiscoConnector : public QObject { Q_OBJECT public: DiscoConnector(QObject *parent) : QObject(parent) {} signals: void itemUpdated(QListViewItem *); private: friend class DiscoListItem; }; struct DiscoData { PsiAccount *pa; TaskList *tasks; DiscoConnector *d; enum Protocol { Auto, Disco, Browse, Agents }; Protocol protocol; }; //---------------------------------------------------------------------------- // ProtocolAction //---------------------------------------------------------------------------- class ProtocolAction : public QAction { Q_OBJECT public: ProtocolAction(QString text, QString toolTip, QObject *parent, QSignalMapper *sm, int parm); bool addTo(QWidget *w); public slots: void setOn(bool); private: QToolButton *btn; }; ProtocolAction::ProtocolAction(QString text, QString toolTip, QObject *parent, QSignalMapper *sm, int parm) : QAction(text, text, 0, parent) { setText( text ); setToggleAction(true); setToolTip(toolTip); connect(this, SIGNAL(activated()), sm, SLOT(map())); sm->setMapping(this, parm); btn = 0; } bool ProtocolAction::addTo(QWidget *w) { if ( w->inherits("QToolBar") ) { if ( btn ) delete btn; QCString bname = name() + QCString("_action_button"); btn = new QToolButton ( w, bname ); btn->setToggleButton ( isToggleAction() ); btn->setOn( isOn() ); btn->setTextLabel ( text() ); btn->setEnabled ( isEnabled() ); btn->setUsesTextLabel( true ); QToolTip::add(btn, toolTip()); connect(btn, SIGNAL(toggled(bool)), this, SLOT(setOn(bool))); return true; } return false; } void ProtocolAction::setOn(bool b) { if ( btn ) { if ( b ) emit activated(); btn->setOn(b); } QAction::setOn(b); } //---------------------------------------------------------------------------- // DiscoListItem //---------------------------------------------------------------------------- class DiscoListItem : public QObject, public QListViewItem { Q_OBJECT public: DiscoListItem(DiscoItem it, DiscoData *d, QListView *parent); DiscoListItem(DiscoItem it, DiscoData *d, QListViewItem *parent); ~DiscoListItem(); QString text(int columns) const; void setOpen(bool open); const DiscoItem &item() const; void itemSelected(); public slots: // the two are used internally by class, and also called by DiscoDlg::Private::refresh() void updateInfo(); void updateItems(bool parentAutoItems = false); private slots: void discoItemsFinished(); void discoInfoFinished(); void doBrowse(bool parentAutoItems = false); void doAgents(bool parentAutoItems = false); void browseFinished(); void agentsFinished(); private: DiscoItem di; DiscoData *d; bool isRoot; bool alreadyItems, alreadyInfo; bool autoItems; // used in updateItemsFinished bool autoInfo; void copyItem(const DiscoItem &); void updateInfo(const DiscoItem &); void updateItemsFinished(const DiscoList &); void autoItemsChildren() const; // automatically call disco#items for children :-) QString hash() { return computeHash( item().jid().full(), item().node() ); } QString computeHash( QString jid, QString node ); // helper functions void init(DiscoItem it, DiscoData *dd); bool autoItemsEnabled() const; bool autoInfoEnabled() const; DiscoDlg *dlg() const; }; DiscoListItem::DiscoListItem(DiscoItem it, DiscoData *_d, QListView *parent) : QListViewItem (parent) { isRoot = true; init(it, _d); } DiscoListItem::DiscoListItem(DiscoItem it, DiscoData *_d, QListViewItem *parent) : QListViewItem (parent) { isRoot = false; init(it, _d); } DiscoListItem::~DiscoListItem() { } void DiscoListItem::init(DiscoItem _item, DiscoData *_d) { d = _d; di = _item; copyItem(_item); alreadyItems = alreadyInfo = false; if ( !autoItemsEnabled() ) setExpandable (true); autoInfo = false; if ( autoInfoEnabled() || isRoot ) { updateInfo(); if ( !isRoot ) autoInfo = true; } //setDragEnabled(true); // EXPERIMENTAL } void DiscoListItem::copyItem(const DiscoItem &it) { if ( !(!di.jid().full().isEmpty() && it.jid().full().isEmpty()) ) di.setJid ( it.jid() ); if ( !(!di.node().isEmpty() && it.node().isEmpty()) ) di.setNode ( it.node() ); if ( !(!di.name().isEmpty() && it.name().isEmpty()) ) di.setName ( it.name() ); di.setAction ( it.action() ); if ( !(!di.features().list().isEmpty() && it.features().list().isEmpty()) ) di.setFeatures ( it.features() ); if ( !(!di.identities().isEmpty() && it.identities().isEmpty()) ) di.setIdentities ( it.identities() ); if ( di.jid().userHost().left(4) == "jud." || di.jid().userHost().left(6) == "users." ) { // nasty hack for the nasty (and outdated) JUD service :-/ if ( !di.features().canSearch() ) { QStringList features = di.features().list(); features << "jabber:iq:search"; di.setFeatures( features ); } bool found = false; DiscoItem::Identities::ConstIterator it = di.identities().begin(); for ( ; it != di.identities().end(); ++it) { if ( (*it).category == "service" && (*it).type == "jud" ) { found = true; break; } } if ( !found ) { DiscoItem::Identity id; id.category = "service"; id.type = "jud"; DiscoItem::Identities ids; ids << id; di.setIdentities( ids ); } } bool pixmapOk = false; if ( !di.identities().isEmpty() ) { DiscoItem::Identity id = di.identities().first(); if ( !id.category.isEmpty() ) { Icon ic = category2icon(id.category, id.type); if ( !ic.impix().isNull() ) { setPixmap (0, ic.impix().pixmap()); pixmapOk = true; } } } if ( !pixmapOk ) setPixmap (0, is->status(di.jid(), STATUS_ONLINE)); repaint(); if ( isSelected() ) // update actions emit d->d->itemUpdated( this ); } QString DiscoListItem::text (int c) const { if (c == 0) return di.name(); else if (c == 1) return di.jid().full(); else if (c == 2) return di.node(); return ""; } const DiscoItem &DiscoListItem::item() const { return di; } DiscoDlg *DiscoListItem::dlg() const { return (DiscoDlg *)listView()->parent()->parent(); } bool DiscoListItem::autoItemsEnabled() const { return dlg()->ck_autoItems->isChecked(); } bool DiscoListItem::autoInfoEnabled() const { return dlg()->ck_autoInfo->isChecked(); } void DiscoListItem::setOpen (bool o) { if ( o ) { if ( !alreadyItems ) updateItems(); else autoItemsChildren(); } QListViewItem::setOpen(o); } void DiscoListItem::itemSelected() { if ( !alreadyInfo ) updateInfo(); } void DiscoListItem::updateItems(bool parentAutoItems) { if ( parentAutoItems ) { // save traffic if ( alreadyItems ) return; // FIXME: currently, JUD doesn't seem to answer to browsing requests if ( item().identities().size() ) { DiscoItem::Identity id = item().identities().first(); if ( id.category == "service" && id.type == "jud" ) return; } QString j = item().jid().host(); // just another method to discover if we're gonna to browse JUD if ( item().jid().user().isEmpty() && (j.left(4) == "jud." || j.left(6) == "users.") ) return; } autoItems = !parentAutoItems; if ( !autoItemsEnabled() ) autoItems = false; if ( d->protocol == DiscoData::Auto || d->protocol == DiscoData::Disco ) { JT_DiscoItems *jt = new JT_DiscoItems(d->pa->client()->rootTask()); connect(jt, SIGNAL(finished()), SLOT(discoItemsFinished())); jt->get(di.jid(), di.node()); jt->go(true); d->tasks->append(jt); } else if ( d->protocol == DiscoData::Browse ) doBrowse(parentAutoItems); else if ( d->protocol == DiscoData::Agents ) doAgents(parentAutoItems); } void DiscoListItem::discoItemsFinished() { JT_DiscoItems *jt = (JT_DiscoItems *)sender(); if ( jt->success() ) { updateItemsFinished(jt->items()); } else if ( d->protocol == DiscoData::Auto ) { doBrowse(); return; } else if ( !autoItems ) { QString error = jt->statusString(); QMessageBox::critical(dlg(), tr("Error"), tr("There was an error getting items for %1.\nReason: %2").arg(di.jid().full()).arg(error)); } alreadyItems = true; } void DiscoListItem::doBrowse(bool parentAutoItems) { if ( parentAutoItems ) { // save traffic if ( alreadyItems ) return; if ( item().identities().size() ) { DiscoItem::Identity id = item().identities().first(); if ( id.category == "service" && id.type == "jud" ) return; } } autoItems = !parentAutoItems; if ( !autoItemsEnabled() ) autoItems = false; JT_Browse *jt = new JT_Browse(d->pa->client()->rootTask()); connect(jt, SIGNAL(finished()), SLOT(browseFinished())); jt->get(di.jid()); jt->go(true); d->tasks->append(jt); } void DiscoListItem::browseFinished() { JT_Browse *jt = (JT_Browse *)sender(); if ( jt->success() ) { // update info DiscoItem root; root.fromAgentItem( jt->root() ); updateInfo(root); alreadyInfo = true; autoInfo = false; // update items AgentList from = jt->agents(); DiscoList to; AgentList::Iterator it = from.begin(); for ( ; it != from.end(); ++it) { DiscoItem item; item.fromAgentItem( *it ); to.append( item ); } updateItemsFinished(to); } else if ( d->protocol == DiscoData::Auto ) { doAgents(); return; } else if ( !autoItems ) { QString error = jt->statusString(); QMessageBox::critical(dlg(), tr("Error"), tr("There was an error browsing items for %1.\nReason: %2").arg(di.jid().full()).arg(error)); } alreadyItems = true; } void DiscoListItem::doAgents(bool parentAutoItems) { if ( parentAutoItems ) { // save traffic if ( alreadyItems ) return; if ( item().identities().size() ) { DiscoItem::Identity id = item().identities().first(); if ( id.category == "service" && id.type == "jud" ) return; } } autoItems = !parentAutoItems; if ( !autoItemsEnabled() ) autoItems = false; JT_GetServices *jt = new JT_GetServices(d->pa->client()->rootTask()); connect(jt, SIGNAL(finished()), SLOT(agentsFinished())); jt->get(di.jid()); jt->go(true); d->tasks->append(jt); } void DiscoListItem::agentsFinished() { JT_GetServices *jt = (JT_GetServices *)sender(); if ( jt->success() ) { // update info DiscoItem root; DiscoItem::Identity id; id.name = tr("Jabber Service"); id.category = "service"; id.type = "jabber"; DiscoItem::Identities ids; ids.append(id); root.setIdentities(ids); updateInfo(root); alreadyInfo = true; autoInfo = false; // update items AgentList from = jt->agents(); DiscoList to; AgentList::Iterator it = from.begin(); for ( ; it != from.end(); ++it) { DiscoItem item; item.fromAgentItem( *it ); to.append( item ); } updateItemsFinished(to); } else if ( !autoItems ) { QString error = jt->statusString(); QMessageBox::critical(dlg(), tr("Error"), tr("There was an error getting agents for %1.\nReason: %2").arg(di.jid().full()).arg(error)); } alreadyItems = true; } QString DiscoListItem::computeHash( QString jid, QString node ) { QString ret = jid.replace( '@', "\\@" ); ret += "@"; ret += node.replace( '@', "\\@" ); return ret; } void DiscoListItem::updateItemsFinished(const DiscoList &list) { QDict children; DiscoListItem *child = (DiscoListItem *)firstChild(); while ( child ) { children.insert( child->hash(), child ); child = (DiscoListItem *)child->nextSibling(); } // add/update items for(DiscoList::ConstIterator it = list.begin(); it != list.end(); ++it) { const DiscoItem a = *it; QString key = computeHash(a.jid().full(), a.node()); DiscoListItem *child = children[ key ]; if ( child ) { child->copyItem ( a ); children.remove( key ); } else { new DiscoListItem (a, d, this); } } // remove all items that are not on new DiscoList children.setAutoDelete( true ); children.clear(); if ( autoItems && isOpen() ) autoItemsChildren(); // don't forget to remove '+' (or '-') sign in case, that the child list is empty setExpandable ( !list.isEmpty() ); repaint(); // root item is initially hidden if ( isRoot && !isVisible() ) setVisible (true); } void DiscoListItem::autoItemsChildren() const { if ( !autoItemsEnabled() ) return; DiscoListItem *child = (DiscoListItem *)firstChild(); while ( child ) { child->updateItems(true); child = (DiscoListItem *)child->nextSibling(); } } void DiscoListItem::updateInfo() { if ( d->protocol != DiscoData::Auto && d->protocol != DiscoData::Disco ) return; JT_DiscoInfo *jt = new JT_DiscoInfo(d->pa->client()->rootTask()); connect(jt, SIGNAL(finished()), SLOT(discoInfoFinished())); jt->get(di.jid(), di.node()); jt->go(true); d->tasks->append(jt); } void DiscoListItem::discoInfoFinished() { JT_DiscoInfo *jt = (JT_DiscoInfo *)sender(); if ( jt->success() ) { updateInfo( jt->item() ); } else if ( !autoInfo && d->protocol != DiscoData::Auto ) { QString error = jt->statusString(); QMessageBox::critical(dlg(), tr("Error"), tr("There was an error getting item's info for %1.\nReason: %2").arg(di.jid().full()).arg(error)); } alreadyInfo = true; autoInfo = false; } void DiscoListItem::updateInfo(const DiscoItem &item) { copyItem( item ); if ( isRoot && !isVisible() ) setVisible (true); } //---------------------------------------------------------------------------- // DiscoList //---------------------------------------------------------------------------- class DiscoListView : public QListView, public QToolTip { Q_OBJECT public: DiscoListView(QWidget *parent); protected: void maybeTip(const QPoint &); QDragObject *dragObject(); }; DiscoListView::DiscoListView(QWidget *parent) : QListView(parent), QToolTip(viewport()) { addColumn( tr( "Name" ) ); addColumn( tr( "JID" ) ); addColumn( tr( "Node" ) ); } void DiscoListView::maybeTip(const QPoint &pos) { DiscoListItem *i = (DiscoListItem *)itemAt(pos); if(!i) return; // NAME (Node "NODE") // // Identities: // (icon) NAME (Category "CATEGORY"; Type "TYPE") // (icon) NAME (Category "CATEGORY"; Type "TYPE") // // Features: // NAME (http://jabber.org/feature) // NAME (http://jabber.org/feature) // top row QString text = ""; DiscoItem item = i->item(); if ( !item.name().isEmpty() ) text += item.name() + " "; text += "<" + item.jid().full() + ">"; if ( !item.node().isEmpty() ) text += " (" + tr("Node") + " \"" + item.node() + "\")"; text += ""; if ( !item.identities().isEmpty() || !item.features().list().isEmpty() ) text += "
\n"; // identities if ( !item.identities().isEmpty() ) { text += "
\n" + tr("Identities:") + "\n"; DiscoItem::Identities::ConstIterator it = item.identities().begin(); for ( ; it != item.identities().end(); ++it) { text += "
"; Icon icon( category2icon((*it).category, (*it).type) ); if ( !icon.name().isEmpty() ) text += " "; text += (*it).name; text += " (" + tr("Category") + " \"" + (*it).category + "\"; " + tr("Type") + " \"" + (*it).type + "\")\n"; } if ( !item.features().list().isEmpty() ) text += "
\n"; } // features if ( !item.features().list().isEmpty() ) { text += "
\n" + tr("Features:") + "\n"; QStringList features = item.features().list(); QStringList::ConstIterator it = features.begin(); for ( ; it != features.end(); ++it) { Features f( *it ); text += "\n
"; if ( f.id() > Features::FID_None ) text += f.name() + " ("; text += *it; if ( f.id() > Features::FID_None ) text += ")"; } } text += "
"; QRect r( itemRect(i) ); tip(r, text); } QDragObject *DiscoListView::dragObject() { DiscoListItem *i = (DiscoListItem *)selectedItem(); if(!i) return 0; QDragObject *d = new QTextDrag(i->item().jid().full(), this); d->setPixmap(IconsetFactory::icon("status/online"), QPoint(8,8)); return d; } //---------------------------------------------------------------------------- // DiscoDlg::Private //---------------------------------------------------------------------------- class DiscoDlg::Private : public QObject { Q_OBJECT private: // helper class for use in toolbar class StretchWidget : public QWidget { public: StretchWidget(QWidget *parent) : QWidget(parent) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); } }; // helper class to store browser history class History { private: QListViewItem *item; public: History(QListViewItem *it) { item = it; } ~History() { if ( item ) delete item; } QListViewItem *takeItem() { QListViewItem *i = item; item = 0; return i; } }; public: // data DiscoDlg *dlg; Jid jid; QString node; DiscoData data; PsiToolBar *toolBar; IconAction *actBrowse, *actBack, *actForward, *actRefresh, *actStop; // custom actions, that will be added to toolbar and context menu IconAction *actRegister, *actSearch, *actJoin, *actVCard, *actAdd; typedef QPtrList HistoryList; HistoryList backHistory, forwardHistory; BusyWidget *busy; public: // functions Private(DiscoDlg *parent, PsiAccount *pa); ~Private(); public slots: void doDisco(QString host = QString::null, QString node = QString::null); void actionStop(); void actionRefresh(); void actionBrowse(); void actionBack(); void actionForward(); void updateBackForward(); void backForwardHelper(QListViewItem *); void updateComboBoxes(Jid j, QString node); void itemUpdateStarted(); void itemUpdateFinished(); void disableButtons(); void enableButtons(const DiscoItem &); void itemSelected (QListViewItem *); void itemDoubleclicked (QListViewItem *); bool eventFilter (QObject *, QEvent *); void setProtocol(int); // features... void actionActivated(int); void objectDestroyed(QObject *); private: friend class DiscoListItem; }; DiscoDlg::Private::Private(DiscoDlg *parent, PsiAccount *pa) { dlg = parent; data.pa = pa; data.tasks = new TaskList; connect(data.tasks, SIGNAL(started()), SLOT(itemUpdateStarted())); connect(data.tasks, SIGNAL(finished()), SLOT(itemUpdateFinished())); data.d = new DiscoConnector(this); connect(data.d, SIGNAL(itemUpdated(QListViewItem *)), SLOT(itemSelected (QListViewItem *))); data.protocol = DiscoData::Auto; backHistory.setAutoDelete(true); forwardHistory.setAutoDelete(true); // mess with widgets busy = parent->busy; connect(busy, SIGNAL(destroyed(QObject *)), SLOT(objectDestroyed(QObject *))); QListView *lv_discoOld = dlg->lv_disco; dlg->lv_disco = new DiscoListView(dlg->centralWidget()); replaceWidget(lv_discoOld, dlg->lv_disco); dlg->lv_disco->installEventFilter (this); connect(dlg->lv_disco, SIGNAL(selectionChanged (QListViewItem *)), SLOT(itemSelected (QListViewItem *)));; connect(dlg->lv_disco, SIGNAL(doubleClicked (QListViewItem *)), SLOT(itemDoubleclicked (QListViewItem *)));; // protocol actions QSignalMapper *pm = new QSignalMapper(this); connect(pm, SIGNAL(mapped(int)), SLOT(setProtocol(int))); QActionGroup *protocolActions = new QActionGroup (this); protocolActions->setExclusive(true); ProtocolAction *autoProtocol = new ProtocolAction (tr("Auto"), tr("Automatically determine protocol"), protocolActions, pm, DiscoData::Auto); ProtocolAction *discoProtocol = new ProtocolAction ("D", tr("Service Discovery"), protocolActions, pm, DiscoData::Disco); ProtocolAction *browseProtocol = new ProtocolAction ("B", tr("Browse Services"), protocolActions, pm, DiscoData::Browse); ProtocolAction *agentsProtocol = new ProtocolAction ("A", tr("Browse Agents"), protocolActions, pm, DiscoData::Agents); autoProtocol->setOn(true); // create actions actBrowse = new IconAction (tr("Browse"), "psi/jabber", tr("&Browse"), 0, dlg); connect (actBrowse, SIGNAL(activated()), SLOT(actionBrowse())); actRefresh = new IconAction (tr("Refresh Item"), "psi/reload", tr("&Refresh Item"), 0, dlg); connect (actRefresh, SIGNAL(activated()), SLOT(actionRefresh())); actStop = new IconAction (tr("Stop"), "psi/stop", tr("Sto&p"), 0, dlg); connect (actStop, SIGNAL(activated()), SLOT(actionStop())); actBack = new IconAction (tr("Back"), "psi/arrowLeft", tr("&Back"), 0, dlg); connect (actBack, SIGNAL(activated()), SLOT(actionBack())); actForward = new IconAction (tr("Forward"), "psi/arrowRight", tr("&Forward"), 0, dlg); connect (actForward, SIGNAL(activated()), SLOT(actionForward())); // custom actions QSignalMapper *sm = new QSignalMapper(this); connect(sm, SIGNAL(mapped(int)), SLOT(actionActivated(int))); actRegister = new IconAction (tr("Register"), "psi/register", tr("&Register"), 0, dlg); connect (actRegister, SIGNAL(activated()), sm, SLOT(map())); sm->setMapping(actRegister, Features::FID_Register); actSearch = new IconAction (tr("Search"), "psi/search", tr("&Search"), 0, dlg); connect (actSearch, SIGNAL(activated()), sm, SLOT(map())); sm->setMapping(actSearch, Features::FID_Search); actJoin = new IconAction (tr("Join"), "psi/groupChat", tr("&Join"), 0, dlg); connect (actJoin, SIGNAL(activated()), sm, SLOT(map())); sm->setMapping(actJoin, Features::FID_Groupchat); actVCard = new IconAction (tr("vCard"), "psi/vCard", tr("&vCard"), 0, dlg); connect (actVCard, SIGNAL(activated()), sm, SLOT(map())); sm->setMapping(actVCard, Features::FID_VCard); actAdd = new IconAction (tr("Add to roster"), "psi/addContact", tr("&Add to roster"), 0, dlg); connect (actAdd, SIGNAL(activated()), sm, SLOT(map())); sm->setMapping(actAdd, Features::FID_Add); // create toolbar toolBar = new PsiToolBar(dlg); toolBar->setCustomizeable( false ); actBack->addTo(toolBar); actBrowse->addTo(toolBar); actForward->addTo(toolBar); toolBar->addSeparator(); actRefresh->addTo(toolBar); actStop->addTo(toolBar); // custom actions toolBar->addSeparator(); actRegister->addTo(toolBar); actSearch->addTo(toolBar); actJoin->addTo(toolBar); toolBar->addSeparator(); actAdd->addTo(toolBar); actVCard->addTo(toolBar); // select protocol toolBar->addSeparator(); autoProtocol->addTo(toolBar); discoProtocol->addTo(toolBar); browseProtocol->addTo(toolBar); agentsProtocol->addTo(toolBar); toolBar->setStretchableWidget(new StretchWidget(toolBar)); pa->accountLabel(toolBar, true); // misc stuff disableButtons(); actStop->setEnabled(false); // stop action is not handled by disableButtons() updateBackForward(); // same applies to back & forward } DiscoDlg::Private::~Private() { delete data.tasks; } void DiscoDlg::Private::doDisco(QString _host, QString _node) { PsiAccount *pa = data.pa; if ( !pa->checkConnected(dlg) ) return; // Strip whitespace Jid j; QString host = _host; if ( host.isEmpty() ) host = dlg->cb_address->currentText(); j = host.stripWhiteSpace(); if ( !j.isValid() ) return; QString n = _node.stripWhiteSpace(); if ( n.isEmpty() ) n = dlg->cb_node->currentText().stripWhiteSpace(); // check, whether we need to update history if ( (jid.full() != j.full()) || (node != n) ) { QListViewItem *item = dlg->lv_disco->firstChild(); // get the root item if ( item ) { dlg->lv_disco->takeItem( item ); backHistory.append( new History(item) ); forwardHistory.clear(); } } jid = j; node = n; updateComboBoxes(jid, node); data.tasks->clear(); // also will call all all necessary functions disableButtons(); updateBackForward(); dlg->lv_disco->clear(); // create new root item DiscoItem di; di.setJid( jid ); di.setNode( node ); DiscoListItem *root = new DiscoListItem (di, &data, dlg->lv_disco); root->setVisible (false); // don't confuse users with empty root root->setOpen(true); // begin browsing } void DiscoDlg::Private::updateComboBoxes(Jid j, QString n) { data.pa->psi()->recentBrowseAdd( j.full() ); dlg->cb_address->clear(); dlg->cb_address->insertStringList(data.pa->psi()->recentBrowseList()); data.pa->psi()->recentNodeAdd( n ); dlg->cb_node->clear(); dlg->cb_node->insertStringList(data.pa->psi()->recentNodeList()); } void DiscoDlg::Private::actionStop() { data.tasks->clear(); } void DiscoDlg::Private::actionRefresh() { DiscoListItem *it = (DiscoListItem *)dlg->lv_disco->selectedItem(); if ( !it ) return; it->updateItems(); it->updateInfo(); } void DiscoDlg::Private::actionBrowse() { DiscoListItem *it = (DiscoListItem *)dlg->lv_disco->selectedItem(); if ( !it ) return; doDisco(it->item().jid().full(), it->item().node()); } void DiscoDlg::Private::actionBack() { // add current selection to forward history QListViewItem *item = dlg->lv_disco->firstChild(); if ( item ) { dlg->lv_disco->takeItem( item ); forwardHistory.append( new History(item) ); } // now, take info from back history... QListViewItem *i = backHistory.last()->takeItem(); backHistory.removeLast(); // and restore view backForwardHelper(i); } void DiscoDlg::Private::actionForward() { // add current selection to back history QListViewItem *item = dlg->lv_disco->firstChild(); if ( item ) { dlg->lv_disco->takeItem( item ); backHistory.append( new History(item) ); } // now, take info from forward history... QListViewItem *i = forwardHistory.last()->takeItem(); forwardHistory.removeLast(); // and restore view backForwardHelper(i); } void DiscoDlg::Private::backForwardHelper(QListViewItem *root) { DiscoListItem *i = (DiscoListItem *)root; jid = i->item().jid(); node = i->item().node(); updateComboBoxes(jid, node); data.tasks->clear(); // also will call all all necessary functions disableButtons(); updateBackForward(); dlg->lv_disco->insertItem( root ); // fixes multiple selection bug QListViewItemIterator it( dlg->lv_disco ); while ( it.current() ) { QListViewItem *item = it.current(); ++it; if ( item->isSelected() ) for (int i = 0; i <= 1; i++) // it's boring to write same line twice :-) dlg->lv_disco->setSelected(item, (bool)i); } } void DiscoDlg::Private::updateBackForward() { actBack->setEnabled ( !backHistory.isEmpty() ); actForward->setEnabled ( !forwardHistory.isEmpty() ); } void DiscoDlg::Private::itemUpdateStarted() { actStop->setEnabled(true); if ( busy ) busy->start(); } void DiscoDlg::Private::itemUpdateFinished() { actStop->setEnabled(false); if ( busy ) busy->stop(); } void DiscoDlg::Private::disableButtons() { DiscoItem di; enableButtons ( di ); } void DiscoDlg::Private::enableButtons(const DiscoItem &it) { bool itemSelected = !it.jid().full().isEmpty(); actRefresh->setEnabled( itemSelected ); actBrowse->setEnabled( itemSelected ); // custom actions Features f = it.features(); actRegister->setEnabled( f.canRegister() ); actSearch->setEnabled( f.canSearch() ); actJoin->setEnabled( f.canGroupchat() ); actAdd->setEnabled( itemSelected ); actVCard->setEnabled( f.haveVCard() ); } void DiscoDlg::Private::itemSelected (QListViewItem *item) { DiscoListItem *it = (DiscoListItem *)item; if ( !it ) { disableButtons(); return; } it->itemSelected(); const DiscoItem di = it->item(); enableButtons ( di ); } void DiscoDlg::Private::itemDoubleclicked (QListViewItem *item) { DiscoListItem *it = (DiscoListItem *)item; if ( !it ) return; const DiscoItem d = it->item(); const Features &f = d.features(); // set the prior state of item // FIXME: causes minor flickering if ( f.canGroupchat() || f.canRegister() || f.canSearch() ) { if ( !it->isOpen() ) { if ( it->isExpandable() || it->childCount() ) it->setOpen( true ); } else { it->setOpen( false ); } } long id = 0; // trigger default action if ( f.canGroupchat() ) { id = Features::FID_Groupchat; } else { // FIXME: check the category and type for JUD! DiscoItem::Identity ident = d.identities().first(); bool searchFirst = ident.category == "service" && ident.type == "jud"; if ( searchFirst && f.canSearch() ) { id = Features::FID_Search; } else { if ( f.canRegister() ) id = Features::FID_Register; } } if ( id > 0 ) emit dlg->featureActivated( Features::feature(id), d.jid(), d.node() ); } bool DiscoDlg::Private::eventFilter (QObject *object, QEvent *event) { if ( object == dlg->lv_disco ) { if ( event->type() == QEvent::ContextMenu ) { QContextMenuEvent *e = (QContextMenuEvent *)event; DiscoListItem *it = (DiscoListItem *)dlg->lv_disco->selectedItem(); if ( !it ) return true; // prepare features list QValueList idFeatures; QStringList features = it->item().features().list(); { // convert all features to their IDs QStringList::Iterator it = features.begin(); for ( ; it != features.end(); ++it) { Features f( *it ); if ( f.id() > Features::FID_None ) idFeatures.append( Features::id(*it) ); } //qHeapSort(idFeatures); } QValueList ids; { // ensure, that there's in no duplicated IDs inside. FIXME: optimize this, anyone? long id = 0, count = 0; QValueList::Iterator it; while ( count < (long)idFeatures.count() ) { bool found = false; for (it = idFeatures.begin(); it != idFeatures.end(); ++it) { if ( id == *it ) { if ( !found ) { found = true; ids.append( id ); } count++; } } id++; } } // prepare popup menu QPopupMenu p; actBrowse->addTo (&p); actRefresh->addTo (&p); actStop->addTo (&p); // custom actions p.insertSeparator(); actRegister->addTo(&p); actSearch->addTo(&p); actJoin->addTo(&p); p.insertSeparator(); actAdd->addTo(&p); actVCard->addTo(&p); // popup with all available features QPopupMenu *fm = new QPopupMenu(&p); { QValueList::Iterator it = ids.begin(); for ( ; it != ids.end(); ++it) fm->insertItem(Features::name(*it), *it + 10000); // TODO: add pixmap } //p.insertSeparator(); //int menuId = p.insertItem(tr("Activate &Feature"), fm); //p.setItemEnabled(menuId, !ids.isEmpty()); // display popup e->accept(); int r = p.exec ( e->globalPos() ); if ( r > 10000 ) actionActivated(r-10000); return true; } } return false; } void DiscoDlg::Private::actionActivated(int id) { DiscoListItem *it = (DiscoListItem *)dlg->lv_disco->selectedItem(); if ( !it ) return; emit dlg->featureActivated(Features::feature(id), it->item().jid(), it->item().node()); } void DiscoDlg::Private::objectDestroyed(QObject *obj) { if ( obj == busy ) busy = 0; } void DiscoDlg::Private::setProtocol(int p) { data.protocol = (DiscoData::Protocol)p; } //---------------------------------------------------------------------------- // DiscoDlg //---------------------------------------------------------------------------- DiscoDlg::DiscoDlg(PsiAccount *pa, const Jid &jid, const QString &node) : DiscoUI (0, 0, WDestructiveClose) { // restore options ck_autoItems->setChecked(option.discoItems); ck_autoInfo->setChecked(option.discoInfo); // initialize d = new Private(this, pa); d->jid = jid; d->node = node; setCaption(CAP(caption())); setIcon(is->transportStatus("transport", STATUS_ONLINE)); X11WM_CLASS("disco"); statusBar()->hide(); pb_browse->setDefault(true); connect (pb_browse, SIGNAL(clicked()), d, SLOT(doDisco())); cb_address->setInsertionPolicy(QComboBox::NoInsertion); cb_address->insertStringList(pa->psi()->recentBrowseList()); // FIXME cb_address->setFocus(); connect(cb_address, SIGNAL(activated(const QString &)), d, SLOT(doDisco())); cb_address->setCurrentText(d->jid.full()); cb_node->setInsertionPolicy(QComboBox::NoInsertion); cb_node->insertStringList(pa->psi()->recentNodeList()); connect(cb_node, SIGNAL(activated(const QString &)), d, SLOT(doDisco())); cb_node->setCurrentText(node); if ( pa->loggedIn() ) doDisco(); } DiscoDlg::~DiscoDlg() { delete d; // save options option.discoItems = ck_autoItems->isChecked(); option.discoInfo = ck_autoInfo->isChecked(); } void DiscoDlg::doDisco(QString host, QString node) { d->doDisco(host, node); } #include "discodlg.moc"