1 # -*- coding: utf-8 -*-
4 ## Copyright (C) 2005-2006 Stéphan Kochen <stephan AT kochen.nl>
5 ## Copyright (C) 2005-2007 Nikos Kouremenos <kourem AT gmail.com>
6 ## Copyright (C) 2005-2010 Yann Leboulanger <asterix AT lagaule.org>
7 ## Copyright (C) 2006 Dimitur Kirov <dkirov AT gmail.com>
8 ## Copyright (C) 2006-2008 Jean-Marie Traissard <jim AT lapin.org>
9 ## Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
11 ## This file is part of Gajim.
13 ## Gajim is free software; you can redistribute it and/or modify
14 ## it under the terms of the GNU General Public License as published
15 ## by the Free Software Foundation; version 3 only.
17 ## Gajim is distributed in the hope that it will be useful,
18 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ## GNU General Public License for more details.
22 ## You should have received a copy of the GNU General Public License
23 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
26 # The appearance of the treeview, and parts of the dialog, are controlled by
27 # AgentBrowser (sub-)classes. Methods that probably should be overridden when
28 # subclassing are: (look at the docstrings and source for additional info)
29 # - def cleanup(self) *
30 # - def _create_treemodel(self) *
31 # - def _add_actions(self)
32 # - def _clean_actions(self)
33 # - def update_theme(self) *
34 # - def update_actions(self)
35 # - def default_action(self)
36 # - def _find_item(self, jid, node)
37 # - def _add_item(self, jid, node, parent_node, item, force)
38 # - def _update_item(self, iter_, jid, node, item)
39 # - def _update_info(self, iter_, jid, node, identities, features, data)
40 # - def _update_error(self, iter_, jid, node)
42 # * Should call the super class for this method.
43 # All others do not have to call back to the super class. (but can if they want
45 # There are more methods, of course, but this is a basic set.
61 from common
import gajim
62 from common
import xmpp
63 from common
.exceptions
import GajimGeneralException
64 from common
import helpers
65 from common
import ged
67 # Dictionary mapping category, type pairs to browser class, image pairs.
68 # This is a function, so we can call it after the classes are declared.
69 # For the browser class, None means that the service will only be browsable
70 # when it advertises disco as it's feature, False means it's never browsable.
71 def _gen_agent_type_info():
77 ('server', 'im'): (ToplevelAgentBrowser
, 'jabber'),
78 ('services', 'jabber'): (ToplevelAgentBrowser
, 'jabber'),
79 ('hierarchy', 'branch'): (AgentBrowser
, 'jabber'),
82 ('conference', 'text'): (MucBrowser
, 'conference'),
83 ('headline', 'rss'): (AgentBrowser
, 'rss'),
84 ('headline', 'weather'): (False, 'weather'),
85 ('gateway', 'weather'): (False, 'weather'),
86 ('_jid', 'weather'): (False, 'weather'),
87 ('gateway', 'sip'): (False, 'sip'),
88 ('directory', 'user'): (None, 'jud'),
89 ('pubsub', 'generic'): (PubSubBrowser
, 'pubsub'),
90 ('pubsub', 'service'): (PubSubBrowser
, 'pubsub'),
91 ('proxy', 'bytestreams'): (None, 'bytestreams'), # Socks5 FT proxy
92 ('headline', 'newmail'): (ToplevelAgentBrowser
, 'mail'),
95 ('conference', 'irc'): (ToplevelAgentBrowser
, 'irc'),
96 ('_jid', 'irc'): (False, 'irc'),
97 ('gateway', 'aim'): (False, 'aim'),
98 ('_jid', 'aim'): (False, 'aim'),
99 ('gateway', 'gadu-gadu'): (False, 'gadu-gadu'),
100 ('_jid', 'gadugadu'): (False, 'gadu-gadu'),
101 ('gateway', 'http-ws'): (False, 'http-ws'),
102 ('gateway', 'icq'): (False, 'icq'),
103 ('_jid', 'icq'): (False, 'icq'),
104 ('gateway', 'msn'): (False, 'msn'),
105 ('_jid', 'msn'): (False, 'msn'),
106 ('gateway', 'sms'): (False, 'sms'),
107 ('_jid', 'sms'): (False, 'sms'),
108 ('gateway', 'smtp'): (False, 'mail'),
109 ('gateway', 'yahoo'): (False, 'yahoo'),
110 ('_jid', 'yahoo'): (False, 'yahoo'),
111 ('gateway', 'mrim'): (False, 'mrim'),
112 ('_jid', 'mrim'): (False, 'mrim'),
113 ('gateway', 'facebook'): (False, 'facebook'),
114 ('_jid', 'facebook'): (False, 'facebook'),
117 # Category type to "human-readable" description string, and sort priority
119 'other': (_('Others'), 2),
120 'gateway': (_('Transports'), 0),
121 '_jid': (_('Transports'), 0),
122 #conference is a category for listing mostly groupchats in service discovery
123 'conference': (_('Conference'), 1),
127 class CacheDictionary
:
129 A dictionary that keeps items around for only a specific time. Lifetime is
130 in minutes. Getrefresh specifies whether to refresh when an item is merely
131 accessed instead of set aswell
134 def __init__(self
, lifetime
, getrefresh
= True):
135 self
.lifetime
= lifetime
* 1000 * 60
136 self
.getrefresh
= getrefresh
141 An object to store cache items and their timeouts
143 def __init__(self
, value
):
151 for key
in self
.cache
.keys():
152 item
= self
.cache
[key
]
154 gobject
.source_remove(item
.source
)
157 def _expire_timeout(self
, key
):
159 The timeout has expired, remove the object
161 if key
in self
.cache
:
165 def _refresh_timeout(self
, key
):
167 The object was accessed, refresh the timeout
169 item
= self
.cache
[key
]
171 gobject
.source_remove(item
.source
)
173 source
= gobject
.timeout_add_seconds(self
.lifetime
/1000, self
._expire
_timeout
, key
)
176 def __getitem__(self
, key
):
177 item
= self
.cache
[key
]
179 self
._refresh
_timeout
(key
)
182 def __setitem__(self
, key
, value
):
183 item
= self
.CacheItem(value
)
184 self
.cache
[key
] = item
185 self
._refresh
_timeout
(key
)
187 def __delitem__(self
, key
):
188 item
= self
.cache
[key
]
190 gobject
.source_remove(item
.source
)
193 def __contains__(self
, key
):
194 return key
in self
.cache
195 has_key
= __contains__
197 _icon_cache
= CacheDictionary(15)
199 def get_agent_address(jid
, node
= None):
201 Get an agent's address for displaying in the GUI
204 return '%s@%s' % (node
, str(jid
))
208 class Closure(object):
210 A weak reference to a callback with arguments as an object
212 Weak references to methods immediatly die, even if the object is still
213 alive. Besides a handy way to store a callback, this provides a workaround
214 that keeps a reference to the object instead.
216 Userargs and removeargs must be tuples.
219 def __init__(self
, cb
, userargs
= (), remove
= None, removeargs
= ()):
220 self
.userargs
= userargs
222 self
.removeargs
= removeargs
223 if isinstance(cb
, types
.MethodType
):
224 self
.meth_self
= weakref
.ref(cb
.im_self
, self
._remove
)
225 self
.meth_name
= cb
.func_name
227 self
.meth_self
= None
228 self
.cb
= weakref
.ref(cb
, self
._remove
)
230 raise TypeError('Object is not callable')
232 def _remove(self
, ref
):
234 self
.remove(self
, *self
.removeargs
)
236 def __call__(self
, *args
, **kwargs
):
238 obj
= self
.meth_self()
239 cb
= getattr(obj
, self
.meth_name
)
242 args
= args
+ self
.userargs
243 return cb(*args
, **kwargs
)
248 Class that caches our query results. Each connection will have it's own
249 ServiceCache instance
252 def __init__(self
, account
):
253 self
.account
= account
254 self
._items
= CacheDictionary(0, getrefresh
= False)
255 self
._info
= CacheDictionary(0, getrefresh
= False)
256 self
._subscriptions
= CacheDictionary(5, getrefresh
=False)
258 gajim
.ged
.register_event_handler('agent-items-received', ged
.GUI1
,
259 self
._nec
_agent
_items
_received
)
260 gajim
.ged
.register_event_handler('agent-items-error-received', ged
.GUI1
,
261 self
._nec
_agent
_items
_error
_received
)
262 gajim
.ged
.register_event_handler('agent-info-received', ged
.GUI1
,
263 self
._nec
_agent
_info
_received
)
264 gajim
.ged
.register_event_handler('agent-info-error-received', ged
.GUI1
,
265 self
._nec
_agent
_info
_error
_received
)
268 gajim
.ged
.remove_event_handler('agent-items-received', ged
.GUI1
,
269 self
._nec
_agent
_items
_received
)
270 gajim
.ged
.remove_event_handler('agent-items-error-received', ged
.GUI1
,
271 self
._nec
_agent
_items
_error
_received
)
272 gajim
.ged
.remove_event_handler('agent-info-received', ged
.GUI1
,
273 self
._nec
_agent
_info
_received
)
274 gajim
.ged
.remove_event_handler('agent-info-error-received', ged
.GUI1
,
275 self
._nec
_agent
_info
_error
_received
)
278 self
._items
.cleanup()
281 def _clean_closure(self
, cb
, type_
, addr
):
282 # A closure died, clean up
283 cbkey
= (type_
, addr
)
285 self
._cbs
[cbkey
].remove(cb
)
290 # Clean an empty list
291 if not self
._cbs
[cbkey
]:
294 def get_icon(self
, identities
= []):
296 Return the icon for an agent
298 # Grab the first identity with an icon
299 for identity
in identities
:
301 cat
, type_
= identity
['category'], identity
['type']
302 info
= _agent_type_info
[(cat
, type_
)]
309 # Loop fell through, default to unknown
310 info
= _agent_type_info
[(0, 0)]
312 if not filename
: # we don't have an image to show for this type
314 # Use the cache if possible
315 if filename
in _icon_cache
:
316 return _icon_cache
[filename
]
318 pix
= gtkgui_helpers
.get_icon_pixmap('gajim-agent-' + filename
, size
=32)
320 _icon_cache
[filename
] = pix
323 def get_browser(self
, identities
=[], features
=[]):
325 Return the browser class for an agent
327 # First pass, we try to find a ToplevelAgentBrowser
328 for identity
in identities
:
330 cat
, type_
= identity
['category'], identity
['type']
331 info
= _agent_type_info
[(cat
, type_
)]
335 if browser
and browser
== ToplevelAgentBrowser
:
338 # second pass, we haven't found a ToplevelAgentBrowser
339 for identity
in identities
:
341 cat
, type_
= identity
['category'], identity
['type']
342 info
= _agent_type_info
[(cat
, type_
)]
348 # NS_BROWSE is deprecated, but we check for it anyways.
349 # Some services list it in features and respond to
350 # NS_DISCO_ITEMS anyways.
351 # Allow browsing for unknown types aswell.
352 if (not features
and not identities
) or \
353 xmpp
.NS_DISCO_ITEMS
in features
or xmpp
.NS_BROWSE
in features
:
354 return ToplevelAgentBrowser
357 def get_info(self
, jid
, node
, cb
, force
=False, nofetch
=False, args
=()):
359 Get info for an agent
361 addr
= get_agent_address(jid
, node
)
363 if addr
in self
._info
and not force
:
364 args
= self
._info
[addr
] + args
370 # Create a closure object
371 cbkey
= ('info', addr
)
372 cb
= Closure(cb
, userargs
=args
, remove
=self
._clean
_closure
,
374 # Are we already fetching this?
375 if cbkey
in self
._cbs
:
376 self
._cbs
[cbkey
].append(cb
)
378 self
._cbs
[cbkey
] = [cb
]
379 gajim
.connections
[self
.account
].discoverInfo(jid
, node
)
381 def get_items(self
, jid
, node
, cb
, force
=False, nofetch
=False, args
=()):
383 Get a list of items in an agent
385 addr
= get_agent_address(jid
, node
)
387 if addr
in self
._items
and not force
:
388 args
= (self
._items
[addr
],) + args
394 # Create a closure object
395 cbkey
= ('items', addr
)
396 cb
= Closure(cb
, userargs
=args
, remove
=self
._clean
_closure
,
398 # Are we already fetching this?
399 if cbkey
in self
._cbs
:
400 self
._cbs
[cbkey
].append(cb
)
402 self
._cbs
[cbkey
] = [cb
]
403 gajim
.connections
[self
.account
].discoverItems(jid
, node
)
405 def _nec_agent_info_received(self
, obj
):
407 Callback for when we receive an agent's info
408 array is (agent, node, identities, features, data)
410 # We receive events from all accounts from GED
411 if obj
.conn
.name
!= self
.account
:
413 self
._on
_agent
_info
(obj
.fjid
, obj
.node
, obj
.identities
, obj
.features
,
416 def _on_agent_info(self
, fjid
, node
, identities
, features
, data
):
417 addr
= get_agent_address(fjid
, node
)
420 self
._info
[addr
] = (identities
, features
, data
)
423 cbkey
= ('info', addr
)
424 if cbkey
in self
._cbs
:
425 for cb
in self
._cbs
[cbkey
]:
426 cb(fjid
, node
, identities
, features
, data
)
427 # clean_closure may have beaten us to it
428 if cbkey
in self
._cbs
:
431 def _nec_agent_items_received(self
, obj
):
433 Callback for when we receive an agent's items
434 array is (agent, node, items)
436 # We receive events from all accounts from GED
437 if obj
.conn
.name
!= self
.account
:
440 addr
= get_agent_address(obj
.fjid
, obj
.node
)
443 self
._items
[addr
] = obj
.items
446 cbkey
= ('items', addr
)
447 if cbkey
in self
._cbs
:
448 for cb
in self
._cbs
[cbkey
]:
449 cb(obj
.fjid
, obj
.node
, obj
.items
)
450 # clean_closure may have beaten us to it
451 if cbkey
in self
._cbs
:
454 def _nec_agent_info_error_received(self
, obj
):
456 Callback for when a query fails. Even after the browse and agents
459 # We receive events from all accounts from GED
460 if obj
.conn
.name
!= self
.account
:
462 addr
= get_agent_address(obj
.fjid
)
465 cbkey
= ('info', addr
)
466 if cbkey
in self
._cbs
:
467 for cb
in self
._cbs
[cbkey
]:
468 cb(obj
.fjid
, '', 0, 0, 0)
469 # clean_closure may have beaten us to it
470 if cbkey
in self
._cbs
:
473 def _nec_agent_items_error_received(self
, obj
):
475 Callback for when a query fails. Even after the browse and agents
478 # We receive events from all accounts from GED
479 if obj
.conn
.name
!= self
.account
:
481 addr
= get_agent_address(obj
.fjid
)
484 cbkey
= ('items', addr
)
485 if cbkey
in self
._cbs
:
486 for cb
in self
._cbs
[cbkey
]:
488 # clean_closure may have beaten us to it
489 if cbkey
in self
._cbs
:
492 # object is needed so that @property works
493 class ServiceDiscoveryWindow(object):
495 Class that represents the Services Discovery window
498 def __init__(self
, account
, jid
='', node
='', address_entry
=False,
499 parent
=None, initial_identities
=None):
500 self
.account
= account
503 jid
= gajim
.config
.get_per('accounts', account
, 'hostname')
511 self
.reloading
= False
514 if gajim
.connections
[account
].connected
< 2:
515 dialogs
.ErrorDialog(_('You are not connected to the server'),
516 _('Without a connection, you can not browse available services'))
517 raise RuntimeError, 'You must be connected to browse services'
519 # Get a ServicesCache object.
521 self
.cache
= gajim
.connections
[account
].services_cache
522 except AttributeError:
523 self
.cache
= ServicesCache(account
)
524 gajim
.connections
[account
].services_cache
= self
.cache
526 if initial_identities
:
527 self
.cache
._on
_agent
_info
(jid
, node
, initial_identities
, [], None)
528 self
.xml
= gtkgui_helpers
.get_gtk_builder('service_discovery_window.ui')
529 self
.window
= self
.xml
.get_object('service_discovery_window')
530 self
.services_treeview
= self
.xml
.get_object('services_treeview')
532 # This is more reliable than the cursor-changed signal.
533 selection
= self
.services_treeview
.get_selection()
534 selection
.connect_after('changed',
535 self
.on_services_treeview_selection_changed
)
536 self
.services_scrollwin
= self
.xml
.get_object('services_scrollwin')
537 self
.progressbar
= self
.xml
.get_object('services_progressbar')
538 self
.banner
= self
.xml
.get_object('banner_agent_label')
539 self
.banner_icon
= self
.xml
.get_object('banner_agent_icon')
540 self
.banner_eventbox
= self
.xml
.get_object('banner_agent_eventbox')
541 self
.style_event_id
= 0
542 self
.banner
.realize()
544 self
.action_buttonbox
= self
.xml
.get_object('action_buttonbox')
547 self
.address_comboboxentry
= None
548 address_table
= self
.xml
.get_object('address_table')
550 self
.address_comboboxentry
= self
.xml
.get_object(
551 'address_comboboxentry')
552 self
.address_comboboxentry_entry
= self
.address_comboboxentry
.child
553 self
.address_comboboxentry_entry
.set_activates_default(True)
555 self
.latest_addresses
= gajim
.config
.get(
556 'latest_disco_addresses').split()
557 if jid
in self
.latest_addresses
:
558 self
.latest_addresses
.remove(jid
)
559 self
.latest_addresses
.insert(0, jid
)
560 if len(self
.latest_addresses
) > 10:
561 self
.latest_addresses
= self
.latest_addresses
[0:10]
562 for j
in self
.latest_addresses
:
563 self
.address_comboboxentry
.append_text(j
)
564 self
.address_comboboxentry
.child
.set_text(jid
)
566 # Don't show it at all if we didn't ask for it
567 address_table
.set_no_show_all(True)
570 accel_group
= gtk
.AccelGroup()
571 keyval
, mod
= gtk
.accelerator_parse('<Control>r')
572 accel_group
.connect_group(keyval
, mod
, gtk
.ACCEL_VISIBLE
,
573 self
.accel_group_func
)
574 self
.window
.add_accel_group(accel_group
)
576 self
._initial
_state
()
577 self
.xml
.connect_signals(self
)
578 self
.travel(jid
, node
)
579 self
.window
.show_all()
582 def _get_account(self
):
586 def _set_account(self
, value
):
588 self
.cache
.account
= value
590 self
.browser
.account
= value
592 def accel_group_func(self
, accel_group
, acceleratable
, keyval
, modifier
):
593 if (modifier
& gtk
.gdk
.CONTROL_MASK
) and (keyval
== gtk
.keysyms
.r
):
596 def _initial_state(self
):
598 Set some initial state on the window. Separated in a method because it's
599 handy to use within browser's cleanup method
601 self
.progressbar
.hide()
602 title_text
= _('Service Discovery using account %s') % self
.account
603 self
.window
.set_title(title_text
)
604 self
._set
_window
_banner
_text
(_('Service Discovery'))
605 self
.banner_icon
.clear()
606 self
.banner_icon
.hide() # Just clearing it doesn't work
608 def _set_window_banner_text(self
, text
, text_after
= None):
609 theme
= gajim
.config
.get('roster_theme')
610 bannerfont
= gajim
.config
.get_per('themes', theme
, 'bannerfont')
611 bannerfontattrs
= gajim
.config
.get_per('themes', theme
,
615 font
= pango
.FontDescription(bannerfont
)
617 font
= pango
.FontDescription('Normal')
619 # B is attribute set by default
620 if 'B' in bannerfontattrs
:
621 font
.set_weight(pango
.WEIGHT_HEAVY
)
622 if 'I' in bannerfontattrs
:
623 font
.set_style(pango
.STYLE_ITALIC
)
625 font_attrs
= 'font_desc="%s"' % font
.to_string()
626 font_size
= font
.get_size()
628 # in case there is no font specified we use x-large font size
630 font_attrs
= '%s size="large"' % font_attrs
631 markup
= '<span %s>%s</span>' % (font_attrs
, text
)
633 font
.set_weight(pango
.WEIGHT_NORMAL
)
634 markup
= '%s\n<span font_desc="%s" size="small">%s</span>' % \
635 (markup
, font
.to_string(), text_after
)
636 self
.banner
.set_markup(markup
)
638 def paint_banner(self
):
640 Repaint the banner with theme color
642 theme
= gajim
.config
.get('roster_theme')
643 bgcolor
= gajim
.config
.get_per('themes', theme
, 'bannerbgcolor')
644 textcolor
= gajim
.config
.get_per('themes', theme
, 'bannertextcolor')
645 self
.disconnect_style_event()
647 color
= gtk
.gdk
.color_parse(bgcolor
)
648 self
.banner_eventbox
.modify_bg(gtk
.STATE_NORMAL
, color
)
654 color
= gtk
.gdk
.color_parse(textcolor
)
655 self
.banner
.modify_fg(gtk
.STATE_NORMAL
, color
)
659 if default_fg
or default_bg
:
660 self
._on
_style
_set
_event
(self
.banner
, None, default_fg
, default_bg
)
662 self
.browser
.update_theme()
664 def disconnect_style_event(self
):
665 if self
.style_event_id
:
666 self
.banner
.disconnect(self
.style_event_id
)
667 self
.style_event_id
= 0
669 def connect_style_event(self
, set_fg
= False, set_bg
= False):
670 self
.disconnect_style_event()
671 self
.style_event_id
= self
.banner
.connect('style-set',
672 self
._on
_style
_set
_event
, set_fg
, set_bg
)
674 def _on_style_set_event(self
, widget
, style
, *opts
):
676 Set style of widget from style class *.Frame.Eventbox
677 opts[0] == True -> set fg color
678 opts[1] == True -> set bg color
680 self
.disconnect_style_event()
682 bg_color
= widget
.style
.bg
[gtk
.STATE_SELECTED
]
683 self
.banner_eventbox
.modify_bg(gtk
.STATE_NORMAL
, bg_color
)
685 fg_color
= widget
.style
.fg
[gtk
.STATE_SELECTED
]
686 self
.banner
.modify_fg(gtk
.STATE_NORMAL
, fg_color
)
687 self
.banner
.ensure_style()
688 self
.connect_style_event(opts
[0], opts
[1])
690 def destroy(self
, chain
= False):
692 Close the browser. This can optionally close its children and propagate
693 to the parent. This should happen on actions like register, or join to
694 kill off the entire browser chain
700 # self.browser._get_agent_address() would break when no browser.
701 addr
= get_agent_address(self
.jid
, self
.node
)
702 if addr
in gajim
.interface
.instances
[self
.account
]['disco']:
703 del gajim
.interface
.instances
[self
.account
]['disco'][addr
]
707 self
.browser
.cleanup()
709 self
.window
.destroy()
711 for child
in self
.children
[:]:
714 child
.destroy(chain
= chain
)
715 self
.children
.remove(child
)
717 if self
in self
.parent
.children
:
718 self
.parent
.children
.remove(self
)
719 if chain
and not self
.parent
.children
:
720 self
.parent
.destroy(chain
= chain
)
728 self
.reloading
= True
729 self
.travel(self
.jid
, self
.node
)
731 def travel(self
, jid
, node
):
733 Travel to an agent within the current services window
736 self
.browser
.cleanup()
738 # Update the window list
740 old_addr
= get_agent_address(self
.jid
, self
.node
)
741 if old_addr
in gajim
.interface
.instances
[self
.account
]['disco']:
742 del gajim
.interface
.instances
[self
.account
]['disco'][old_addr
]
743 addr
= get_agent_address(jid
, node
)
744 gajim
.interface
.instances
[self
.account
]['disco'][addr
] = self
745 # We need to store these, self.browser is not always available.
748 self
.cache
.get_info(jid
, node
, self
._travel
, force
=self
.reloading
)
750 def _travel(self
, jid
, node
, identities
, features
, data
):
752 Continuation of travel
754 if self
.dying
or jid
!= self
.jid
or node
!= self
.node
:
757 if not self
.address_comboboxentry
:
758 # We can't travel anywhere else.
760 dialogs
.ErrorDialog(_('The service could not be found'),
761 _('There is no service at the address you entered, or it is not responding. Check the address and try again.'))
763 klass
= self
.cache
.get_browser(identities
, features
)
765 dialogs
.ErrorDialog(_('The service is not browsable'),
766 _('This type of service does not contain any items to browse.'))
770 self
.browser
= klass(self
.account
, jid
, node
)
771 self
.browser
.prepare_window(self
)
772 self
.browser
.browse(force
=self
.reloading
)
773 self
.reloading
= False
775 def open(self
, jid
, node
):
777 Open an agent. By default, this happens in a new window
780 win
= gajim
.interface
.instances
[self
.account
]['disco']\
781 [get_agent_address(jid
, node
)]
787 win
= ServiceDiscoveryWindow(self
.account
, jid
, node
, parent
=self
)
789 # Disconnected, perhaps
791 self
.children
.append(win
)
793 def on_service_discovery_window_destroy(self
, widget
):
796 def on_close_button_clicked(self
, widget
):
799 def on_address_comboboxentry_changed(self
, widget
):
800 if self
.address_comboboxentry
.get_active() != -1:
801 # user selected one of the entries so do auto-visit
802 jid
= self
.address_comboboxentry
.child
.get_text().decode('utf-8')
804 jid
= helpers
.parse_jid(jid
)
805 except helpers
.InvalidFormat
, s
:
806 pritext
= _('Invalid Server Name')
807 dialogs
.ErrorDialog(pritext
, str(s
))
811 def on_go_button_clicked(self
, widget
):
812 jid
= self
.address_comboboxentry
.child
.get_text().decode('utf-8')
814 jid
= helpers
.parse_jid(jid
)
815 except helpers
.InvalidFormat
, s
:
816 pritext
= _('Invalid Server Name')
817 dialogs
.ErrorDialog(pritext
, str(s
))
819 if jid
== self
.jid
: # jid has not changed
821 if jid
in self
.latest_addresses
:
822 self
.latest_addresses
.remove(jid
)
823 self
.latest_addresses
.insert(0, jid
)
824 if len(self
.latest_addresses
) > 10:
825 self
.latest_addresses
= self
.latest_addresses
[0:10]
826 self
.address_comboboxentry
.get_model().clear()
827 for j
in self
.latest_addresses
:
828 self
.address_comboboxentry
.append_text(j
)
829 gajim
.config
.set('latest_disco_addresses',
830 ' '.join(self
.latest_addresses
))
831 gajim
.interface
.save_config()
834 def on_services_treeview_row_activated(self
, widget
, path
, col
= 0):
836 self
.browser
.default_action()
838 def on_services_treeview_selection_changed(self
, widget
):
840 self
.browser
.update_actions()
845 Class that deals with browsing agents and appearance of the browser window.
846 This class and subclasses should basically be treated as "part" of the
847 ServiceDiscoveryWindow class, but had to be separated because this part is
851 def __init__(self
, account
, jid
, node
):
852 self
.account
= account
855 self
._total
_items
= 0
856 self
.browse_button
= None
857 # This is for some timeout callbacks
860 def _get_agent_address(self
):
862 Get the agent's address for displaying in the GUI
864 return get_agent_address(self
.jid
, self
.node
)
866 def _set_initial_title(self
):
868 Set the initial window title based on agent address
870 self
.window
.window
.set_title(_('Browsing %(address)s using account '
871 '%(account)s') % {'address': self
._get
_agent
_address
(),
872 'account': self
.account
})
873 self
.window
._set
_window
_banner
_text
(self
._get
_agent
_address
())
875 def _create_treemodel(self
):
877 Create the treemodel for the services treeview. When subclassing, note
878 that the first two columns should ALWAYS be of type string and contain
879 the JID and node of the item respectively
881 # JID, node, name, address
882 self
.model
= gtk
.ListStore(str, str, str, str)
883 self
.model
.set_sort_column_id(3, gtk
.SORT_ASCENDING
)
884 self
.window
.services_treeview
.set_model(self
.model
)
886 col
= gtk
.TreeViewColumn(_('Name'))
887 renderer
= gtk
.CellRendererText()
888 col
.pack_start(renderer
)
889 col
.set_attributes(renderer
, text
= 2)
890 self
.window
.services_treeview
.insert_column(col
, -1)
891 col
.set_resizable(True)
893 col
= gtk
.TreeViewColumn(_('JID'))
894 renderer
= gtk
.CellRendererText()
895 col
.pack_start(renderer
)
896 col
.set_attributes(renderer
, text
= 3)
897 self
.window
.services_treeview
.insert_column(col
, -1)
898 col
.set_resizable(True)
899 self
.window
.services_treeview
.set_headers_visible(True)
901 def _clean_treemodel(self
):
903 for col
in self
.window
.services_treeview
.get_columns():
904 self
.window
.services_treeview
.remove_column(col
)
905 self
.window
.services_treeview
.set_headers_visible(False)
907 def _add_actions(self
):
909 Add the action buttons to the buttonbox for actions the browser can
912 self
.browse_button
= gtk
.Button()
913 image
= gtk
.image_new_from_stock(gtk
.STOCK_OPEN
, gtk
.ICON_SIZE_BUTTON
)
914 label
= gtk
.Label(_('_Browse'))
915 label
.set_use_underline(True)
917 hbox
.pack_start(image
, False, True, 6)
918 hbox
.pack_end(label
, True, True)
919 self
.browse_button
.add(hbox
)
920 self
.browse_button
.connect('clicked', self
.on_browse_button_clicked
)
921 self
.window
.action_buttonbox
.add(self
.browse_button
)
922 self
.browse_button
.show_all()
924 def _clean_actions(self
):
926 Remove the action buttons specific to this browser
928 if self
.browse_button
:
929 self
.browse_button
.destroy()
930 self
.browse_button
= None
932 def _set_title(self
, jid
, node
, identities
, features
, data
):
934 Set the window title based on agent info
936 # Set the banner and window title
937 if 'name' in identities
[0]:
938 name
= identities
[0]['name']
939 self
.window
._set
_window
_banner
_text
(self
._get
_agent
_address
(), name
)
941 # Add an icon to the banner.
942 pix
= self
.cache
.get_icon(identities
)
943 self
.window
.banner_icon
.set_from_pixbuf(pix
)
944 self
.window
.banner_icon
.show()
946 def _clean_title(self
):
947 # Everything done here is done in window._initial_state
948 # This is for subclasses.
951 def prepare_window(self
, window
):
953 Prepare the service discovery window. Called when a browser is hooked up
954 with a ServiceDiscoveryWindow instance
957 self
.cache
= window
.cache
959 self
._set
_initial
_title
()
960 self
._create
_treemodel
()
963 # This is a hack. The buttonbox apparently doesn't care about pack_start
964 # or pack_end, so we repack the close button here to make sure it's last
965 close_button
= self
.window
.xml
.get_object('close_button')
966 self
.window
.action_buttonbox
.remove(close_button
)
967 self
.window
.action_buttonbox
.pack_end(close_button
)
968 close_button
.show_all()
970 self
.update_actions()
973 self
.cache
.get_info(self
.jid
, self
.node
, self
._set
_title
)
977 Cleanup when the window intends to switch browsers
981 self
._clean
_actions
()
982 self
._clean
_treemodel
()
985 self
.window
._initial
_state
()
987 def update_theme(self
):
989 Called when the default theme is changed
993 def on_browse_button_clicked(self
, widget
= None):
995 When we want to browse an agent: open a new services window with a
996 browser for the agent type
998 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1001 jid
= model
[iter_
][0].decode('utf-8')
1003 node
= model
[iter_
][1].decode('utf-8')
1004 self
.window
.open(jid
, node
)
1006 def update_actions(self
):
1008 When we select a row: activate action buttons based on the agent's info
1010 if self
.browse_button
:
1011 self
.browse_button
.set_sensitive(False)
1012 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1015 jid
= model
[iter_
][0].decode('utf-8')
1016 node
= model
[iter_
][1].decode('utf-8')
1018 self
.cache
.get_info(jid
, node
, self
._update
_actions
, nofetch
= True)
1020 def _update_actions(self
, jid
, node
, identities
, features
, data
):
1022 Continuation of update_actions
1024 if not identities
or not self
.browse_button
:
1026 klass
= self
.cache
.get_browser(identities
, features
)
1028 self
.browse_button
.set_sensitive(True)
1030 def default_action(self
):
1032 When we double-click a row: perform the default action on the selected
1035 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1038 jid
= model
[iter_
][0].decode('utf-8')
1039 node
= model
[iter_
][1].decode('utf-8')
1041 self
.cache
.get_info(jid
, node
, self
._default
_action
, nofetch
= True)
1043 def _default_action(self
, jid
, node
, identities
, features
, data
):
1045 Continuation of default_action
1047 if self
.cache
.get_browser(identities
, features
):
1049 self
.on_browse_button_clicked()
1053 def browse(self
, force
=False):
1055 Fill the treeview with agents, fetching the info if necessary
1058 self
._total
_items
= self
._progress
= 0
1059 self
.window
.progressbar
.show()
1060 self
._pulse
_timeout
= gobject
.timeout_add(250, self
._pulse
_timeout
_cb
)
1061 self
.cache
.get_items(self
.jid
, self
.node
, self
._agent
_items
,
1062 force
=force
, args
=(force
,))
1064 def _pulse_timeout_cb(self
, *args
):
1066 Simple callback to keep the progressbar pulsing
1070 self
.window
.progressbar
.pulse()
1073 def _find_item(self
, jid
, node
):
1075 Check if an item is already in the treeview. Return an iter to it if so,
1078 iter_
= self
.model
.get_iter_root()
1080 cjid
= self
.model
.get_value(iter_
, 0).decode('utf-8')
1081 cnode
= self
.model
.get_value(iter_
, 1).decode('utf-8')
1082 if jid
== cjid
and node
== cnode
:
1084 iter_
= self
.model
.iter_next(iter_
)
1089 def _agent_items(self
, jid
, node
, items
, force
):
1091 Callback for when we receive a list of agent items
1094 self
._total
_items
= 0
1095 gobject
.source_remove(self
._pulse
_timeout
)
1096 self
.window
.progressbar
.hide()
1097 # The server returned an error
1099 if not self
.window
.address_comboboxentry
:
1100 # We can't travel anywhere else.
1101 self
.window
.destroy()
1102 dialogs
.ErrorDialog(_('The service is not browsable'),
1103 _('This service does not contain any items to browse.'))
1105 # We got a list of items
1106 self
.window
.services_treeview
.set_model(None)
1109 node_
= item
.get('node', '')
1110 # If such an item is already here: don't add it
1111 if self
._find
_item
(jid_
, node_
):
1113 self
._total
_items
+= 1
1114 self
._add
_item
(jid_
, node_
, node
, item
, force
)
1115 self
.window
.services_treeview
.set_model(self
.model
)
1117 def _agent_info(self
, jid
, node
, identities
, features
, data
):
1119 Callback for when we receive info about an agent's item
1121 iter_
= self
._find
_item
(jid
, node
)
1123 # Not in the treeview, stop
1126 # The server returned an error
1127 self
._update
_error
(iter_
, jid
, node
)
1130 self
._update
_info
(iter_
, jid
, node
, identities
, features
, data
)
1131 self
.update_actions()
1133 def _add_item(self
, jid
, node
, parent_node
, item
, force
):
1135 Called when an item should be added to the model. The result of a
1138 self
.model
.append((jid
, node
, item
.get('name', ''),
1139 get_agent_address(jid
, node
)))
1140 self
.cache
.get_info(jid
, node
, self
._agent
_info
, force
= force
)
1142 def _update_item(self
, iter_
, jid
, node
, item
):
1144 Called when an item should be updated in the model. The result of a
1148 self
.model
[iter_
][2] = item
['name']
1150 def _update_info(self
, iter_
, jid
, node
, identities
, features
, data
):
1152 Called when an item should be updated in the model with further info.
1153 The result of a disco#info query
1155 name
= identities
[0].get('name', '')
1157 self
.model
[iter_
][2] = name
1159 def _update_error(self
, iter_
, jid
, node
):
1160 '''Called when a disco#info query failed for an item.'''
1164 class ToplevelAgentBrowser(AgentBrowser
):
1166 This browser is used at the top level of a jabber server to browse services
1167 such as transports, conference servers, etc
1170 def __init__(self
, *args
):
1171 AgentBrowser
.__init
__(self
, *args
)
1172 self
._progressbar
_sourceid
= None
1173 self
._renderer
= None
1175 self
.tooltip
= tooltips
.ServiceDiscoveryTooltip()
1176 self
.register_button
= None
1177 self
.join_button
= None
1178 self
.execute_button
= None
1179 self
.search_button
= None
1180 # Keep track of our treeview signals
1181 self
._view
_signals
= []
1182 self
._scroll
_signal
= None
1184 def _pixbuf_renderer_data_func(self
, col
, cell
, model
, iter_
):
1186 Callback for setting the pixbuf renderer's properties
1188 jid
= model
.get_value(iter_
, 0)
1190 pix
= model
.get_value(iter_
, 2)
1191 cell
.set_property('visible', True)
1192 cell
.set_property('pixbuf', pix
)
1194 cell
.set_property('visible', False)
1196 def _text_renderer_data_func(self
, col
, cell
, model
, iter_
):
1198 Callback for setting the text renderer's properties
1200 jid
= model
.get_value(iter_
, 0)
1201 markup
= model
.get_value(iter_
, 3)
1202 state
= model
.get_value(iter_
, 4)
1203 cell
.set_property('markup', markup
)
1205 cell
.set_property('cell_background_set', False)
1207 # 1 = fetching, 2 = error
1208 cell
.set_property('foreground_set', True)
1211 cell
.set_property('foreground_set', False)
1213 theme
= gajim
.config
.get('roster_theme')
1214 bgcolor
= gajim
.config
.get_per('themes', theme
, 'groupbgcolor')
1216 cell
.set_property('cell_background_set', True)
1217 cell
.set_property('foreground_set', False)
1219 def _treemodel_sort_func(self
, model
, iter1
, iter2
):
1221 Sort function for our treemode
1224 statecmp
= cmp(model
.get_value(iter1
, 4), model
.get_value(iter2
, 4))
1226 # These can be None, apparently
1227 descr1
= model
.get_value(iter1
, 3)
1229 descr1
= descr1
.decode('utf-8')
1230 descr2
= model
.get_value(iter2
, 3)
1232 descr2
= descr2
.decode('utf-8')
1234 return cmp(descr1
, descr2
)
1237 def _show_tooltip(self
, state
):
1238 view
= self
.window
.services_treeview
1239 pointer
= view
.get_pointer()
1240 props
= view
.get_path_at_pos(pointer
[0], pointer
[1])
1241 # check if the current pointer is at the same path
1242 # as it was before setting the timeout
1243 if props
and self
.tooltip
.id == props
[0]:
1244 # bounding rectangle of coordinates for the cell within the treeview
1245 rect
= view
.get_cell_area(props
[0], props
[1])
1246 # position of the treeview on the screen
1247 position
= view
.window
.get_origin()
1248 self
.tooltip
.show_tooltip(state
, rect
.height
, position
[1] + rect
.y
)
1250 self
.tooltip
.hide_tooltip()
1252 # These are all callbacks to make tooltips work
1253 def on_treeview_leave_notify_event(self
, widget
, event
):
1254 props
= widget
.get_path_at_pos(int(event
.x
), int(event
.y
))
1255 if self
.tooltip
.timeout
> 0:
1256 if not props
or self
.tooltip
.id == props
[0]:
1257 self
.tooltip
.hide_tooltip()
1259 def on_treeview_motion_notify_event(self
, widget
, event
):
1260 props
= widget
.get_path_at_pos(int(event
.x
), int(event
.y
))
1261 if self
.tooltip
.timeout
> 0:
1262 if not props
or self
.tooltip
.id != props
[0]:
1263 self
.tooltip
.hide_tooltip()
1268 iter_
= self
.model
.get_iter(row
)
1270 self
.tooltip
.hide_tooltip()
1272 jid
= self
.model
[iter_
][0]
1273 state
= self
.model
[iter_
][4]
1274 # Not a category, and we have something to say about state
1275 if jid
and state
> 0 and \
1276 (self
.tooltip
.timeout
== 0 or self
.tooltip
.id != props
[0]):
1277 self
.tooltip
.id = row
1278 self
.tooltip
.timeout
= gobject
.timeout_add(500,
1279 self
._show
_tooltip
, state
)
1281 def on_treeview_event_hide_tooltip(self
, widget
, event
):
1283 This happens on scroll_event, key_press_event and button_press_event
1285 self
.tooltip
.hide_tooltip()
1287 def _create_treemodel(self
):
1288 # JID, node, icon, description, state
1289 # State means 2 when error, 1 when fetching, 0 when succes.
1290 view
= self
.window
.services_treeview
1291 self
.model
= gtk
.TreeStore(str, str, gtk
.gdk
.Pixbuf
, str, int)
1292 self
.model
.set_sort_func(4, self
._treemodel
_sort
_func
)
1293 self
.model
.set_sort_column_id(4, gtk
.SORT_ASCENDING
)
1294 view
.set_model(self
.model
)
1296 col
= gtk
.TreeViewColumn()
1298 renderer
= gtk
.CellRendererPixbuf()
1299 renderer
.set_property('xpad', 6)
1300 col
.pack_start(renderer
, expand
=False)
1301 col
.set_cell_data_func(renderer
, self
._pixbuf
_renderer
_data
_func
)
1303 renderer
= gtk
.CellRendererText()
1304 col
.pack_start(renderer
, expand
=True)
1305 col
.set_cell_data_func(renderer
, self
._text
_renderer
_data
_func
)
1306 renderer
.set_property('foreground', 'dark gray')
1307 # Save this so we can go along with theme changes
1308 self
._renderer
= renderer
1311 view
.insert_column(col
, -1)
1312 col
.set_resizable(True)
1315 scrollwin
= self
.window
.services_scrollwin
1316 self
._view
_signals
.append(view
.connect('leave-notify-event',
1317 self
.on_treeview_leave_notify_event
))
1318 self
._view
_signals
.append(view
.connect('motion-notify-event',
1319 self
.on_treeview_motion_notify_event
))
1320 self
._view
_signals
.append(view
.connect('key-press-event',
1321 self
.on_treeview_event_hide_tooltip
))
1322 self
._view
_signals
.append(view
.connect('button-press-event',
1323 self
.on_treeview_event_hide_tooltip
))
1324 self
._scroll
_signal
= scrollwin
.connect('scroll-event',
1325 self
.on_treeview_event_hide_tooltip
)
1327 def _clean_treemodel(self
):
1328 # Disconnect signals
1329 view
= self
.window
.services_treeview
1330 for sig
in self
._view
_signals
:
1331 view
.disconnect(sig
)
1332 self
._view
_signals
= []
1333 if self
._scroll
_signal
:
1334 scrollwin
= self
.window
.services_scrollwin
1335 scrollwin
.disconnect(self
._scroll
_signal
)
1336 self
._scroll
_signal
= None
1337 AgentBrowser
._clean
_treemodel
(self
)
1339 def _add_actions(self
):
1340 AgentBrowser
._add
_actions
(self
)
1341 self
.execute_button
= gtk
.Button()
1342 image
= gtk
.image_new_from_stock(gtk
.STOCK_EXECUTE
, gtk
.ICON_SIZE_BUTTON
)
1343 label
= gtk
.Label(_('_Execute Command'))
1344 label
.set_use_underline(True)
1346 hbox
.pack_start(image
, False, True, 6)
1347 hbox
.pack_end(label
, True, True)
1348 self
.execute_button
.add(hbox
)
1349 self
.execute_button
.connect('clicked', self
.on_execute_button_clicked
)
1350 self
.window
.action_buttonbox
.add(self
.execute_button
)
1351 self
.execute_button
.show_all()
1353 self
.register_button
= gtk
.Button(label
=_("Re_gister"),
1355 self
.register_button
.connect('clicked', self
.on_register_button_clicked
)
1356 self
.window
.action_buttonbox
.add(self
.register_button
)
1357 self
.register_button
.show_all()
1359 self
.join_button
= gtk
.Button()
1360 image
= gtk
.image_new_from_stock(gtk
.STOCK_CONNECT
, gtk
.ICON_SIZE_BUTTON
)
1361 label
= gtk
.Label(_('_Join'))
1362 label
.set_use_underline(True)
1364 hbox
.pack_start(image
, False, True, 6)
1365 hbox
.pack_end(label
, True, True)
1366 self
.join_button
.add(hbox
)
1367 self
.join_button
.connect('clicked', self
.on_join_button_clicked
)
1368 self
.window
.action_buttonbox
.add(self
.join_button
)
1369 self
.join_button
.show_all()
1371 self
.search_button
= gtk
.Button()
1372 image
= gtk
.image_new_from_stock(gtk
.STOCK_FIND
, gtk
.ICON_SIZE_BUTTON
)
1373 label
= gtk
.Label(_('_Search'))
1374 label
.set_use_underline(True)
1376 hbox
.pack_start(image
, False, True, 6)
1377 hbox
.pack_end(label
, True, True)
1378 self
.search_button
.add(hbox
)
1379 self
.search_button
.connect('clicked', self
.on_search_button_clicked
)
1380 self
.window
.action_buttonbox
.add(self
.search_button
)
1381 self
.search_button
.show_all()
1383 def _clean_actions(self
):
1384 if self
.execute_button
:
1385 self
.execute_button
.destroy()
1386 self
.execute_button
= None
1387 if self
.register_button
:
1388 self
.register_button
.destroy()
1389 self
.register_button
= None
1390 if self
.join_button
:
1391 self
.join_button
.destroy()
1392 self
.join_button
= None
1393 if self
.search_button
:
1394 self
.search_button
.destroy()
1395 self
.search_button
= None
1396 AgentBrowser
._clean
_actions
(self
)
1398 def on_search_button_clicked(self
, widget
= None):
1400 When we want to search something: open search window
1402 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1405 service
= model
[iter_
][0].decode('utf-8')
1406 if service
in gajim
.interface
.instances
[self
.account
]['search']:
1407 gajim
.interface
.instances
[self
.account
]['search'][service
].window
.\
1410 gajim
.interface
.instances
[self
.account
]['search'][service
] = \
1411 search_window
.SearchWindow(self
.account
, service
)
1414 self
.tooltip
.hide_tooltip()
1415 AgentBrowser
.cleanup(self
)
1417 def update_theme(self
):
1418 theme
= gajim
.config
.get('roster_theme')
1419 bgcolor
= gajim
.config
.get_per('themes', theme
, 'groupbgcolor')
1421 self
._renderer
.set_property('cell-background', bgcolor
)
1422 self
.window
.services_treeview
.queue_draw()
1424 def on_execute_button_clicked(self
, widget
=None):
1426 When we want to execute a command: open adhoc command window
1428 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1431 service
= model
[iter_
][0].decode('utf-8')
1432 node
= model
[iter_
][1].decode('utf-8')
1433 adhoc_commands
.CommandWindow(self
.account
, service
, commandnode
=node
)
1435 def on_register_button_clicked(self
, widget
= None):
1437 When we want to register an agent: request information about registering
1438 with the agent and close the window
1440 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1443 jid
= model
[iter_
][0].decode('utf-8')
1445 gajim
.connections
[self
.account
].request_register_agent_info(jid
)
1446 self
.window
.destroy(chain
= True)
1448 def on_join_button_clicked(self
, widget
):
1450 When we want to join an IRC room or create a new MUC room: Opens the
1451 join_groupchat_window
1453 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1456 service
= model
[iter_
][0].decode('utf-8')
1457 if 'join_gc' not in gajim
.interface
.instances
[self
.account
]:
1459 dialogs
.JoinGroupchatWindow(self
.account
, service
)
1460 except GajimGeneralException
:
1463 gajim
.interface
.instances
[self
.account
]['join_gc'].window
.present()
1465 def update_actions(self
):
1466 if self
.execute_button
:
1467 self
.execute_button
.set_sensitive(False)
1468 if self
.register_button
:
1469 self
.register_button
.set_sensitive(False)
1470 if self
.browse_button
:
1471 self
.browse_button
.set_sensitive(False)
1472 if self
.join_button
:
1473 self
.join_button
.set_sensitive(False)
1474 if self
.search_button
:
1475 self
.search_button
.set_sensitive(False)
1476 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1479 if not model
[iter_
][0]:
1480 # We're on a category row
1482 if model
[iter_
][4] != 0:
1483 # We don't have the info (yet)
1484 # It's either unknown or a transport, register button should be active
1485 if self
.register_button
:
1486 self
.register_button
.set_sensitive(True)
1487 # Guess what kind of service we're dealing with
1488 if self
.browse_button
:
1489 jid
= model
[iter_
][0].decode('utf-8')
1490 type_
= gajim
.get_transport_name_from_jid(jid
,
1491 use_config_setting
= False)
1493 identity
= {'category': '_jid', 'type': type_
}
1494 klass
= self
.cache
.get_browser([identity
])
1496 self
.browse_button
.set_sensitive(True)
1499 self
.browse_button
.set_sensitive(True)
1501 # Normal case, we have info
1502 AgentBrowser
.update_actions(self
)
1504 def _update_actions(self
, jid
, node
, identities
, features
, data
):
1505 AgentBrowser
._update
_actions
(self
, jid
, node
, identities
, features
, data
)
1506 if self
.execute_button
and xmpp
.NS_COMMANDS
in features
:
1507 self
.execute_button
.set_sensitive(True)
1508 if self
.search_button
and xmpp
.NS_SEARCH
in features
:
1509 self
.search_button
.set_sensitive(True)
1510 if self
.register_button
and xmpp
.NS_REGISTER
in features
:
1511 # We can register this agent
1512 registered_transports
= []
1513 jid_list
= gajim
.contacts
.get_jid_list(self
.account
)
1514 for jid
in jid_list
:
1515 contact
= gajim
.contacts
.get_first_contact_from_jid(
1517 if _('Transports') in contact
.groups
:
1518 registered_transports
.append(jid
)
1519 if jid
in registered_transports
:
1520 self
.register_button
.set_label(_('_Edit'))
1522 self
.register_button
.set_label(_('Re_gister'))
1523 self
.register_button
.set_sensitive(True)
1524 if self
.join_button
and xmpp
.NS_MUC
in features
:
1525 self
.join_button
.set_sensitive(True)
1527 def _default_action(self
, jid
, node
, identities
, features
, data
):
1528 if AgentBrowser
._default
_action
(self
, jid
, node
, identities
, features
, data
):
1530 if xmpp
.NS_REGISTER
in features
:
1531 # Register if we can't browse
1532 self
.on_register_button_clicked()
1536 def browse(self
, force
=False):
1538 AgentBrowser
.browse(self
, force
= force
)
1540 def _expand_all(self
):
1542 Expand all items in the treeview
1544 # GTK apparently screws up here occasionally. :/
1545 #def expand_all(*args):
1546 # self.window.services_treeview.expand_all()
1547 # self.expanding = False
1549 #self.expanding = True
1550 #gobject.idle_add(expand_all)
1551 self
.window
.services_treeview
.expand_all()
1553 def _update_progressbar(self
):
1555 Update the progressbar
1557 # Refresh this every update
1558 if self
._progressbar
_sourceid
:
1559 gobject
.source_remove(self
._progressbar
_sourceid
)
1562 if self
._total
_items
:
1563 self
.window
.progressbar
.set_text(_("Scanning %(current)d / %(total)d.."
1564 ) % {'current': self
._progress
, 'total': self
._total
_items
})
1565 fraction
= float(self
._progress
) / float(self
._total
_items
)
1566 if self
._progress
>= self
._total
_items
:
1567 # We show the progressbar for just a bit before hiding it.
1568 id_
= gobject
.timeout_add_seconds(2, self
._hide
_progressbar
_cb
)
1569 self
._progressbar
_sourceid
= id_
1571 self
.window
.progressbar
.show()
1572 # Hide the progressbar if we're timing out anyways. (20 secs)
1573 id_
= gobject
.timeout_add_seconds(20, self
._hide
_progressbar
_cb
)
1574 self
._progressbar
_sourceid
= id_
1575 self
.window
.progressbar
.set_fraction(fraction
)
1577 def _hide_progressbar_cb(self
, *args
):
1579 Simple callback to hide the progressbar a second after we finish
1582 self
.window
.progressbar
.hide()
1585 def _friendly_category(self
, category
, type_
=None):
1587 Get the friendly category name and priority
1591 # Try type-specific override
1593 cat
, prio
= _cat_to_descr
[(category
, type_
)]
1598 cat
, prio
= _cat_to_descr
[category
]
1600 cat
, prio
= _cat_to_descr
['other']
1603 def _create_category(self
, cat
, type_
=None):
1605 Creates a category row
1607 cat
, prio
= self
._friendly
_category
(cat
, type_
)
1608 return self
.model
.append(None, ('', '', None, cat
, prio
))
1610 def _find_category(self
, cat
, type_
=None):
1612 Looks up a category row and returns the iterator to it, or None
1614 cat
= self
._friendly
_category
(cat
, type_
)[0]
1615 iter_
= self
.model
.get_iter_root()
1617 if self
.model
.get_value(iter_
, 3).decode('utf-8') == cat
:
1619 iter_
= self
.model
.iter_next(iter_
)
1624 def _find_item(self
, jid
, node
):
1626 cat_iter
= self
.model
.get_iter_root()
1627 while cat_iter
and not iter_
:
1628 iter_
= self
.model
.iter_children(cat_iter
)
1630 cjid
= self
.model
.get_value(iter_
, 0).decode('utf-8')
1631 cnode
= self
.model
.get_value(iter_
, 1).decode('utf-8')
1632 if jid
== cjid
and node
== cnode
:
1634 iter_
= self
.model
.iter_next(iter_
)
1635 cat_iter
= self
.model
.iter_next(cat_iter
)
1640 def _add_item(self
, jid
, node
, parent_node
, item
, force
):
1642 addr
= get_agent_address(jid
, node
)
1644 descr
= "<b>%s</b>\n%s" % (item
['name'], addr
)
1646 descr
= "<b>%s</b>" % addr
1647 # Guess which kind of service this is
1649 type_
= gajim
.get_transport_name_from_jid(jid
,
1650 use_config_setting
= False)
1652 identity
= {'category': '_jid', 'type': type_
}
1653 identities
.append(identity
)
1654 cat_args
= ('_jid', type_
)
1656 # Put it in the 'other' category for now
1657 cat_args
= ('other',)
1658 # Set the pixmap for the row
1659 pix
= self
.cache
.get_icon(identities
)
1660 # Put it in the right category
1661 cat
= self
._find
_category
(*cat_args
)
1663 cat
= self
._create
_category
(*cat_args
)
1664 self
.model
.append(cat
, (jid
, node
, pix
, descr
, 1))
1665 gobject
.idle_add(self
._expand
_all
)
1666 # Grab info on the service
1667 self
.cache
.get_info(jid
, node
, self
._agent
_info
, force
=force
)
1668 self
._update
_progressbar
()
1670 def _update_item(self
, iter_
, jid
, node
, item
):
1671 addr
= get_agent_address(jid
, node
)
1673 descr
= "<b>%s</b>\n%s" % (item
['name'], addr
)
1675 descr
= "<b>%s</b>" % addr
1676 self
.model
[iter_
][3] = descr
1678 def _update_info(self
, iter_
, jid
, node
, identities
, features
, data
):
1679 addr
= get_agent_address(jid
, node
)
1680 name
= identities
[0].get('name', '')
1682 descr
= "<b>%s</b>\n%s" % (name
, addr
)
1684 descr
= "<b>%s</b>" % addr
1688 self
._update
_progressbar
()
1690 # Search for an icon and category we can display
1691 pix
= self
.cache
.get_icon(identities
)
1692 cat
, type_
= None, None
1693 for identity
in identities
:
1695 cat
, type_
= identity
['category'], identity
['type']
1700 # Check if we have to move categories
1701 old_cat_iter
= self
.model
.iter_parent(iter_
)
1702 old_cat
= self
.model
.get_value(old_cat_iter
, 3).decode('utf-8')
1703 if self
.model
.get_value(old_cat_iter
, 3) == cat
:
1704 # Already in the right category, just update
1705 self
.model
[iter_
][2] = pix
1706 self
.model
[iter_
][3] = descr
1707 self
.model
[iter_
][4] = 0
1709 # Not in the right category, move it.
1710 self
.model
.remove(iter_
)
1712 # Check if the old category is empty
1713 if not self
.model
.iter_is_valid(old_cat_iter
):
1714 old_cat_iter
= self
._find
_category
(old_cat
)
1715 if not self
.model
.iter_children(old_cat_iter
):
1716 self
.model
.remove(old_cat_iter
)
1718 cat_iter
= self
._find
_category
(cat
, type_
)
1720 cat_iter
= self
._create
_category
(cat
, type_
)
1721 self
.model
.append(cat_iter
, (jid
, node
, pix
, descr
, 0))
1724 def _update_error(self
, iter_
, jid
, node
):
1725 self
.model
[iter_
][4] = 2
1727 self
._update
_progressbar
()
1730 class MucBrowser(AgentBrowser
):
1731 def __init__(self
, *args
, **kwargs
):
1732 AgentBrowser
.__init
__(self
, *args
, **kwargs
)
1733 self
.join_button
= None
1734 self
.bookmark_button
= None
1736 def _create_treemodel(self
):
1737 # JID, node, name, users_int, users_str, description, fetched
1738 # This is rather long, I'd rather not use a data_func here though.
1739 # Users is a string, because want to be able to leave it empty.
1740 self
.model
= gtk
.ListStore(str, str, str, int, str, str, bool)
1741 self
.model
.set_sort_column_id(2, gtk
.SORT_ASCENDING
)
1742 self
.window
.services_treeview
.set_model(self
.model
)
1744 col
= gtk
.TreeViewColumn(_('Name'))
1745 col
.set_sizing(gtk
.TREE_VIEW_COLUMN_FIXED
)
1746 col
.set_fixed_width(100)
1747 renderer
= gtk
.CellRendererText()
1748 col
.pack_start(renderer
)
1749 col
.set_attributes(renderer
, text
= 2)
1750 col
.set_sort_column_id(2)
1751 self
.window
.services_treeview
.insert_column(col
, -1)
1752 col
.set_resizable(True)
1754 col
= gtk
.TreeViewColumn(_('Users'))
1755 renderer
= gtk
.CellRendererText()
1756 col
.pack_start(renderer
)
1757 col
.set_attributes(renderer
, text
= 4)
1758 col
.set_sort_column_id(3)
1759 self
.window
.services_treeview
.insert_column(col
, -1)
1760 col
.set_resizable(True)
1761 # Description column
1762 col
= gtk
.TreeViewColumn(_('Description'))
1763 renderer
= gtk
.CellRendererText()
1764 col
.pack_start(renderer
)
1765 col
.set_attributes(renderer
, text
= 5)
1766 col
.set_sort_column_id(4)
1767 self
.window
.services_treeview
.insert_column(col
, -1)
1768 col
.set_resizable(True)
1770 col
= gtk
.TreeViewColumn(_('Id'))
1771 renderer
= gtk
.CellRendererText()
1772 col
.pack_start(renderer
)
1773 col
.set_attributes(renderer
, text
= 0)
1774 col
.set_sort_column_id(0)
1775 self
.window
.services_treeview
.insert_column(col
, -1)
1776 col
.set_resizable(True)
1777 self
.window
.services_treeview
.set_headers_visible(True)
1778 self
.window
.services_treeview
.set_headers_clickable(True)
1779 # Source id for idle callback used to start disco#info queries.
1780 self
._fetch
_source
= None
1781 # Query failure counter
1783 # Connect to scrollwindow scrolling
1784 self
.vadj
= self
.window
.services_scrollwin
.get_property('vadjustment')
1785 self
.vadj_cbid
= self
.vadj
.connect('value-changed', self
.on_scroll
)
1786 # And to size changes
1787 self
.size_cbid
= self
.window
.services_scrollwin
.connect(
1788 'size-allocate', self
.on_scroll
)
1790 def _clean_treemodel(self
):
1792 self
.window
.services_scrollwin
.disconnect(self
.size_cbid
)
1793 self
.size_cbid
= None
1795 self
.vadj
.disconnect(self
.vadj_cbid
)
1796 self
.vadj_cbid
= None
1797 AgentBrowser
._clean
_treemodel
(self
)
1799 def _add_actions(self
):
1800 self
.bookmark_button
= gtk
.Button(label
=_('_Bookmark'), use_underline
=True)
1801 self
.bookmark_button
.connect('clicked', self
.on_bookmark_button_clicked
)
1802 self
.window
.action_buttonbox
.add(self
.bookmark_button
)
1803 self
.bookmark_button
.show_all()
1804 self
.join_button
= gtk
.Button(label
=_('_Join'), use_underline
=True)
1805 self
.join_button
.connect('clicked', self
.on_join_button_clicked
)
1806 self
.window
.action_buttonbox
.add(self
.join_button
)
1807 self
.join_button
.show_all()
1809 def _clean_actions(self
):
1810 if self
.bookmark_button
:
1811 self
.bookmark_button
.destroy()
1812 self
.bookmark_button
= None
1813 if self
.join_button
:
1814 self
.join_button
.destroy()
1815 self
.join_button
= None
1817 def on_bookmark_button_clicked(self
, *args
):
1818 model
, iter = self
.window
.services_treeview
.get_selection().get_selected()
1821 name
= gajim
.config
.get_per('accounts', self
.account
, 'name')
1822 room_jid
= model
[iter][0].decode('utf-8')
1824 'name': room_jid
.split('@')[0],
1832 for bookmark
in gajim
.connections
[self
.account
].bookmarks
:
1833 if bookmark
['jid'] == bm
['jid']:
1834 dialogs
.ErrorDialog(
1835 _('Bookmark already set'),
1836 _('Group Chat "%s" is already in your bookmarks.') % bm
['jid'])
1839 gajim
.connections
[self
.account
].bookmarks
.append(bm
)
1840 gajim
.connections
[self
.account
].store_bookmarks()
1842 gajim
.interface
.roster
.set_actions_menu_needs_rebuild()
1844 dialogs
.InformationDialog(
1845 _('Bookmark has been added successfully'),
1846 _('You can manage your bookmarks via Actions menu in your roster.'))
1848 def on_join_button_clicked(self
, *args
):
1850 When we want to join a conference: ask specific informations about the
1851 selected agent and close the window
1853 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1856 service
= model
[iter_
][0].decode('utf-8')
1857 if 'join_gc' not in gajim
.interface
.instances
[self
.account
]:
1859 dialogs
.JoinGroupchatWindow(self
.account
, service
)
1860 except GajimGeneralException
:
1863 gajim
.interface
.instances
[self
.account
]['join_gc']._set
_room
_jid
(
1865 gajim
.interface
.instances
[self
.account
]['join_gc'].window
.present()
1867 def update_actions(self
):
1868 sens
= self
.window
.services_treeview
.get_selection().count_selected_rows()
1869 if self
.bookmark_button
:
1870 self
.bookmark_button
.set_sensitive(sens
> 0)
1871 if self
.join_button
:
1872 self
.join_button
.set_sensitive(sens
> 0)
1874 def default_action(self
):
1875 self
.on_join_button_clicked()
1877 def _start_info_query(self
):
1879 Idle callback to start checking for visible rows
1881 self
._fetch
_source
= None
1882 self
._query
_visible
()
1885 def on_scroll(self
, *args
):
1887 Scrollwindow callback to trigger new queries on scolling
1889 # This apparently happens when inactive sometimes
1890 self
._query
_visible
()
1892 def _query_visible(self
):
1894 Query the next visible row for info
1896 if self
._fetch
_source
:
1897 # We're already fetching
1899 view
= self
.window
.services_treeview
1900 if not view
.flags() & gtk
.REALIZED
:
1901 # Prevent a silly warning, try again in a bit.
1902 self
._fetch
_source
= gobject
.timeout_add(100, self
._start
_info
_query
)
1904 # We have to do this in a pygtk <2.8 compatible way :/
1905 #start, end = self.window.services_treeview.get_visible_range()
1906 rect
= view
.get_visible_rect()
1910 sx
, sy
= view
.tree_to_widget_coords(rect
.x
, rect
.y
)
1911 spath
= view
.get_path_at_pos(sx
, sy
)[0]
1912 iter_
= self
.model
.get_iter(spath
)
1914 self
._fetch
_source
= None
1917 # Iter compare is broke, use the path instead
1919 ex
, ey
= view
.tree_to_widget_coords(rect
.x
+ rect
.height
,
1920 rect
.y
+ rect
.height
)
1921 end
= view
.get_path_at_pos(ex
, ey
)[0]
1922 # end is the last visible, we want to query that aswell
1925 # We're at the end of the model, we can leave end=None though.
1927 while iter_
and self
.model
.get_path(iter_
) != end
:
1928 if not self
.model
.get_value(iter_
, 6):
1929 jid
= self
.model
.get_value(iter_
, 0).decode('utf-8')
1930 node
= self
.model
.get_value(iter_
, 1).decode('utf-8')
1931 self
.cache
.get_info(jid
, node
, self
._agent
_info
)
1932 self
._fetch
_source
= True
1934 iter_
= self
.model
.iter_next(iter_
)
1935 self
._fetch
_source
= None
1937 def _channel_altinfo(self
, jid
, node
, items
, name
= None):
1939 Callback for the alternate disco#items query. We try to atleast get the
1940 amount of users in the room if the service does not support MUC dataforms
1943 # The server returned an error
1945 if self
._broken
>= 3:
1946 # Disable queries completely after 3 failures
1948 self
.window
.services_scrollwin
.disconnect(self
.size_cbid
)
1949 self
.size_cbid
= None
1951 self
.vadj
.disconnect(self
.vadj_cbid
)
1952 self
.vadj_cbid
= None
1953 self
._fetch
_source
= None
1956 iter_
= self
._find
_item
(jid
, node
)
1959 self
.model
[iter_
][2] = name
1960 self
.model
[iter_
][3] = len(items
) # The number of users
1961 self
.model
[iter_
][4] = str(len(items
)) # The number of users
1962 self
.model
[iter_
][6] = True
1963 self
._fetch
_source
= None
1964 self
._query
_visible
()
1966 def _add_item(self
, jid
, node
, parent_node
, item
, force
):
1967 self
.model
.append((jid
, node
, item
.get('name', ''), -1, '', '', False))
1968 if not self
._fetch
_source
:
1969 self
._fetch
_source
= gobject
.idle_add(self
._start
_info
_query
)
1971 def _update_info(self
, iter_
, jid
, node
, identities
, features
, data
):
1972 name
= identities
[0].get('name', '')
1974 typefield
= form
.getField('FORM_TYPE')
1975 if typefield
and typefield
.getValue() == \
1976 'http://jabber.org/protocol/muc#roominfo':
1977 # Fill model row from the form's fields
1978 users
= form
.getField('muc#roominfo_occupants')
1979 descr
= form
.getField('muc#roominfo_description')
1981 self
.model
[iter_
][3] = int(users
.getValue())
1982 self
.model
[iter_
][4] = users
.getValue()
1984 self
.model
[iter_
][5] = descr
.getValue()
1985 # Only set these when we find a form with additional info
1986 # Some servers don't support forms and put extra info in
1987 # the name attribute, so we preserve it in that case.
1988 self
.model
[iter_
][2] = name
1989 self
.model
[iter_
][6] = True
1992 # We didn't find a form, switch to alternate query mode
1993 self
.cache
.get_items(jid
, node
, self
._channel
_altinfo
, args
= (name
,))
1995 # Continue with the next
1996 self
._fetch
_source
= None
1997 self
._query
_visible
()
1999 def _update_error(self
, iter_
, jid
, node
):
2000 # switch to alternate query mode
2001 self
.cache
.get_items(jid
, node
, self
._channel
_altinfo
)
2003 def PubSubBrowser(account
, jid
, node
):
2005 Return an AgentBrowser subclass that will display service discovery for
2006 particular pubsub service. Different pubsub services may need to present
2007 different data during browsing
2009 # for now, only discussion groups are supported...
2010 # TODO: check if it has appropriate features to be such kind of service
2011 return DiscussionGroupsBrowser(account
, jid
, node
)
2013 class DiscussionGroupsBrowser(AgentBrowser
):
2015 For browsing pubsub-based discussion groups service
2018 def __init__(self
, account
, jid
, node
):
2019 AgentBrowser
.__init
__(self
, account
, jid
, node
)
2021 # this will become set object when we get subscriptions; None means
2022 # we don't know yet which groups are subscribed
2023 self
.subscriptions
= None
2025 # this will become our action widgets when we create them; None means
2026 # we don't have them yet (needed for check in callback)
2027 self
.subscribe_button
= None
2028 self
.unsubscribe_button
= None
2030 gajim
.connections
[account
].send_pb_subscription_query(jid
, self
._on
_pep
_subscriptions
)
2032 def _create_treemodel(self
):
2034 Create treemodel for the window
2036 # JID, node, name (with description) - pango markup, dont have info?, subscribed?
2037 self
.model
= gtk
.TreeStore(str, str, str, bool, bool)
2039 self
.model
.set_sort_column_id(2, gtk
.SORT_ASCENDING
)
2040 self
.window
.services_treeview
.set_model(self
.model
)
2043 # Pango markup for name and description, description printed with
2045 renderer
= gtk
.CellRendererText()
2046 col
= gtk
.TreeViewColumn(_('Name'))
2047 col
.pack_start(renderer
)
2048 col
.set_attributes(renderer
, markup
=2)
2049 col
.set_resizable(True)
2050 self
.window
.services_treeview
.insert_column(col
, -1)
2051 self
.window
.services_treeview
.set_headers_visible(True)
2053 # Subscription state
2054 renderer
= gtk
.CellRendererToggle()
2055 col
= gtk
.TreeViewColumn(_('Subscribed'))
2056 col
.pack_start(renderer
)
2057 col
.set_attributes(renderer
, inconsistent
=3, active
=4)
2058 col
.set_resizable(False)
2059 self
.window
.services_treeview
.insert_column(col
, -1)
2062 renderer
= gtk
.CellRendererText()
2063 col
= gtk
.TreeViewColumn(_('Node'))
2064 col
.pack_start(renderer
)
2065 col
.set_attributes(renderer
, markup
=1)
2066 col
.set_resizable(True)
2067 self
.window
.services_treeview
.insert_column(col
, -1)
2069 def _add_items(self
, jid
, node
, items
, force
):
2072 node_
= item
.get('node', '')
2073 self
._total
_items
+= 1
2074 self
._add
_item
(jid_
, node_
, node
, item
, force
)
2076 def _in_list_foreach(self
, model
, path
, iter_
, node
):
2077 if model
[path
][1] == node
:
2080 def _in_list(self
, node
):
2081 self
.in_list
= False
2082 self
.model
.foreach(self
._in
_list
_foreach
, node
)
2085 def _add_item(self
, jid
, node
, parent_node
, item
, force
):
2087 Called when we got basic information about new node from query. Show the
2090 name
= item
.get('name', '')
2092 if self
.subscriptions
is not None:
2094 subscribed
= node
in self
.subscriptions
2099 name
= gobject
.markup_escape_text(name
)
2100 name
= '<b>%s</b>' % name
2102 parent_iter
= self
._get
_iter
(parent_node
)
2103 if not self
._in
_list
(node
):
2104 self
.model
.append(parent_iter
, (jid
, node
, name
, dunno
, subscribed
))
2105 self
.cache
.get_items(jid
, node
, self
._add
_items
, force
= force
,
2108 def _get_child_iter(self
, parent_iter
, node
):
2109 child_iter
= self
.model
.iter_children(parent_iter
)
2111 if self
.model
[child_iter
][1] == node
:
2113 child_iter
= self
.model
.iter_next(child_iter
)
2116 def _get_iter(self
, node
):
2117 ''' Look for an iter with the given node '''
2118 self
.found_iter
= None
2119 def is_node(model
, path
, iter, node
):
2120 if model
[iter][1] == node
:
2121 self
.found_iter
= iter
2123 self
.model
.foreach(is_node
, node
)
2124 return self
.found_iter
2126 def _add_actions(self
):
2127 self
.post_button
= gtk
.Button(label
=_('New post'), use_underline
=True)
2128 self
.post_button
.set_sensitive(False)
2129 self
.post_button
.connect('clicked', self
.on_post_button_clicked
)
2130 self
.window
.action_buttonbox
.add(self
.post_button
)
2131 self
.post_button
.show_all()
2133 self
.subscribe_button
= gtk
.Button(label
=_('_Subscribe'), use_underline
=True)
2134 self
.subscribe_button
.set_sensitive(False)
2135 self
.subscribe_button
.connect('clicked', self
.on_subscribe_button_clicked
)
2136 self
.window
.action_buttonbox
.add(self
.subscribe_button
)
2137 self
.subscribe_button
.show_all()
2139 self
.unsubscribe_button
= gtk
.Button(label
=_('_Unsubscribe'), use_underline
=True)
2140 self
.unsubscribe_button
.set_sensitive(False)
2141 self
.unsubscribe_button
.connect('clicked', self
.on_unsubscribe_button_clicked
)
2142 self
.window
.action_buttonbox
.add(self
.unsubscribe_button
)
2143 self
.unsubscribe_button
.show_all()
2145 def _clean_actions(self
):
2146 if self
.post_button
is not None:
2147 self
.post_button
.destroy()
2148 self
.post_button
= None
2150 if self
.subscribe_button
is not None:
2151 self
.subscribe_button
.destroy()
2152 self
.subscribe_button
= None
2154 if self
.unsubscribe_button
is not None:
2155 self
.unsubscribe_button
.destroy()
2156 self
.unsubscribe_button
= None
2158 def update_actions(self
):
2160 Called when user selected a row. Make subscribe/unsubscribe buttons
2161 sensitive appropriatelly
2163 # we have nothing to do if we don't have buttons...
2164 if self
.subscribe_button
is None: return
2166 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
2167 if not iter_
or self
.subscriptions
is None:
2168 # no item selected or no subscriptions info, all buttons are insensitive
2169 self
.post_button
.set_sensitive(False)
2170 self
.subscribe_button
.set_sensitive(False)
2171 self
.unsubscribe_button
.set_sensitive(False)
2173 subscribed
= model
.get_value(iter_
, 4) # 4 = subscribed?
2174 self
.post_button
.set_sensitive(subscribed
)
2175 self
.subscribe_button
.set_sensitive(not subscribed
)
2176 self
.unsubscribe_button
.set_sensitive(subscribed
)
2178 def on_post_button_clicked(self
, widget
):
2180 Called when 'post' button is pressed. Open window to create post
2182 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
2183 if iter_
is None: return
2185 groupnode
= model
.get_value(iter_
, 1) # 1 = groupnode
2187 groups
.GroupsPostWindow(self
.account
, self
.jid
, groupnode
)
2189 def on_subscribe_button_clicked(self
, widget
):
2191 Called when 'subscribe' button is pressed. Send subscribtion request
2193 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
2194 if iter_
is None: return
2196 groupnode
= model
.get_value(iter_
, 1) # 1 = groupnode
2198 gajim
.connections
[self
.account
].send_pb_subscribe(self
.jid
, groupnode
, self
._on
_pep
_subscribe
, groupnode
)
2200 def on_unsubscribe_button_clicked(self
, widget
):
2202 Called when 'unsubscribe' button is pressed. Send unsubscription request
2204 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
2205 if iter_
is None: return
2207 groupnode
= model
.get_value(iter_
, 1) # 1 = groupnode
2209 gajim
.connections
[self
.account
].send_pb_unsubscribe(self
.jid
, groupnode
, self
._on
_pep
_unsubscribe
, groupnode
)
2211 def _on_pep_subscriptions(self
, conn
, request
):
2213 We got the subscribed groups list stanza. Now, if we already have items
2214 on the list, we should actualize them
2217 subscriptions
= request
.getTag('pubsub').getTag('subscriptions')
2222 for child
in subscriptions
.getTags('subscription'):
2223 groups
.add(child
['node'])
2225 self
.subscriptions
= groups
2227 # try to setup existing items in model
2228 model
= self
.window
.services_treeview
.get_model()
2231 # 3 = insensitive checkbox for subscribed
2235 row
[4] = groupnode
in groups
2237 # we now know subscriptions, update button states
2238 self
.update_actions()
2240 raise xmpp
.NodeProcessed
2242 def _on_pep_subscribe(self
, conn
, request
, groupnode
):
2244 We have just subscribed to a node. Update UI
2246 self
.subscriptions
.add(groupnode
)
2248 model
= self
.window
.services_treeview
.get_model()
2250 if row
[1] == groupnode
: # 1 = groupnode
2254 self
.update_actions()
2256 raise xmpp
.NodeProcessed
2258 def _on_pep_unsubscribe(self
, conn
, request
, groupnode
):
2260 We have just unsubscribed from a node. Update UI
2262 self
.subscriptions
.remove(groupnode
)
2264 model
= self
.window
.services_treeview
.get_model()
2266 if row
[1] == groupnode
: # 1 = groupnode
2270 self
.update_actions()
2272 raise xmpp
.NodeProcessed
2274 # Fill the global agent type info dictionary
2275 _agent_type_info
= _gen_agent_type_info()