source: psi/trunk/src/discodlg.cpp@ 113

Last change on this file since 113 was 2, checked in by dmik, 19 years ago

Imported original Psi 0.10 sources from Affinix

File size: 32.6 KB
Line 
1/*
2 * discodlg.cpp - main dialog for the Service Discovery protocol
3 * Copyright (C) 2003 Michail Pishchagin
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; either version 2
8 * of the License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this library; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 *
19 */
20
21#include "discodlg.h"
22
23#include <qlistview.h>
24#include <qcombobox.h>
25#include <qcheckbox.h>
26#include <qmessagebox.h>
27#include <qaction.h>
28#include <qstatusbar.h>
29#include <qpopupmenu.h>
30#include <qsignalmapper.h>
31#include <qpushbutton.h>
32#include <qdragobject.h>
33#include <qtooltip.h>
34#include <qtoolbutton.h>
35
36#include "im.h"
37#include "xmpp_tasks.h"
38
39#include "psitoolbar.h"
40#include "tasklist.h"
41#include "psiaccount.h"
42#include "psicon.h"
43#include "busywidget.h"
44#include "common.h"
45#include "iconaction.h"
46
47//----------------------------------------------------------------------------
48// DiscoData -- a shared data struct
49//----------------------------------------------------------------------------
50
51class DiscoListItem;
52class DiscoConnector : public QObject
53{
54 Q_OBJECT
55public:
56 DiscoConnector(QObject *parent)
57 : QObject(parent) {}
58
59signals:
60 void itemUpdated(QListViewItem *);
61
62private:
63 friend class DiscoListItem;
64};
65
66struct DiscoData {
67 PsiAccount *pa;
68 TaskList *tasks;
69 DiscoConnector *d;
70
71 enum Protocol {
72 Auto,
73
74 Disco,
75 Browse,
76 Agents
77 };
78 Protocol protocol;
79};
80
81//----------------------------------------------------------------------------
82// ProtocolAction
83//----------------------------------------------------------------------------
84
85class ProtocolAction : public QAction
86{
87 Q_OBJECT
88public:
89 ProtocolAction(QString text, QString toolTip, QObject *parent, QSignalMapper *sm, int parm);
90
91 bool addTo(QWidget *w);
92
93public slots:
94 void setOn(bool);
95private:
96 QToolButton *btn;
97};
98
99ProtocolAction::ProtocolAction(QString text, QString toolTip, QObject *parent, QSignalMapper *sm, int parm)
100: QAction(text, text, 0, parent)
101{
102 setText( text );
103 setToggleAction(true);
104 setToolTip(toolTip);
105 connect(this, SIGNAL(activated()), sm, SLOT(map()));
106 sm->setMapping(this, parm);
107 btn = 0;
108}
109
110bool ProtocolAction::addTo(QWidget *w)
111{
112 if ( w->inherits("QToolBar") ) {
113 if ( btn )
114 delete btn;
115 QCString bname = name() + QCString("_action_button");
116 btn = new QToolButton ( w, bname );
117
118 btn->setToggleButton ( isToggleAction() );
119 btn->setOn( isOn() );
120 btn->setTextLabel ( text() );
121 btn->setEnabled ( isEnabled() );
122 btn->setUsesTextLabel( true );
123 QToolTip::add(btn, toolTip());
124
125 connect(btn, SIGNAL(toggled(bool)), this, SLOT(setOn(bool)));
126
127 return true;
128 }
129
130 return false;
131}
132
133void ProtocolAction::setOn(bool b)
134{
135 if ( btn ) {
136 if ( b )
137 emit activated();
138 btn->setOn(b);
139 }
140
141 QAction::setOn(b);
142}
143
144//----------------------------------------------------------------------------
145// DiscoListItem
146//----------------------------------------------------------------------------
147
148class DiscoListItem : public QObject, public QListViewItem
149{
150 Q_OBJECT
151public:
152 DiscoListItem(DiscoItem it, DiscoData *d, QListView *parent);
153 DiscoListItem(DiscoItem it, DiscoData *d, QListViewItem *parent);
154 ~DiscoListItem();
155
156 QString text(int columns) const;
157 void setOpen(bool open);
158 const DiscoItem &item() const;
159
160 void itemSelected();
161
162public slots: // the two are used internally by class, and also called by DiscoDlg::Private::refresh()
163 void updateInfo();
164 void updateItems(bool parentAutoItems = false);
165
166private slots:
167 void discoItemsFinished();
168 void discoInfoFinished();
169
170 void doBrowse(bool parentAutoItems = false);
171 void doAgents(bool parentAutoItems = false);
172 void browseFinished();
173 void agentsFinished();
174
175private:
176 DiscoItem di;
177 DiscoData *d;
178 bool isRoot;
179 bool alreadyItems, alreadyInfo;
180 bool autoItems; // used in updateItemsFinished
181 bool autoInfo;
182
183 void copyItem(const DiscoItem &);
184 void updateInfo(const DiscoItem &);
185 void updateItemsFinished(const DiscoList &);
186 void autoItemsChildren() const; // automatically call disco#items for children :-)
187 QString hash() { return computeHash( item().jid().full(), item().node() ); }
188 QString computeHash( QString jid, QString node );
189
190 // helper functions
191 void init(DiscoItem it, DiscoData *dd);
192
193 bool autoItemsEnabled() const;
194 bool autoInfoEnabled() const;
195 DiscoDlg *dlg() const;
196};
197
198DiscoListItem::DiscoListItem(DiscoItem it, DiscoData *_d, QListView *parent)
199: QListViewItem (parent)
200{
201 isRoot = true;
202
203 init(it, _d);
204}
205
206DiscoListItem::DiscoListItem(DiscoItem it, DiscoData *_d, QListViewItem *parent)
207: QListViewItem (parent)
208{
209 isRoot = false;
210
211 init(it, _d);
212}
213
214DiscoListItem::~DiscoListItem()
215{
216}
217
218void DiscoListItem::init(DiscoItem _item, DiscoData *_d)
219{
220 d = _d;
221 di = _item;
222 copyItem(_item);
223
224 alreadyItems = alreadyInfo = false;
225
226 if ( !autoItemsEnabled() )
227 setExpandable (true);
228
229 autoInfo = false;
230 if ( autoInfoEnabled() || isRoot ) {
231 updateInfo();
232 if ( !isRoot )
233 autoInfo = true;
234 }
235
236 //setDragEnabled(true); // EXPERIMENTAL
237}
238
239void DiscoListItem::copyItem(const DiscoItem &it)
240{
241 if ( !(!di.jid().full().isEmpty() && it.jid().full().isEmpty()) )
242 di.setJid ( it.jid() );
243 if ( !(!di.node().isEmpty() && it.node().isEmpty()) )
244 di.setNode ( it.node() );
245 if ( !(!di.name().isEmpty() && it.name().isEmpty()) )
246 di.setName ( it.name() );
247
248 di.setAction ( it.action() );
249
250 if ( !(!di.features().list().isEmpty() && it.features().list().isEmpty()) )
251 di.setFeatures ( it.features() );
252 if ( !(!di.identities().isEmpty() && it.identities().isEmpty()) )
253 di.setIdentities ( it.identities() );
254
255 if ( di.jid().userHost().left(4) == "jud." || di.jid().userHost().left(6) == "users." ) {
256 // nasty hack for the nasty (and outdated) JUD service :-/
257 if ( !di.features().canSearch() ) {
258 QStringList features = di.features().list();
259 features << "jabber:iq:search";
260 di.setFeatures( features );
261 }
262
263 bool found = false;
264 DiscoItem::Identities::ConstIterator it = di.identities().begin();
265 for ( ; it != di.identities().end(); ++it) {
266 if ( (*it).category == "service" && (*it).type == "jud" ) {
267 found = true;
268 break;
269 }
270 }
271 if ( !found ) {
272 DiscoItem::Identity id;
273 id.category = "service";
274 id.type = "jud";
275 DiscoItem::Identities ids;
276 ids << id;
277 di.setIdentities( ids );
278 }
279 }
280
281 bool pixmapOk = false;
282 if ( !di.identities().isEmpty() ) {
283 DiscoItem::Identity id = di.identities().first();
284
285 if ( !id.category.isEmpty() ) {
286 Icon ic = category2icon(id.category, id.type);
287
288 if ( !ic.impix().isNull() ) {
289 setPixmap (0, ic.impix().pixmap());
290 pixmapOk = true;
291 }
292 }
293 }
294
295 if ( !pixmapOk )
296 setPixmap (0, is->status(di.jid(), STATUS_ONLINE));
297
298 repaint();
299
300 if ( isSelected() ) // update actions
301 emit d->d->itemUpdated( this );
302}
303
304QString DiscoListItem::text (int c) const
305{
306 if (c == 0)
307 return di.name();
308 else if (c == 1)
309 return di.jid().full();
310 else if (c == 2)
311 return di.node();
312 return "";
313}
314
315const DiscoItem &DiscoListItem::item() const
316{
317 return di;
318}
319
320DiscoDlg *DiscoListItem::dlg() const
321{
322 return (DiscoDlg *)listView()->parent()->parent();
323}
324
325bool DiscoListItem::autoItemsEnabled() const
326{
327 return dlg()->ck_autoItems->isChecked();
328}
329
330bool DiscoListItem::autoInfoEnabled() const
331{
332 return dlg()->ck_autoInfo->isChecked();
333}
334
335void DiscoListItem::setOpen (bool o)
336{
337 if ( o ) {
338 if ( !alreadyItems )
339 updateItems();
340 else
341 autoItemsChildren();
342 }
343
344 QListViewItem::setOpen(o);
345}
346
347void DiscoListItem::itemSelected()
348{
349 if ( !alreadyInfo )
350 updateInfo();
351}
352
353void DiscoListItem::updateItems(bool parentAutoItems)
354{
355 if ( parentAutoItems ) {
356 // save traffic
357 if ( alreadyItems )
358 return;
359
360 // FIXME: currently, JUD doesn't seem to answer to browsing requests
361 if ( item().identities().size() ) {
362 DiscoItem::Identity id = item().identities().first();
363 if ( id.category == "service" && id.type == "jud" )
364 return;
365 }
366 QString j = item().jid().host(); // just another method to discover if we're gonna to browse JUD
367 if ( item().jid().user().isEmpty() && (j.left(4) == "jud." || j.left(6) == "users.") )
368 return;
369 }
370
371 autoItems = !parentAutoItems;
372
373 if ( !autoItemsEnabled() )
374 autoItems = false;
375
376 if ( d->protocol == DiscoData::Auto || d->protocol == DiscoData::Disco ) {
377 JT_DiscoItems *jt = new JT_DiscoItems(d->pa->client()->rootTask());
378 connect(jt, SIGNAL(finished()), SLOT(discoItemsFinished()));
379 jt->get(di.jid(), di.node());
380 jt->go(true);
381 d->tasks->append(jt);
382 }
383 else if ( d->protocol == DiscoData::Browse )
384 doBrowse(parentAutoItems);
385 else if ( d->protocol == DiscoData::Agents )
386 doAgents(parentAutoItems);
387}
388
389void DiscoListItem::discoItemsFinished()
390{
391 JT_DiscoItems *jt = (JT_DiscoItems *)sender();
392
393 if ( jt->success() ) {
394 updateItemsFinished(jt->items());
395 }
396 else if ( d->protocol == DiscoData::Auto ) {
397 doBrowse();
398 return;
399 }
400 else if ( !autoItems ) {
401 QString error = jt->statusString();
402 QMessageBox::critical(dlg(), tr("Error"), tr("There was an error getting items for <b>%1</b>.\nReason: %2").arg(di.jid().full()).arg(error));
403 }
404
405 alreadyItems = true;
406}
407
408void DiscoListItem::doBrowse(bool parentAutoItems)
409{
410 if ( parentAutoItems ) {
411 // save traffic
412 if ( alreadyItems )
413 return;
414
415 if ( item().identities().size() ) {
416 DiscoItem::Identity id = item().identities().first();
417 if ( id.category == "service" && id.type == "jud" )
418 return;
419 }
420 }
421
422 autoItems = !parentAutoItems;
423 if ( !autoItemsEnabled() )
424 autoItems = false;
425
426 JT_Browse *jt = new JT_Browse(d->pa->client()->rootTask());
427 connect(jt, SIGNAL(finished()), SLOT(browseFinished()));
428 jt->get(di.jid());
429 jt->go(true);
430 d->tasks->append(jt);
431}
432
433void DiscoListItem::browseFinished()
434{
435 JT_Browse *jt = (JT_Browse *)sender();
436
437 if ( jt->success() ) {
438 // update info
439 DiscoItem root;
440 root.fromAgentItem( jt->root() );
441 updateInfo(root);
442 alreadyInfo = true;
443 autoInfo = false;
444
445 // update items
446 AgentList from = jt->agents();
447 DiscoList to;
448 AgentList::Iterator it = from.begin();
449 for ( ; it != from.end(); ++it) {
450 DiscoItem item;
451 item.fromAgentItem( *it );
452
453 to.append( item );
454 }
455
456 updateItemsFinished(to);
457 }
458 else if ( d->protocol == DiscoData::Auto ) {
459 doAgents();
460 return;
461 }
462 else if ( !autoItems ) {
463 QString error = jt->statusString();
464 QMessageBox::critical(dlg(), tr("Error"), tr("There was an error browsing items for <b>%1</b>.\nReason: %2").arg(di.jid().full()).arg(error));
465 }
466
467 alreadyItems = true;
468}
469
470void DiscoListItem::doAgents(bool parentAutoItems)
471{
472 if ( parentAutoItems ) {
473 // save traffic
474 if ( alreadyItems )
475 return;
476
477 if ( item().identities().size() ) {
478 DiscoItem::Identity id = item().identities().first();
479 if ( id.category == "service" && id.type == "jud" )
480 return;
481 }
482 }
483
484 autoItems = !parentAutoItems;
485 if ( !autoItemsEnabled() )
486 autoItems = false;
487
488 JT_GetServices *jt = new JT_GetServices(d->pa->client()->rootTask());
489 connect(jt, SIGNAL(finished()), SLOT(agentsFinished()));
490 jt->get(di.jid());
491 jt->go(true);
492 d->tasks->append(jt);
493}
494
495void DiscoListItem::agentsFinished()
496{
497 JT_GetServices *jt = (JT_GetServices *)sender();
498
499 if ( jt->success() ) {
500 // update info
501 DiscoItem root;
502 DiscoItem::Identity id;
503 id.name = tr("Jabber Service");
504 id.category = "service";
505 id.type = "jabber";
506 DiscoItem::Identities ids;
507 ids.append(id);
508 root.setIdentities(ids);
509 updateInfo(root);
510 alreadyInfo = true;
511 autoInfo = false;
512
513 // update items
514 AgentList from = jt->agents();
515 DiscoList to;
516 AgentList::Iterator it = from.begin();
517 for ( ; it != from.end(); ++it) {
518 DiscoItem item;
519 item.fromAgentItem( *it );
520
521 to.append( item );
522 }
523
524 updateItemsFinished(to);
525 }
526 else if ( !autoItems ) {
527 QString error = jt->statusString();
528 QMessageBox::critical(dlg(), tr("Error"), tr("There was an error getting agents for <b>%1</b>.\nReason: %2").arg(di.jid().full()).arg(error));
529 }
530
531 alreadyItems = true;
532}
533
534QString DiscoListItem::computeHash( QString jid, QString node )
535{
536 QString ret = jid.replace( '@', "\\@" );
537 ret += "@";
538 ret += node.replace( '@', "\\@" );
539 return ret;
540}
541
542void DiscoListItem::updateItemsFinished(const DiscoList &list)
543{
544 QDict<DiscoListItem> children;
545 DiscoListItem *child = (DiscoListItem *)firstChild();
546 while ( child ) {
547 children.insert( child->hash(), child );
548
549 child = (DiscoListItem *)child->nextSibling();
550 }
551
552 // add/update items
553 for(DiscoList::ConstIterator it = list.begin(); it != list.end(); ++it) {
554 const DiscoItem a = *it;
555
556 QString key = computeHash(a.jid().full(), a.node());
557 DiscoListItem *child = children[ key ];
558
559 if ( child ) {
560 child->copyItem ( a );
561 children.remove( key );
562 }
563 else {
564 new DiscoListItem (a, d, this);
565 }
566 }
567
568 // remove all items that are not on new DiscoList
569 children.setAutoDelete( true );
570 children.clear();
571
572 if ( autoItems && isOpen() )
573 autoItemsChildren();
574
575 // don't forget to remove '+' (or '-') sign in case, that the child list is empty
576 setExpandable ( !list.isEmpty() );
577
578 repaint();
579
580 // root item is initially hidden
581 if ( isRoot && !isVisible() )
582 setVisible (true);
583}
584
585void DiscoListItem::autoItemsChildren() const
586{
587 if ( !autoItemsEnabled() )
588 return;
589
590 DiscoListItem *child = (DiscoListItem *)firstChild();
591 while ( child ) {
592 child->updateItems(true);
593
594 child = (DiscoListItem *)child->nextSibling();
595 }
596}
597
598void DiscoListItem::updateInfo()
599{
600 if ( d->protocol != DiscoData::Auto && d->protocol != DiscoData::Disco )
601 return;
602
603 JT_DiscoInfo *jt = new JT_DiscoInfo(d->pa->client()->rootTask());
604 connect(jt, SIGNAL(finished()), SLOT(discoInfoFinished()));
605 jt->get(di.jid(), di.node());
606 jt->go(true);
607 d->tasks->append(jt);
608}
609
610void DiscoListItem::discoInfoFinished()
611{
612 JT_DiscoInfo *jt = (JT_DiscoInfo *)sender();
613
614 if ( jt->success() ) {
615 updateInfo( jt->item() );
616 }
617 else if ( !autoInfo && d->protocol != DiscoData::Auto ) {
618 QString error = jt->statusString();
619 QMessageBox::critical(dlg(), tr("Error"), tr("There was an error getting item's info for <b>%1</b>.\nReason: %2").arg(di.jid().full()).arg(error));
620 }
621
622 alreadyInfo = true;
623 autoInfo = false;
624}
625
626void DiscoListItem::updateInfo(const DiscoItem &item)
627{
628 copyItem( item );
629
630 if ( isRoot && !isVisible() )
631 setVisible (true);
632}
633
634//----------------------------------------------------------------------------
635// DiscoList
636//----------------------------------------------------------------------------
637
638class DiscoListView : public QListView, public QToolTip
639{
640 Q_OBJECT
641public:
642 DiscoListView(QWidget *parent);
643
644protected:
645 void maybeTip(const QPoint &);
646 QDragObject *dragObject();
647};
648
649DiscoListView::DiscoListView(QWidget *parent)
650: QListView(parent), QToolTip(viewport())
651{
652 addColumn( tr( "Name" ) );
653 addColumn( tr( "JID" ) );
654 addColumn( tr( "Node" ) );
655}
656
657void DiscoListView::maybeTip(const QPoint &pos)
658{
659 DiscoListItem *i = (DiscoListItem *)itemAt(pos);
660 if(!i)
661 return;
662
663 // NAME <JID> (Node "NODE")
664 //
665 // Identities:
666 // (icon) NAME (Category "CATEGORY"; Type "TYPE")
667 // (icon) NAME (Category "CATEGORY"; Type "TYPE")
668 //
669 // Features:
670 // NAME (http://jabber.org/feature)
671 // NAME (http://jabber.org/feature)
672
673 // top row
674 QString text = "<qt><nobr>";
675 DiscoItem item = i->item();
676
677 if ( !item.name().isEmpty() )
678 text += item.name() + " ";
679
680 text += "&lt;" + item.jid().full() + "&gt;";
681
682 if ( !item.node().isEmpty() )
683 text += " (" + tr("Node") + " \"" + item.node() + "\")";
684
685 text += "<nobr>";
686
687 if ( !item.identities().isEmpty() || !item.features().list().isEmpty() )
688 text += "<br>\n";
689
690 // identities
691 if ( !item.identities().isEmpty() ) {
692 text += "<br>\n<b>" + tr("Identities:") + "</b>\n";
693
694 DiscoItem::Identities::ConstIterator it = item.identities().begin();
695 for ( ; it != item.identities().end(); ++it) {
696 text += "<br>";
697 Icon icon( category2icon((*it).category, (*it).type) );
698 if ( !icon.name().isEmpty() )
699 text += "<icon name=\"" + icon.name() + "\"> ";
700 text += (*it).name;
701 text += " (" + tr("Category") + " \"" + (*it).category + "\"; " + tr("Type") + " \"" + (*it).type + "\")\n";
702 }
703
704 if ( !item.features().list().isEmpty() )
705 text += "<br>\n";
706 }
707
708 // features
709 if ( !item.features().list().isEmpty() ) {
710 text += "<br>\n<b>" + tr("Features:") + "</b>\n";
711
712 QStringList features = item.features().list();
713 QStringList::ConstIterator it = features.begin();
714 for ( ; it != features.end(); ++it) {
715 Features f( *it );
716 text += "\n<br>";
717 if ( f.id() > Features::FID_None )
718 text += f.name() + " (";
719 text += *it;
720 if ( f.id() > Features::FID_None )
721 text += ")";
722 }
723 }
724
725 text += "</qt>";
726 QRect r( itemRect(i) );
727 tip(r, text);
728}
729
730QDragObject *DiscoListView::dragObject()
731{
732 DiscoListItem *i = (DiscoListItem *)selectedItem();
733 if(!i)
734 return 0;
735
736 QDragObject *d = new QTextDrag(i->item().jid().full(), this);
737 d->setPixmap(IconsetFactory::icon("status/online"), QPoint(8,8));
738 return d;
739}
740
741//----------------------------------------------------------------------------
742// DiscoDlg::Private
743//----------------------------------------------------------------------------
744
745class DiscoDlg::Private : public QObject
746{
747 Q_OBJECT
748
749private:
750 // helper class for use in toolbar
751 class StretchWidget : public QWidget
752 {
753 public:
754 StretchWidget(QWidget *parent)
755 : QWidget(parent)
756 {
757 setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
758 }
759 };
760
761 // helper class to store browser history
762 class History {
763 private:
764 QListViewItem *item;
765 public:
766 History(QListViewItem *it) {
767 item = it;
768 }
769
770 ~History() {
771 if ( item )
772 delete item;
773 }
774
775 QListViewItem *takeItem() {
776 QListViewItem *i = item;
777 item = 0;
778 return i;
779 }
780 };
781
782public: // data
783 DiscoDlg *dlg;
784 Jid jid;
785 QString node;
786
787 DiscoData data;
788
789 PsiToolBar *toolBar;
790 IconAction *actBrowse, *actBack, *actForward, *actRefresh, *actStop;
791
792 // custom actions, that will be added to toolbar and context menu
793 IconAction *actRegister, *actSearch, *actJoin, *actVCard, *actAdd;
794
795 typedef QPtrList<History> HistoryList;
796 HistoryList backHistory, forwardHistory;
797
798 BusyWidget *busy;
799
800public: // functions
801 Private(DiscoDlg *parent, PsiAccount *pa);
802 ~Private();
803
804public slots:
805 void doDisco(QString host = QString::null, QString node = QString::null);
806
807 void actionStop();
808 void actionRefresh();
809 void actionBrowse();
810
811 void actionBack();
812 void actionForward();
813 void updateBackForward();
814 void backForwardHelper(QListViewItem *);
815
816 void updateComboBoxes(Jid j, QString node);
817
818 void itemUpdateStarted();
819 void itemUpdateFinished();
820
821 void disableButtons();
822 void enableButtons(const DiscoItem &);
823
824 void itemSelected (QListViewItem *);
825 void itemDoubleclicked (QListViewItem *);
826 bool eventFilter (QObject *, QEvent *);
827
828 void setProtocol(int);
829
830 // features...
831 void actionActivated(int);
832
833 void objectDestroyed(QObject *);
834
835private:
836 friend class DiscoListItem;
837};
838
839DiscoDlg::Private::Private(DiscoDlg *parent, PsiAccount *pa)
840{
841 dlg = parent;
842 data.pa = pa;
843 data.tasks = new TaskList;
844 connect(data.tasks, SIGNAL(started()), SLOT(itemUpdateStarted()));
845 connect(data.tasks, SIGNAL(finished()), SLOT(itemUpdateFinished()));
846 data.d = new DiscoConnector(this);
847 connect(data.d, SIGNAL(itemUpdated(QListViewItem *)), SLOT(itemSelected (QListViewItem *)));
848 data.protocol = DiscoData::Auto;
849
850 backHistory.setAutoDelete(true);
851 forwardHistory.setAutoDelete(true);
852
853 // mess with widgets
854 busy = parent->busy;
855 connect(busy, SIGNAL(destroyed(QObject *)), SLOT(objectDestroyed(QObject *)));
856
857 QListView *lv_discoOld = dlg->lv_disco;
858 dlg->lv_disco = new DiscoListView(dlg->centralWidget());
859 replaceWidget(lv_discoOld, dlg->lv_disco);
860
861 dlg->lv_disco->installEventFilter (this);
862 connect(dlg->lv_disco, SIGNAL(selectionChanged (QListViewItem *)), SLOT(itemSelected (QListViewItem *)));;
863 connect(dlg->lv_disco, SIGNAL(doubleClicked (QListViewItem *)), SLOT(itemDoubleclicked (QListViewItem *)));;
864
865 // protocol actions
866 QSignalMapper *pm = new QSignalMapper(this);
867 connect(pm, SIGNAL(mapped(int)), SLOT(setProtocol(int)));
868 QActionGroup *protocolActions = new QActionGroup (this);
869 protocolActions->setExclusive(true);
870
871 ProtocolAction *autoProtocol = new ProtocolAction (tr("Auto"), tr("Automatically determine protocol"), protocolActions, pm, DiscoData::Auto);
872 ProtocolAction *discoProtocol = new ProtocolAction ("D", tr("Service Discovery"), protocolActions, pm, DiscoData::Disco);
873 ProtocolAction *browseProtocol = new ProtocolAction ("B", tr("Browse Services"), protocolActions, pm, DiscoData::Browse);
874 ProtocolAction *agentsProtocol = new ProtocolAction ("A", tr("Browse Agents"), protocolActions, pm, DiscoData::Agents);
875 autoProtocol->setOn(true);
876
877 // create actions
878 actBrowse = new IconAction (tr("Browse"), "psi/jabber", tr("&Browse"), 0, dlg);
879 connect (actBrowse, SIGNAL(activated()), SLOT(actionBrowse()));
880 actRefresh = new IconAction (tr("Refresh Item"), "psi/reload", tr("&Refresh Item"), 0, dlg);
881 connect (actRefresh, SIGNAL(activated()), SLOT(actionRefresh()));
882 actStop = new IconAction (tr("Stop"), "psi/stop", tr("Sto&p"), 0, dlg);
883 connect (actStop, SIGNAL(activated()), SLOT(actionStop()));
884 actBack = new IconAction (tr("Back"), "psi/arrowLeft", tr("&Back"), 0, dlg);
885 connect (actBack, SIGNAL(activated()), SLOT(actionBack()));
886 actForward = new IconAction (tr("Forward"), "psi/arrowRight", tr("&Forward"), 0, dlg);
887 connect (actForward, SIGNAL(activated()), SLOT(actionForward()));
888
889 // custom actions
890 QSignalMapper *sm = new QSignalMapper(this);
891 connect(sm, SIGNAL(mapped(int)), SLOT(actionActivated(int)));
892 actRegister = new IconAction (tr("Register"), "psi/register", tr("&Register"), 0, dlg);
893 connect (actRegister, SIGNAL(activated()), sm, SLOT(map()));
894 sm->setMapping(actRegister, Features::FID_Register);
895 actSearch = new IconAction (tr("Search"), "psi/search", tr("&Search"), 0, dlg);
896 connect (actSearch, SIGNAL(activated()), sm, SLOT(map()));
897 sm->setMapping(actSearch, Features::FID_Search);
898 actJoin = new IconAction (tr("Join"), "psi/groupChat", tr("&Join"), 0, dlg);
899 connect (actJoin, SIGNAL(activated()), sm, SLOT(map()));
900 sm->setMapping(actJoin, Features::FID_Groupchat);
901 actVCard = new IconAction (tr("vCard"), "psi/vCard", tr("&vCard"), 0, dlg);
902 connect (actVCard, SIGNAL(activated()), sm, SLOT(map()));
903 sm->setMapping(actVCard, Features::FID_VCard);
904 actAdd = new IconAction (tr("Add to roster"), "psi/addContact", tr("&Add to roster"), 0, dlg);
905 connect (actAdd, SIGNAL(activated()), sm, SLOT(map()));
906 sm->setMapping(actAdd, Features::FID_Add);
907
908 // create toolbar
909 toolBar = new PsiToolBar(dlg);
910 toolBar->setCustomizeable( false );
911
912 actBack->addTo(toolBar);
913 actBrowse->addTo(toolBar);
914 actForward->addTo(toolBar);
915
916 toolBar->addSeparator();
917 actRefresh->addTo(toolBar);
918 actStop->addTo(toolBar);
919
920 // custom actions
921 toolBar->addSeparator();
922 actRegister->addTo(toolBar);
923 actSearch->addTo(toolBar);
924 actJoin->addTo(toolBar);
925
926 toolBar->addSeparator();
927 actAdd->addTo(toolBar);
928 actVCard->addTo(toolBar);
929
930 // select protocol
931 toolBar->addSeparator();
932 autoProtocol->addTo(toolBar);
933 discoProtocol->addTo(toolBar);
934 browseProtocol->addTo(toolBar);
935 agentsProtocol->addTo(toolBar);
936
937 toolBar->setStretchableWidget(new StretchWidget(toolBar));
938 pa->accountLabel(toolBar, true);
939
940 // misc stuff
941 disableButtons();
942 actStop->setEnabled(false); // stop action is not handled by disableButtons()
943 updateBackForward(); // same applies to back & forward
944}
945
946DiscoDlg::Private::~Private()
947{
948 delete data.tasks;
949}
950
951void DiscoDlg::Private::doDisco(QString _host, QString _node)
952{
953 PsiAccount *pa = data.pa;
954 if ( !pa->checkConnected(dlg) )
955 return;
956
957 // Strip whitespace
958 Jid j;
959 QString host = _host;
960 if ( host.isEmpty() )
961 host = dlg->cb_address->currentText();
962 j = host.stripWhiteSpace();
963 if ( !j.isValid() )
964 return;
965
966 QString n = _node.stripWhiteSpace();
967 if ( n.isEmpty() )
968 n = dlg->cb_node->currentText().stripWhiteSpace();
969
970 // check, whether we need to update history
971 if ( (jid.full() != j.full()) || (node != n) ) {
972 QListViewItem *item = dlg->lv_disco->firstChild(); // get the root item
973
974 if ( item ) {
975 dlg->lv_disco->takeItem( item );
976
977 backHistory.append( new History(item) );
978 forwardHistory.clear();
979 }
980 }
981
982 jid = j;
983 node = n;
984
985 updateComboBoxes(jid, node);
986
987 data.tasks->clear(); // also will call all all necessary functions
988 disableButtons();
989 updateBackForward();
990
991 dlg->lv_disco->clear();
992
993 // create new root item
994 DiscoItem di;
995 di.setJid( jid );
996 di.setNode( node );
997
998 DiscoListItem *root = new DiscoListItem (di, &data, dlg->lv_disco);
999 root->setVisible (false); // don't confuse users with empty root
1000
1001 root->setOpen(true); // begin browsing
1002}
1003
1004void DiscoDlg::Private::updateComboBoxes(Jid j, QString n)
1005{
1006 data.pa->psi()->recentBrowseAdd( j.full() );
1007 dlg->cb_address->clear();
1008 dlg->cb_address->insertStringList(data.pa->psi()->recentBrowseList());
1009
1010 data.pa->psi()->recentNodeAdd( n );
1011 dlg->cb_node->clear();
1012 dlg->cb_node->insertStringList(data.pa->psi()->recentNodeList());
1013}
1014
1015void DiscoDlg::Private::actionStop()
1016{
1017 data.tasks->clear();
1018}
1019
1020void DiscoDlg::Private::actionRefresh()
1021{
1022 DiscoListItem *it = (DiscoListItem *)dlg->lv_disco->selectedItem();
1023 if ( !it )
1024 return;
1025
1026 it->updateItems();
1027 it->updateInfo();
1028}
1029
1030void DiscoDlg::Private::actionBrowse()
1031{
1032 DiscoListItem *it = (DiscoListItem *)dlg->lv_disco->selectedItem();
1033 if ( !it )
1034 return;
1035
1036 doDisco(it->item().jid().full(), it->item().node());
1037}
1038
1039void DiscoDlg::Private::actionBack()
1040{
1041 // add current selection to forward history
1042 QListViewItem *item = dlg->lv_disco->firstChild();
1043 if ( item ) {
1044 dlg->lv_disco->takeItem( item );
1045
1046 forwardHistory.append( new History(item) );
1047 }
1048
1049 // now, take info from back history...
1050 QListViewItem *i = backHistory.last()->takeItem();
1051 backHistory.removeLast();
1052
1053 // and restore view
1054 backForwardHelper(i);
1055}
1056
1057void DiscoDlg::Private::actionForward()
1058{
1059 // add current selection to back history
1060 QListViewItem *item = dlg->lv_disco->firstChild();
1061 if ( item ) {
1062 dlg->lv_disco->takeItem( item );
1063
1064 backHistory.append( new History(item) );
1065 }
1066
1067 // now, take info from forward history...
1068 QListViewItem *i = forwardHistory.last()->takeItem();
1069 forwardHistory.removeLast();
1070
1071 // and restore view
1072 backForwardHelper(i);
1073}
1074
1075void DiscoDlg::Private::backForwardHelper(QListViewItem *root)
1076{
1077 DiscoListItem *i = (DiscoListItem *)root;
1078
1079 jid = i->item().jid();
1080 node = i->item().node();
1081
1082 updateComboBoxes(jid, node);
1083
1084 data.tasks->clear(); // also will call all all necessary functions
1085 disableButtons();
1086 updateBackForward();
1087
1088 dlg->lv_disco->insertItem( root );
1089
1090 // fixes multiple selection bug
1091 QListViewItemIterator it( dlg->lv_disco );
1092 while ( it.current() ) {
1093 QListViewItem *item = it.current();
1094 ++it;
1095
1096 if ( item->isSelected() )
1097 for (int i = 0; i <= 1; i++) // it's boring to write same line twice :-)
1098 dlg->lv_disco->setSelected(item, (bool)i);
1099 }
1100}
1101
1102void DiscoDlg::Private::updateBackForward()
1103{
1104 actBack->setEnabled ( !backHistory.isEmpty() );
1105 actForward->setEnabled ( !forwardHistory.isEmpty() );
1106}
1107
1108void DiscoDlg::Private::itemUpdateStarted()
1109{
1110 actStop->setEnabled(true);
1111 if ( busy )
1112 busy->start();
1113}
1114
1115void DiscoDlg::Private::itemUpdateFinished()
1116{
1117 actStop->setEnabled(false);
1118 if ( busy )
1119 busy->stop();
1120}
1121
1122void DiscoDlg::Private::disableButtons()
1123{
1124 DiscoItem di;
1125 enableButtons ( di );
1126}
1127
1128void DiscoDlg::Private::enableButtons(const DiscoItem &it)
1129{
1130 bool itemSelected = !it.jid().full().isEmpty();
1131 actRefresh->setEnabled( itemSelected );
1132 actBrowse->setEnabled( itemSelected );
1133
1134 // custom actions
1135 Features f = it.features();
1136 actRegister->setEnabled( f.canRegister() );
1137 actSearch->setEnabled( f.canSearch() );
1138 actJoin->setEnabled( f.canGroupchat() );
1139 actAdd->setEnabled( itemSelected );
1140 actVCard->setEnabled( f.haveVCard() );
1141}
1142
1143void DiscoDlg::Private::itemSelected (QListViewItem *item)
1144{
1145 DiscoListItem *it = (DiscoListItem *)item;
1146 if ( !it ) {
1147 disableButtons();
1148 return;
1149 }
1150
1151 it->itemSelected();
1152
1153 const DiscoItem di = it->item();
1154 enableButtons ( di );
1155}
1156
1157void DiscoDlg::Private::itemDoubleclicked (QListViewItem *item)
1158{
1159 DiscoListItem *it = (DiscoListItem *)item;
1160 if ( !it )
1161 return;
1162
1163 const DiscoItem d = it->item();
1164 const Features &f = d.features();
1165
1166 // set the prior state of item
1167 // FIXME: causes minor flickering
1168 if ( f.canGroupchat() || f.canRegister() || f.canSearch() ) {
1169 if ( !it->isOpen() ) {
1170 if ( it->isExpandable() || it->childCount() )
1171 it->setOpen( true );
1172 }
1173 else {
1174 it->setOpen( false );
1175 }
1176 }
1177
1178 long id = 0;
1179
1180 // trigger default action
1181 if ( f.canGroupchat() ) {
1182 id = Features::FID_Groupchat;
1183 }
1184 else {
1185 // FIXME: check the category and type for JUD!
1186 DiscoItem::Identity ident = d.identities().first();
1187 bool searchFirst = ident.category == "service" && ident.type == "jud";
1188
1189 if ( searchFirst && f.canSearch() ) {
1190 id = Features::FID_Search;
1191 }
1192 else {
1193 if ( f.canRegister() )
1194 id = Features::FID_Register;
1195 }
1196 }
1197
1198 if ( id > 0 )
1199 emit dlg->featureActivated( Features::feature(id), d.jid(), d.node() );
1200}
1201
1202bool DiscoDlg::Private::eventFilter (QObject *object, QEvent *event)
1203{
1204 if ( object == dlg->lv_disco ) {
1205 if ( event->type() == QEvent::ContextMenu ) {
1206 QContextMenuEvent *e = (QContextMenuEvent *)event;
1207
1208 DiscoListItem *it = (DiscoListItem *)dlg->lv_disco->selectedItem();
1209 if ( !it )
1210 return true;
1211
1212 // prepare features list
1213 QValueList<long> idFeatures;
1214 QStringList features = it->item().features().list();
1215 { // convert all features to their IDs
1216 QStringList::Iterator it = features.begin();
1217 for ( ; it != features.end(); ++it) {
1218 Features f( *it );
1219 if ( f.id() > Features::FID_None )
1220 idFeatures.append( Features::id(*it) );
1221 }
1222 //qHeapSort(idFeatures);
1223 }
1224
1225 QValueList<long> ids;
1226 { // ensure, that there's in no duplicated IDs inside. FIXME: optimize this, anyone?
1227 long id = 0, count = 0;
1228 QValueList<long>::Iterator it;
1229 while ( count < (long)idFeatures.count() ) {
1230 bool found = false;
1231
1232 for (it = idFeatures.begin(); it != idFeatures.end(); ++it) {
1233 if ( id == *it ) {
1234 if ( !found ) {
1235 found = true;
1236 ids.append( id );
1237 }
1238 count++;
1239 }
1240 }
1241 id++;
1242 }
1243 }
1244
1245 // prepare popup menu
1246 QPopupMenu p;
1247
1248 actBrowse->addTo (&p);
1249 actRefresh->addTo (&p);
1250 actStop->addTo (&p);
1251
1252 // custom actions
1253 p.insertSeparator();
1254 actRegister->addTo(&p);
1255 actSearch->addTo(&p);
1256 actJoin->addTo(&p);
1257
1258 p.insertSeparator();
1259 actAdd->addTo(&p);
1260 actVCard->addTo(&p);
1261
1262 // popup with all available features
1263 QPopupMenu *fm = new QPopupMenu(&p);
1264 {
1265 QValueList<long>::Iterator it = ids.begin();
1266 for ( ; it != ids.end(); ++it)
1267 fm->insertItem(Features::name(*it), *it + 10000); // TODO: add pixmap
1268 }
1269
1270 //p.insertSeparator();
1271 //int menuId = p.insertItem(tr("Activate &Feature"), fm);
1272 //p.setItemEnabled(menuId, !ids.isEmpty());
1273
1274 // display popup
1275 e->accept();
1276 int r = p.exec ( e->globalPos() );
1277
1278 if ( r > 10000 )
1279 actionActivated(r-10000);
1280
1281 return true;
1282 }
1283 }
1284
1285 return false;
1286}
1287
1288void DiscoDlg::Private::actionActivated(int id)
1289{
1290 DiscoListItem *it = (DiscoListItem *)dlg->lv_disco->selectedItem();
1291 if ( !it )
1292 return;
1293
1294 emit dlg->featureActivated(Features::feature(id), it->item().jid(), it->item().node());
1295}
1296
1297void DiscoDlg::Private::objectDestroyed(QObject *obj)
1298{
1299 if ( obj == busy )
1300 busy = 0;
1301}
1302
1303void DiscoDlg::Private::setProtocol(int p)
1304{
1305 data.protocol = (DiscoData::Protocol)p;
1306}
1307
1308//----------------------------------------------------------------------------
1309// DiscoDlg
1310//----------------------------------------------------------------------------
1311
1312DiscoDlg::DiscoDlg(PsiAccount *pa, const Jid &jid, const QString &node)
1313: DiscoUI (0, 0, WDestructiveClose)
1314{
1315 // restore options
1316 ck_autoItems->setChecked(option.discoItems);
1317 ck_autoInfo->setChecked(option.discoInfo);
1318
1319 // initialize
1320 d = new Private(this, pa);
1321 d->jid = jid;
1322 d->node = node;
1323
1324 setCaption(CAP(caption()));
1325 setIcon(is->transportStatus("transport", STATUS_ONLINE));
1326 X11WM_CLASS("disco");
1327
1328 statusBar()->hide();
1329
1330 pb_browse->setDefault(true);
1331 connect (pb_browse, SIGNAL(clicked()), d, SLOT(doDisco()));
1332
1333
1334 cb_address->setInsertionPolicy(QComboBox::NoInsertion);
1335 cb_address->insertStringList(pa->psi()->recentBrowseList()); // FIXME
1336 cb_address->setFocus();
1337 connect(cb_address, SIGNAL(activated(const QString &)), d, SLOT(doDisco()));
1338 cb_address->setCurrentText(d->jid.full());
1339
1340 cb_node->setInsertionPolicy(QComboBox::NoInsertion);
1341 cb_node->insertStringList(pa->psi()->recentNodeList());
1342 connect(cb_node, SIGNAL(activated(const QString &)), d, SLOT(doDisco()));
1343 cb_node->setCurrentText(node);
1344
1345 if ( pa->loggedIn() )
1346 doDisco();
1347}
1348
1349DiscoDlg::~DiscoDlg()
1350{
1351 delete d;
1352
1353 // save options
1354 option.discoItems = ck_autoItems->isChecked();
1355 option.discoInfo = ck_autoInfo->isChecked();
1356}
1357
1358void DiscoDlg::doDisco(QString host, QString node)
1359{
1360 d->doDisco(host, node);
1361}
1362
1363#include "discodlg.moc"
Note: See TracBrowser for help on using the repository browser.