/* * browserdlg.cpp - main dialog for iq:browse 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 "browserdlg.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ui_search.h" #include "im.h" #include "xmpp_tasks.h" #include "psicon.h" #include "psiaccount.h" #include "common.h" #include "busywidget.h" #include "tasklist.h" #include "iconaction.h" //---------------------------------------------------------------------------- // BrowserData -- a shared data struct //---------------------------------------------------------------------------- struct BrowserData { PsiAccount *pa; TaskList *tasks; }; //---------------------------------------------------------------------------- // BrowserItem //---------------------------------------------------------------------------- class BrowserItem : public QObject, public QListViewItem { Q_OBJECT public: BrowserItem (const AgentItem &item, BrowserData *p, QListView *parent); BrowserItem (const AgentItem &item, BrowserData *p, QListViewItem *parent); QString text (int c) const; const AgentItem & item() const; void setOpen (bool o); public slots: void update(bool preBrowse = true, bool parentPreBrowsing = false); private slots: void updateFinished(); private: class Private; Private *d; friend class Private; void init (const AgentItem &item, BrowserData *p); public: BrowserDlg *dlg(); bool preBrowsingEnabled(); }; class BrowserItem::Private : public QObject { private: BrowserItem *bItem; public: Private (BrowserItem *parent) : QObject (parent) { bItem = parent; parentPreBrowsing = false; alreadyBrowsed = false; } void copyItem (const AgentItem &a) { if ( !a.name().isEmpty() ) item.setName( a.name() ); if ( !a.jid().full().isEmpty() ) item.setJid( a.jid() ); if ( !a.category().isEmpty() ) item.setCategory( a.category() ); if ( !a.type().isEmpty() ) { // FIXME: workaround for buggy AIM transport (it is also used for an ICQ transport) if ( !((item.type() == "aim" || item.type() == "icq") && a.type() == "jabber") ) { item.setType( a.type() ); } } if ( !a.category().isEmpty() ) bItem->setPixmap (0, category2icon(item.category(), item.type())); else bItem->setPixmap (0, is->status(item.jid(), STATUS_ONLINE)); if ( !a.features().list().isEmpty() ) item.setFeatures(a.features()); } void preBrowseChilds() { if ( !bItem->preBrowsingEnabled() ) return; // pre-browse all child items BrowserItem *child = (BrowserItem *)bItem->firstChild(); while ( child ) { child->update(false, true); // checkout update(bool, bool) function, if you change this child = (BrowserItem *)child->nextSibling(); } } AgentItem item; BrowserData *p; bool isRoot; BrowserItem *root; bool preBrowsing, parentPreBrowsing; bool alreadyBrowsed; }; BrowserItem::BrowserItem (const AgentItem &item, BrowserData *p, QListView *parent) : QListViewItem (parent) { init (item, p); d->isRoot = true; } BrowserItem::BrowserItem (const AgentItem &item, BrowserData *p, QListViewItem *parent) : QListViewItem (parent) { init (item, p); d->isRoot = false; } void BrowserItem::init (const AgentItem &item, BrowserData *p) { d = new Private (this); d->item = (AgentItem)item; d->p = p; // find 'root' BrowserItem d->root = this; while ( d->root->QListViewItem::parent() ) d->root = (BrowserItem *)d->root->QListViewItem::parent(); d->copyItem(item); // hmm... guess this is too much, concerning that we do 'd->item = item' already if ( !preBrowsingEnabled() ) setExpandable (true); } QString BrowserItem::text (int c) const { if ( c == 0 ) return d->item.name(); else if ( c == 1 ) return d->item.jid().full(); return "Oops :)"; } const AgentItem & BrowserItem::item() const { return d->item; } BrowserDlg *BrowserItem::dlg() { return (BrowserDlg *)listView()->parent()->parent(); } bool BrowserItem::preBrowsingEnabled() { return dlg()->cb_preBrowse->isChecked(); } void BrowserItem::setOpen (bool o) { if ( o ) { if ( !childCount() ) update(); else d->preBrowseChilds(); } QListViewItem::setOpen (o); } void BrowserItem::update(bool preBrowse, bool parentPreBrowsing) { if ( preBrowse == false && parentPreBrowsing == true ) { // to save some traffic if ( d->alreadyBrowsed ) return; // FIXME: currently, JUD doesn't seem to answer to browsing requests if ( d->item.category() == "service" && d->item.type() == "jud" ) return; } if ( d->root == this ) preBrowse = true; if ( !preBrowsingEnabled() ) preBrowse = false; d->preBrowsing = preBrowse; d->parentPreBrowsing = parentPreBrowsing; JT_Browse *jt = new JT_Browse(d->p->pa->client()->rootTask()); connect(jt, SIGNAL(finished()), SLOT(updateFinished())); jt->get(d->item.jid()); jt->go(true); d->p->tasks->append(jt); } void BrowserItem::updateFinished() { QObject *senderObj = (QObject *)sender(); JT_GetServices *jtAgents = 0; JT_Browse *jtBrowse = (senderObj->inherits("XMPP::JT_Browse") ? (JT_Browse *)senderObj : 0); if ( !jtBrowse ) jtAgents = (senderObj->inherits("XMPP::JT_GetServices") ? (JT_GetServices *)senderObj : 0); if( (jtBrowse && jtBrowse->success()) || (jtAgents && jtAgents->success()) ) { if ( jtBrowse ) d->copyItem ( jtBrowse->root() ); else { // iq:agents doesn't have as much useful info as iq:browse has, // so we're gonna to provide it by ourselves :-) AgentItem ai; ai.setName ( "Jabber Service" ); ai.setCategory ( "services" ); ai.setType ( "jabber" ); d->copyItem ( ai ); } if ( !d->parentPreBrowsing ) QListViewItem::setOpen (true); AgentList agents; if ( jtBrowse ) agents = jtBrowse->agents(); else agents = jtAgents->agents(); // add/update items for(AgentList::ConstIterator it = agents.begin(); it != agents.end(); ++it) { const AgentItem a = *it; bool found = false; BrowserItem *child = (BrowserItem *)firstChild(); while ( child ) { if ( child->item().jid().full() == a.jid().full() ) { child->d->copyItem ( a ); child->repaint(); found = true; break; } child = (BrowserItem *)child->nextSibling(); } if ( !found ) new BrowserItem (a, d->p, this); } // check for removed items QPtrList removeList; removeList.setAutoDelete (true); BrowserItem *child = (BrowserItem *)firstChild(); while ( child ) { bool found = false; for(AgentList::ConstIterator it = agents.begin(); it != agents.end(); ++it) { const AgentItem a = *it; if ( child->item().jid().full() == a.jid().full() ) { found = true; break; } } if ( !found ) removeList.append ( child ); // items will be deleted later child = (BrowserItem *)child->nextSibling(); } if ( jtBrowse && !d->parentPreBrowsing ) // comment this to auto-browse entire server tree d->preBrowseChilds(); // don't forget to remove '+' (or '-') sign in case, that the child list is empty setExpandable ( !agents.isEmpty() ); // root item is initially hidden if ( d->isRoot ) setVisible (true); } else { setExpandable ( false ); if ( jtBrowse && d->isRoot ) { // there are chances, that server just doesn't support browsing (this is // true for the Jabber Inc. commercial server). Go to jabber.com for // example JT_GetServices *jt = new JT_GetServices(d->p->pa->client()->rootTask()); connect(jt, SIGNAL(finished()), SLOT(updateFinished())); jt->get(d->item.jid()); jt->go(true); d->p->tasks->append(jt); } else { if ( !d->parentPreBrowsing ) { QString error; if ( jtBrowse ) error = jtBrowse->statusString(); else error = jtAgents->statusString(); QMessageBox::critical(dlg(), tr("Error"), tr("There was an error browsing the %1.\nReason: %2").arg(item().jid().full()).arg(error)); } } } dlg()->itemSelected (dlg()->lv_browse->selectedItem()); d->alreadyBrowsed = true; d->preBrowsing = false; d->parentPreBrowsing = false; repaint(); } //---------------------------------------------------------------------------- // BrowserDlg //---------------------------------------------------------------------------- class BrowserDlg::Private : public QObject { Q_OBJECT public: Private (BrowserDlg *parent, PsiAccount *pa) : QObject(parent) { dlg = parent; data.pa = pa; busy = parent->busy; connect(busy, SIGNAL(destroyed(QObject *)), SLOT(objectDestroyed(QObject *))); data.tasks = new TaskList; connect(data.tasks, SIGNAL(started()), SLOT(itemUpdateStarted())); connect(data.tasks, SIGNAL(finished()), SLOT(itemUpdateFinished())); // create actions actBrowse = new IconAction (tr("Browse"), "psi/jabber", tr("&Browse"), 0, dlg); connect (actBrowse, SIGNAL(activated()), SLOT(actionBrowse())); actRefresh = new IconAction (tr("Refresh"), "psi/reload", tr("&Refresh"), 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())); actRegister = new IconAction (tr("Register"), "psi/register", tr("&Register"), 0, dlg); connect (actRegister, SIGNAL(activated()), SLOT(actionRegister())); actSearch = new IconAction (tr("Search"), "psi/search", tr("&Search"), 0, dlg); connect (actSearch, SIGNAL(activated()), SLOT(actionSearch())); actJoin = new IconAction (tr("Join"), "psi/groupChat", tr("&Join"), 0, dlg); connect (actJoin, SIGNAL(activated()), SLOT(actionJoin())); actInfo = new IconAction (tr("Info"), "psi/vCard", tr("&Info"), 0, dlg); connect (actInfo, SIGNAL(activated()), SLOT(actionInfo())); /*actTime = new QAction (tr("Get Time"), QIconSet(is.getTime), tr("Get &Time"), 0, dlg); connect (actTime, SIGNAL(activated()), SLOT(actionGetTime())); actLast = new QAction (tr("Last Time"), QIconSet(is.getLastTime), tr("&Last Time"), 0, dlg); connect (actLast, SIGNAL(activated()), SLOT(actionGetLast())); actVersion = new QAction (tr("Get Version"), QIconSet(is.getVersion), tr("Get &Version"), 0, dlg); connect (actVersion, SIGNAL(activated()), SLOT(actionVersion())); actMessage = new QAction (tr("Send Message"), QIconSet(is.message.base()), tr("Send &Message"), 0, dlg); connect (actMessage, SIGNAL(activated()), SLOT(actionMessage())); actChat = new QAction (tr("Open Chat"), QIconSet(is.chat.base()), tr("Open &Chat"), 0, dlg); connect (actChat, SIGNAL(activated()), SLOT(actionChat())); actAdd = new QAction (tr("Add to roster"), QIconSet(is.add), tr("Add to &roster"), 0, dlg); connect (actAdd, SIGNAL(activated()), SLOT(actionAdd()));*/ 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())); toolBar = new QToolBar(dlg); actBack->addTo(toolBar); actBrowse->addTo(toolBar); actForward->addTo(toolBar); toolBar->addSeparator(); actRefresh->addTo(toolBar); actStop->addTo(toolBar); toolBar->addSeparator(); actRegister->addTo(toolBar); actSearch->addTo(toolBar); actJoin->addTo(toolBar); toolBar->addSeparator(); actInfo->addTo(toolBar); /*actVersion->addTo(toolBar); actTime->addTo(toolBar); actLast->addTo(toolBar); toolBar->addSeparator(); actMessage->addTo(toolBar); actChat->addTo(toolBar); toolBar->addSeparator(); actAdd->addTo(toolBar);*/ toolBar->setStretchableWidget(new StretchWidget(toolBar)); pa->accountLabel(toolBar, true); backHistory.setAutoDelete(true); forwardHistory.setAutoDelete(true); disableButtons(); actStop->setEnabled(false); // stop action is not handled by disableButtons() updateBackForward(); // same applies to back & forward } ~Private() { delete data.tasks; } public slots: void objectDestroyed(QObject *obj) { if ( obj == busy ) busy = 0; } void actionBrowse() { BrowserItem *it = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !it ) return; doBrowse( it->item().jid().full() ); } void actionRefresh() { BrowserItem *it = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !it ) return; it->update(); } void actionStop() { actStop->setEnabled(false); if ( busy ) busy->stop(); data.tasks->clear(); } void actionRegister() { BrowserItem *it = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !it ) return; emit dlg->signalRegister( it->item().jid() ); } void actionSearch() { BrowserItem *it = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !it ) return; emit dlg->signalSearch( it->item().jid() ); } void actionJoin() { BrowserItem *it = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !it ) return; emit dlg->signalJoin( it->item().jid() ); } void actionInfo() { BrowserItem *it = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !it ) return; data.pa->actionInfo( it->item().jid() ); } /*void actionGetTime() { BrowserItem *it = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !it ) return; // emit ... } void actionGetLast() { BrowserItem *it = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !it ) return; // emit ... }*/ void actionBack() { // add current selection to forward history QListViewItem *item = dlg->lv_browse->firstChild(); if ( item ) dlg->lv_browse->takeItem( item ); forwardHistory.append( new History(jid, item) ); // now, take info from back history... History *h = backHistory.last(); Jid j = h->jid(); QListViewItem *i = h->takeItem(); backHistory.removeLast(); // and restore view doBrowse(j.full(), true, i); } void actionForward() { // add current selection to back history QListViewItem *item = dlg->lv_browse->firstChild(); if ( item ) dlg->lv_browse->takeItem( item ); backHistory.append( new History(jid, item) ); // now, take info from forward history... History *h = forwardHistory.last(); Jid j = h->jid(); QListViewItem *i = h->takeItem(); forwardHistory.removeLast(); // and restore view doBrowse(j.full(), true, i); } void itemUpdateStarted() { actStop->setEnabled(true); if ( busy ) busy->start(); } void itemUpdateFinished() { actStop->setEnabled(false); if ( busy ) busy->stop(); } void doBrowse(const QString &host = QString::null, bool backForwardClicked = false, QListViewItem *rootItem = 0) { PsiAccount *pa = data.pa; if(!pa->checkConnected(dlg)) return; Jid j; QString h = host; if ( h.isEmpty() ) h = dlg->cb_browse->currentText(); j = h.stripWhiteSpace(); if ( !j.isValid() ) return; if ( !backForwardClicked && jid.full() != j.full() ) { QListViewItem *item = dlg->lv_browse->firstChild(); if ( item ) dlg->lv_browse->takeItem( item ); backHistory.append( new History(jid, item) ); forwardHistory.clear(); } updateBackForward(); jid = j; pa->psi()->recentBrowseAdd( jid.full() ); dlg->cb_browse->clear(); dlg->cb_browse->insertStringList(pa->psi()->recentBrowseList()); data.tasks->clear(); // also will call all all necessary functions disableButtons(); if ( !rootItem ) { dlg->lv_browse->clear(); AgentItem ai; ai.setJid( j ); BrowserItem *root = new BrowserItem (ai, &data, dlg->lv_browse); root->setVisible (false); // don't confuse users root->setOpen(true); // begin browsing } else { dlg->lv_browse->insertItem( rootItem ); // fixes multiple selection bug QListViewItemIterator it( dlg->lv_browse ); while ( it.current() ) { QListViewItem *item = it.current(); ++it; if ( item->isSelected() ) for (int i = 0; i <= 1; i++) // its boring to write same line twice :) dlg->lv_browse->setSelected(item, (bool)i); } } } public: BrowserDlg *dlg; Jid jid; BrowserData data; BusyWidget *busy; QToolBar *toolBar; QAction *actBrowse, *actRegister, *actSearch, *actJoin, *actInfo, /* *actTime, *actVersion, *actLast,*/ *actRefresh, *actStop, *actMessage, *actChat, *actAdd; QAction *actBack, *actForward; // 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; Jid j; public: History(Jid jj, QListViewItem *it) { item = it; j = jj; } ~History() { if ( item ) delete item; } QListViewItem *takeItem() { QListViewItem *i = item; item = 0; return i; } Jid jid() { return j; } }; typedef QPtrList HistoryList; HistoryList backHistory, forwardHistory; void disableButtons() { actRefresh->setEnabled(false); //actStop->setEnabled(false); actRegister->setEnabled(false); actSearch->setEnabled(false); actJoin->setEnabled(false); actBrowse->setEnabled(false); actInfo->setEnabled(false); /*actTime->setEnabled(false); actLast->setEnabled(false); actVersion->setEnabled(false); actMessage->setEnabled(false); actChat->setEnabled(false); actAdd->setEnabled(false);*/ } void enableButtons(const AgentItem &agent) { const Features &f = agent.features(); actBrowse->setEnabled(true); actRefresh->setEnabled(true); //actStop->setEnabled(false); actRegister->setEnabled( f.canRegister() ); actSearch->setEnabled( f.canSearch() ); actJoin->setEnabled( f.canGroupchat() ); actInfo->setEnabled(true); /*actTime->setEnabled(false); actLast->setEnabled(false);*/ } bool eventFilter (QObject *object, QEvent *event) { if ( object == dlg->lv_browse ) { if ( event->type() == QEvent::ContextMenu ) { QContextMenuEvent *e = (QContextMenuEvent *)event; BrowserItem *item = (BrowserItem *)dlg->lv_browse->selectedItem(); if ( !item ) return true; enableButtons ( item->item() ); QPopupMenu p; actBrowse->addTo (&p); actRefresh->addTo (&p); actStop->addTo (&p); p.insertSeparator(); actRegister->addTo (&p); actSearch->addTo (&p); actJoin->addTo (&p); p.insertSeparator(); actInfo->addTo (&p); /*actVersion->addTo (&p); actTime->addTo (&p); actLast->addTo (&p); p.insertSeparator(); actMessage->addTo (&p); actChat->addTo (&p); p.insertSeparator(); actAdd->addTo (&p);*/ e->accept(); p.exec ( e->globalPos() ); return true; } } return false; } void updateBackForward() { actBack->setEnabled( !backHistory.isEmpty() ); actForward->setEnabled( !forwardHistory.isEmpty() ); } }; BrowserDlg::BrowserDlg (const Jid &j, PsiAccount *pa) : BrowserUI (0, 0, WDestructiveClose) { d = new Private (this, pa); pa->dialogRegister(this); d->jid = j; setCaption(CAP(caption())); setIcon(is->transportStatus("transport", STATUS_ONLINE)); cb_browse->setInsertionPolicy(QComboBox::NoInsertion); cb_browse->insertStringList(pa->psi()->recentBrowseList()); cb_browse->setFocus(); connect(cb_browse, SIGNAL(activated(const QString &)), d, SLOT(doBrowse())); pb_browse->setDefault(true); cb_browse->setCurrentText(d->jid.full()); connect (pb_browse, SIGNAL(clicked()), d, SLOT(doBrowse())); lv_browse->installEventFilter (d); statusBar()->hide(); X11WM_CLASS("browser"); if(pa->loggedIn()) doBrowse(); } BrowserDlg::~BrowserDlg() { d->data.pa->dialogUnregister(this); } void BrowserDlg::doBrowse(const QString &host) { d->doBrowse(host, true); } void BrowserDlg::itemSelected (QListViewItem *item) { BrowserItem *it = (BrowserItem *)item; if ( !it ) { d->disableButtons(); return; } const AgentItem agent = it->item(); d->enableButtons ( agent ); } void BrowserDlg::itemDoubleclicked (QListViewItem *item) { BrowserItem *it = (BrowserItem *)item; if ( !it ) return; const AgentItem a = it->item(); const Features &f = a.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 ); } } // trigger default action if ( f.canGroupchat() ) { d->actionJoin(); } else { bool searchFirst = a.category() == "service" && a.type() == "jud"; if ( !searchFirst ) { if ( f.canRegister() ) d->actionRegister(); else if ( f.canSearch() ) d->actionSearch(); } else { if ( f.canSearch() ) d->actionSearch(); else if ( f.canRegister() ) d->actionRegister(); } } } #include "browserdlg.moc"