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()
1477 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1480 if not model
[iter_
][0]:
1481 # We're on a category row
1483 if model
[iter_
][4] != 0:
1484 # We don't have the info (yet)
1485 # It's either unknown or a transport, register button should be active
1486 if self
.register_button
:
1487 self
.register_button
.set_sensitive(True)
1488 # Guess what kind of service we're dealing with
1489 if self
.browse_button
:
1490 jid
= model
[iter_
][0].decode('utf-8')
1491 type_
= gajim
.get_transport_name_from_jid(jid
,
1492 use_config_setting
= False)
1494 identity
= {'category': '_jid', 'type': type_
}
1495 klass
= self
.cache
.get_browser([identity
])
1497 self
.browse_button
.set_sensitive(True)
1500 self
.browse_button
.set_sensitive(True)
1502 # Normal case, we have info
1503 AgentBrowser
.update_actions(self
)
1505 def _update_actions(self
, jid
, node
, identities
, features
, data
):
1506 AgentBrowser
._update
_actions
(self
, jid
, node
, identities
, features
, data
)
1507 if self
.execute_button
and xmpp
.NS_COMMANDS
in features
:
1508 self
.execute_button
.set_sensitive(True)
1509 if self
.search_button
and xmpp
.NS_SEARCH
in features
:
1510 self
.search_button
.set_sensitive(True)
1511 if self
.register_button
and xmpp
.NS_REGISTER
in features
:
1512 # We can register this agent
1513 registered_transports
= []
1514 jid_list
= gajim
.contacts
.get_jid_list(self
.account
)
1515 for jid
in jid_list
:
1516 contact
= gajim
.contacts
.get_first_contact_from_jid(
1518 if _('Transports') in contact
.groups
:
1519 registered_transports
.append(jid
)
1520 if jid
in registered_transports
:
1521 self
.register_button
.set_label(_('_Edit'))
1523 self
.register_button
.set_label(_('Re_gister'))
1524 self
.register_button
.set_sensitive(True)
1525 if self
.join_button
and xmpp
.NS_MUC
in features
:
1526 self
.join_button
.set_sensitive(True)
1528 def _default_action(self
, jid
, node
, identities
, features
, data
):
1529 if AgentBrowser
._default
_action
(self
, jid
, node
, identities
, features
, data
):
1531 if xmpp
.NS_REGISTER
in features
:
1532 # Register if we can't browse
1533 self
.on_register_button_clicked()
1537 def browse(self
, force
=False):
1539 AgentBrowser
.browse(self
, force
= force
)
1541 def _expand_all(self
):
1543 Expand all items in the treeview
1545 # GTK apparently screws up here occasionally. :/
1546 #def expand_all(*args):
1547 # self.window.services_treeview.expand_all()
1548 # self.expanding = False
1550 #self.expanding = True
1551 #gobject.idle_add(expand_all)
1552 self
.window
.services_treeview
.expand_all()
1554 def _update_progressbar(self
):
1556 Update the progressbar
1558 # Refresh this every update
1559 if self
._progressbar
_sourceid
:
1560 gobject
.source_remove(self
._progressbar
_sourceid
)
1563 if self
._total
_items
:
1564 self
.window
.progressbar
.set_text(_("Scanning %(current)d / %(total)d.."
1565 ) % {'current': self
._progress
, 'total': self
._total
_items
})
1566 fraction
= float(self
._progress
) / float(self
._total
_items
)
1567 if self
._progress
>= self
._total
_items
:
1568 # We show the progressbar for just a bit before hiding it.
1569 id_
= gobject
.timeout_add_seconds(2, self
._hide
_progressbar
_cb
)
1570 self
._progressbar
_sourceid
= id_
1572 self
.window
.progressbar
.show()
1573 # Hide the progressbar if we're timing out anyways. (20 secs)
1574 id_
= gobject
.timeout_add_seconds(20, self
._hide
_progressbar
_cb
)
1575 self
._progressbar
_sourceid
= id_
1576 self
.window
.progressbar
.set_fraction(fraction
)
1578 def _hide_progressbar_cb(self
, *args
):
1580 Simple callback to hide the progressbar a second after we finish
1583 self
.window
.progressbar
.hide()
1586 def _friendly_category(self
, category
, type_
=None):
1588 Get the friendly category name and priority
1592 # Try type-specific override
1594 cat
, prio
= _cat_to_descr
[(category
, type_
)]
1599 cat
, prio
= _cat_to_descr
[category
]
1601 cat
, prio
= _cat_to_descr
['other']
1604 def _create_category(self
, cat
, type_
=None):
1606 Creates a category row
1608 cat
, prio
= self
._friendly
_category
(cat
, type_
)
1609 return self
.model
.append(None, ('', '', None, cat
, prio
))
1611 def _find_category(self
, cat
, type_
=None):
1613 Looks up a category row and returns the iterator to it, or None
1615 cat
= self
._friendly
_category
(cat
, type_
)[0]
1616 iter_
= self
.model
.get_iter_root()
1618 if self
.model
.get_value(iter_
, 3).decode('utf-8') == cat
:
1620 iter_
= self
.model
.iter_next(iter_
)
1625 def _find_item(self
, jid
, node
):
1627 cat_iter
= self
.model
.get_iter_root()
1628 while cat_iter
and not iter_
:
1629 iter_
= self
.model
.iter_children(cat_iter
)
1631 cjid
= self
.model
.get_value(iter_
, 0).decode('utf-8')
1632 cnode
= self
.model
.get_value(iter_
, 1).decode('utf-8')
1633 if jid
== cjid
and node
== cnode
:
1635 iter_
= self
.model
.iter_next(iter_
)
1636 cat_iter
= self
.model
.iter_next(cat_iter
)
1641 def _add_item(self
, jid
, node
, parent_node
, item
, force
):
1643 addr
= get_agent_address(jid
, node
)
1645 descr
= "<b>%s</b>\n%s" % (item
['name'], addr
)
1647 descr
= "<b>%s</b>" % addr
1648 # Guess which kind of service this is
1650 type_
= gajim
.get_transport_name_from_jid(jid
,
1651 use_config_setting
= False)
1653 identity
= {'category': '_jid', 'type': type_
}
1654 identities
.append(identity
)
1655 cat_args
= ('_jid', type_
)
1657 # Put it in the 'other' category for now
1658 cat_args
= ('other',)
1659 # Set the pixmap for the row
1660 pix
= self
.cache
.get_icon(identities
)
1661 # Put it in the right category
1662 cat
= self
._find
_category
(*cat_args
)
1664 cat
= self
._create
_category
(*cat_args
)
1665 self
.model
.append(cat
, (jid
, node
, pix
, descr
, 1))
1666 gobject
.idle_add(self
._expand
_all
)
1667 # Grab info on the service
1668 self
.cache
.get_info(jid
, node
, self
._agent
_info
, force
=force
)
1669 self
._update
_progressbar
()
1671 def _update_item(self
, iter_
, jid
, node
, item
):
1672 addr
= get_agent_address(jid
, node
)
1674 descr
= "<b>%s</b>\n%s" % (item
['name'], addr
)
1676 descr
= "<b>%s</b>" % addr
1677 self
.model
[iter_
][3] = descr
1679 def _update_info(self
, iter_
, jid
, node
, identities
, features
, data
):
1680 addr
= get_agent_address(jid
, node
)
1681 name
= identities
[0].get('name', '')
1683 descr
= "<b>%s</b>\n%s" % (name
, addr
)
1685 descr
= "<b>%s</b>" % addr
1689 self
._update
_progressbar
()
1691 # Search for an icon and category we can display
1692 pix
= self
.cache
.get_icon(identities
)
1693 cat
, type_
= None, None
1694 for identity
in identities
:
1696 cat
, type_
= identity
['category'], identity
['type']
1701 # Check if we have to move categories
1702 old_cat_iter
= self
.model
.iter_parent(iter_
)
1703 old_cat
= self
.model
.get_value(old_cat_iter
, 3).decode('utf-8')
1704 if self
.model
.get_value(old_cat_iter
, 3) == cat
:
1705 # Already in the right category, just update
1706 self
.model
[iter_
][2] = pix
1707 self
.model
[iter_
][3] = descr
1708 self
.model
[iter_
][4] = 0
1710 # Not in the right category, move it.
1711 self
.model
.remove(iter_
)
1713 # Check if the old category is empty
1714 if not self
.model
.iter_is_valid(old_cat_iter
):
1715 old_cat_iter
= self
._find
_category
(old_cat
)
1716 if not self
.model
.iter_children(old_cat_iter
):
1717 self
.model
.remove(old_cat_iter
)
1719 cat_iter
= self
._find
_category
(cat
, type_
)
1721 cat_iter
= self
._create
_category
(cat
, type_
)
1722 self
.model
.append(cat_iter
, (jid
, node
, pix
, descr
, 0))
1725 def _update_error(self
, iter_
, jid
, node
):
1726 self
.model
[iter_
][4] = 2
1728 self
._update
_progressbar
()
1731 class MucBrowser(AgentBrowser
):
1732 def __init__(self
, *args
, **kwargs
):
1733 AgentBrowser
.__init
__(self
, *args
, **kwargs
)
1734 self
.join_button
= None
1735 self
.bookmark_button
= None
1737 def _create_treemodel(self
):
1738 # JID, node, name, users_int, users_str, description, fetched
1739 # This is rather long, I'd rather not use a data_func here though.
1740 # Users is a string, because want to be able to leave it empty.
1741 self
.model
= gtk
.ListStore(str, str, str, int, str, str, bool)
1742 self
.model
.set_sort_column_id(2, gtk
.SORT_ASCENDING
)
1743 self
.window
.services_treeview
.set_model(self
.model
)
1745 col
= gtk
.TreeViewColumn(_('Name'))
1746 col
.set_sizing(gtk
.TREE_VIEW_COLUMN_FIXED
)
1747 col
.set_fixed_width(100)
1748 renderer
= gtk
.CellRendererText()
1749 col
.pack_start(renderer
)
1750 col
.set_attributes(renderer
, text
= 2)
1751 col
.set_sort_column_id(2)
1752 self
.window
.services_treeview
.insert_column(col
, -1)
1753 col
.set_resizable(True)
1755 col
= gtk
.TreeViewColumn(_('Users'))
1756 renderer
= gtk
.CellRendererText()
1757 col
.pack_start(renderer
)
1758 col
.set_attributes(renderer
, text
= 4)
1759 col
.set_sort_column_id(3)
1760 self
.window
.services_treeview
.insert_column(col
, -1)
1761 col
.set_resizable(True)
1762 # Description column
1763 col
= gtk
.TreeViewColumn(_('Description'))
1764 renderer
= gtk
.CellRendererText()
1765 col
.pack_start(renderer
)
1766 col
.set_attributes(renderer
, text
= 5)
1767 col
.set_sort_column_id(4)
1768 self
.window
.services_treeview
.insert_column(col
, -1)
1769 col
.set_resizable(True)
1771 col
= gtk
.TreeViewColumn(_('Id'))
1772 renderer
= gtk
.CellRendererText()
1773 col
.pack_start(renderer
)
1774 col
.set_attributes(renderer
, text
= 0)
1775 col
.set_sort_column_id(0)
1776 self
.window
.services_treeview
.insert_column(col
, -1)
1777 col
.set_resizable(True)
1778 self
.window
.services_treeview
.set_headers_visible(True)
1779 self
.window
.services_treeview
.set_headers_clickable(True)
1780 # Source id for idle callback used to start disco#info queries.
1781 self
._fetch
_source
= None
1782 # Query failure counter
1784 # Connect to scrollwindow scrolling
1785 self
.vadj
= self
.window
.services_scrollwin
.get_property('vadjustment')
1786 self
.vadj_cbid
= self
.vadj
.connect('value-changed', self
.on_scroll
)
1787 # And to size changes
1788 self
.size_cbid
= self
.window
.services_scrollwin
.connect(
1789 'size-allocate', self
.on_scroll
)
1791 def _clean_treemodel(self
):
1793 self
.window
.services_scrollwin
.disconnect(self
.size_cbid
)
1794 self
.size_cbid
= None
1796 self
.vadj
.disconnect(self
.vadj_cbid
)
1797 self
.vadj_cbid
= None
1798 AgentBrowser
._clean
_treemodel
(self
)
1800 def _add_actions(self
):
1801 self
.bookmark_button
= gtk
.Button(label
=_('_Bookmark'), use_underline
=True)
1802 self
.bookmark_button
.connect('clicked', self
.on_bookmark_button_clicked
)
1803 self
.window
.action_buttonbox
.add(self
.bookmark_button
)
1804 self
.bookmark_button
.show_all()
1805 self
.join_button
= gtk
.Button(label
=_('_Join'), use_underline
=True)
1806 self
.join_button
.connect('clicked', self
.on_join_button_clicked
)
1807 self
.window
.action_buttonbox
.add(self
.join_button
)
1808 self
.join_button
.show_all()
1810 def _clean_actions(self
):
1811 if self
.bookmark_button
:
1812 self
.bookmark_button
.destroy()
1813 self
.bookmark_button
= None
1814 if self
.join_button
:
1815 self
.join_button
.destroy()
1816 self
.join_button
= None
1818 def on_bookmark_button_clicked(self
, *args
):
1819 model
, iter = self
.window
.services_treeview
.get_selection().get_selected()
1822 name
= gajim
.config
.get_per('accounts', self
.account
, 'name')
1823 room_jid
= model
[iter][0].decode('utf-8')
1825 'name': room_jid
.split('@')[0],
1833 for bookmark
in gajim
.connections
[self
.account
].bookmarks
:
1834 if bookmark
['jid'] == bm
['jid']:
1835 dialogs
.ErrorDialog(
1836 _('Bookmark already set'),
1837 _('Group Chat "%s" is already in your bookmarks.') % bm
['jid'])
1840 gajim
.connections
[self
.account
].bookmarks
.append(bm
)
1841 gajim
.connections
[self
.account
].store_bookmarks()
1843 gajim
.interface
.roster
.set_actions_menu_needs_rebuild()
1845 dialogs
.InformationDialog(
1846 _('Bookmark has been added successfully'),
1847 _('You can manage your bookmarks via Actions menu in your roster.'))
1849 def on_join_button_clicked(self
, *args
):
1851 When we want to join a conference: ask specific informations about the
1852 selected agent and close the window
1854 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
1857 service
= model
[iter_
][0].decode('utf-8')
1858 if 'join_gc' not in gajim
.interface
.instances
[self
.account
]:
1860 dialogs
.JoinGroupchatWindow(self
.account
, service
)
1861 except GajimGeneralException
:
1864 gajim
.interface
.instances
[self
.account
]['join_gc']._set
_room
_jid
(
1866 gajim
.interface
.instances
[self
.account
]['join_gc'].window
.present()
1868 def update_actions(self
):
1869 sens
= self
.window
.services_treeview
.get_selection().count_selected_rows()
1870 if self
.bookmark_button
:
1871 self
.bookmark_button
.set_sensitive(sens
> 0)
1872 if self
.join_button
:
1873 self
.join_button
.set_sensitive(sens
> 0)
1875 def default_action(self
):
1876 self
.on_join_button_clicked()
1878 def _start_info_query(self
):
1880 Idle callback to start checking for visible rows
1882 self
._fetch
_source
= None
1883 self
._query
_visible
()
1886 def on_scroll(self
, *args
):
1888 Scrollwindow callback to trigger new queries on scolling
1890 # This apparently happens when inactive sometimes
1891 self
._query
_visible
()
1893 def _query_visible(self
):
1895 Query the next visible row for info
1897 if self
._fetch
_source
:
1898 # We're already fetching
1900 view
= self
.window
.services_treeview
1901 if not view
.flags() & gtk
.REALIZED
:
1902 # Prevent a silly warning, try again in a bit.
1903 self
._fetch
_source
= gobject
.timeout_add(100, self
._start
_info
_query
)
1905 # We have to do this in a pygtk <2.8 compatible way :/
1906 #start, end = self.window.services_treeview.get_visible_range()
1907 rect
= view
.get_visible_rect()
1911 sx
, sy
= view
.tree_to_widget_coords(rect
.x
, rect
.y
)
1912 spath
= view
.get_path_at_pos(sx
, sy
)[0]
1913 iter_
= self
.model
.get_iter(spath
)
1915 self
._fetch
_source
= None
1918 # Iter compare is broke, use the path instead
1920 ex
, ey
= view
.tree_to_widget_coords(rect
.x
+ rect
.height
,
1921 rect
.y
+ rect
.height
)
1922 end
= view
.get_path_at_pos(ex
, ey
)[0]
1923 # end is the last visible, we want to query that aswell
1926 # We're at the end of the model, we can leave end=None though.
1928 while iter_
and self
.model
.get_path(iter_
) != end
:
1929 if not self
.model
.get_value(iter_
, 6):
1930 jid
= self
.model
.get_value(iter_
, 0).decode('utf-8')
1931 node
= self
.model
.get_value(iter_
, 1).decode('utf-8')
1932 self
.cache
.get_info(jid
, node
, self
._agent
_info
)
1933 self
._fetch
_source
= True
1935 iter_
= self
.model
.iter_next(iter_
)
1936 self
._fetch
_source
= None
1938 def _channel_altinfo(self
, jid
, node
, items
, name
= None):
1940 Callback for the alternate disco#items query. We try to atleast get the
1941 amount of users in the room if the service does not support MUC dataforms
1944 # The server returned an error
1946 if self
._broken
>= 3:
1947 # Disable queries completely after 3 failures
1949 self
.window
.services_scrollwin
.disconnect(self
.size_cbid
)
1950 self
.size_cbid
= None
1952 self
.vadj
.disconnect(self
.vadj_cbid
)
1953 self
.vadj_cbid
= None
1954 self
._fetch
_source
= None
1957 iter_
= self
._find
_item
(jid
, node
)
1960 self
.model
[iter_
][2] = name
1961 self
.model
[iter_
][3] = len(items
) # The number of users
1962 self
.model
[iter_
][4] = str(len(items
)) # The number of users
1963 self
.model
[iter_
][6] = True
1964 self
._fetch
_source
= None
1965 self
._query
_visible
()
1967 def _add_item(self
, jid
, node
, parent_node
, item
, force
):
1968 self
.model
.append((jid
, node
, item
.get('name', ''), -1, '', '', False))
1969 if not self
._fetch
_source
:
1970 self
._fetch
_source
= gobject
.idle_add(self
._start
_info
_query
)
1972 def _update_info(self
, iter_
, jid
, node
, identities
, features
, data
):
1973 name
= identities
[0].get('name', '')
1975 typefield
= form
.getField('FORM_TYPE')
1976 if typefield
and typefield
.getValue() == \
1977 'http://jabber.org/protocol/muc#roominfo':
1978 # Fill model row from the form's fields
1979 users
= form
.getField('muc#roominfo_occupants')
1980 descr
= form
.getField('muc#roominfo_description')
1982 self
.model
[iter_
][3] = int(users
.getValue())
1983 self
.model
[iter_
][4] = users
.getValue()
1985 self
.model
[iter_
][5] = descr
.getValue()
1986 # Only set these when we find a form with additional info
1987 # Some servers don't support forms and put extra info in
1988 # the name attribute, so we preserve it in that case.
1989 self
.model
[iter_
][2] = name
1990 self
.model
[iter_
][6] = True
1993 # We didn't find a form, switch to alternate query mode
1994 self
.cache
.get_items(jid
, node
, self
._channel
_altinfo
, args
= (name
,))
1996 # Continue with the next
1997 self
._fetch
_source
= None
1998 self
._query
_visible
()
2000 def _update_error(self
, iter_
, jid
, node
):
2001 # switch to alternate query mode
2002 self
.cache
.get_items(jid
, node
, self
._channel
_altinfo
)
2004 def PubSubBrowser(account
, jid
, node
):
2006 Return an AgentBrowser subclass that will display service discovery for
2007 particular pubsub service. Different pubsub services may need to present
2008 different data during browsing
2010 # for now, only discussion groups are supported...
2011 # TODO: check if it has appropriate features to be such kind of service
2012 return DiscussionGroupsBrowser(account
, jid
, node
)
2014 class DiscussionGroupsBrowser(AgentBrowser
):
2016 For browsing pubsub-based discussion groups service
2019 def __init__(self
, account
, jid
, node
):
2020 AgentBrowser
.__init
__(self
, account
, jid
, node
)
2022 # this will become set object when we get subscriptions; None means
2023 # we don't know yet which groups are subscribed
2024 self
.subscriptions
= None
2026 # this will become our action widgets when we create them; None means
2027 # we don't have them yet (needed for check in callback)
2028 self
.subscribe_button
= None
2029 self
.unsubscribe_button
= None
2031 gajim
.connections
[account
].send_pb_subscription_query(jid
, self
._on
_pep
_subscriptions
)
2033 def _create_treemodel(self
):
2035 Create treemodel for the window
2037 # JID, node, name (with description) - pango markup, dont have info?, subscribed?
2038 self
.model
= gtk
.TreeStore(str, str, str, bool, bool)
2040 self
.model
.set_sort_column_id(2, gtk
.SORT_ASCENDING
)
2041 self
.window
.services_treeview
.set_model(self
.model
)
2044 # Pango markup for name and description, description printed with
2046 renderer
= gtk
.CellRendererText()
2047 col
= gtk
.TreeViewColumn(_('Name'))
2048 col
.pack_start(renderer
)
2049 col
.set_attributes(renderer
, markup
=2)
2050 col
.set_resizable(True)
2051 self
.window
.services_treeview
.insert_column(col
, -1)
2052 self
.window
.services_treeview
.set_headers_visible(True)
2054 # Subscription state
2055 renderer
= gtk
.CellRendererToggle()
2056 col
= gtk
.TreeViewColumn(_('Subscribed'))
2057 col
.pack_start(renderer
)
2058 col
.set_attributes(renderer
, inconsistent
=3, active
=4)
2059 col
.set_resizable(False)
2060 self
.window
.services_treeview
.insert_column(col
, -1)
2063 renderer
= gtk
.CellRendererText()
2064 col
= gtk
.TreeViewColumn(_('Node'))
2065 col
.pack_start(renderer
)
2066 col
.set_attributes(renderer
, markup
=1)
2067 col
.set_resizable(True)
2068 self
.window
.services_treeview
.insert_column(col
, -1)
2070 def _add_items(self
, jid
, node
, items
, force
):
2073 node_
= item
.get('node', '')
2074 self
._total
_items
+= 1
2075 self
._add
_item
(jid_
, node_
, node
, item
, force
)
2077 def _in_list_foreach(self
, model
, path
, iter_
, node
):
2078 if model
[path
][1] == node
:
2081 def _in_list(self
, node
):
2082 self
.in_list
= False
2083 self
.model
.foreach(self
._in
_list
_foreach
, node
)
2086 def _add_item(self
, jid
, node
, parent_node
, item
, force
):
2088 Called when we got basic information about new node from query. Show the
2091 name
= item
.get('name', '')
2093 if self
.subscriptions
is not None:
2095 subscribed
= node
in self
.subscriptions
2100 name
= gobject
.markup_escape_text(name
)
2101 name
= '<b>%s</b>' % name
2103 parent_iter
= self
._get
_iter
(parent_node
)
2104 if not self
._in
_list
(node
):
2105 self
.model
.append(parent_iter
, (jid
, node
, name
, dunno
, subscribed
))
2106 self
.cache
.get_items(jid
, node
, self
._add
_items
, force
= force
,
2109 def _get_child_iter(self
, parent_iter
, node
):
2110 child_iter
= self
.model
.iter_children(parent_iter
)
2112 if self
.model
[child_iter
][1] == node
:
2114 child_iter
= self
.model
.iter_next(child_iter
)
2117 def _get_iter(self
, node
):
2118 ''' Look for an iter with the given node '''
2119 self
.found_iter
= None
2120 def is_node(model
, path
, iter, node
):
2121 if model
[iter][1] == node
:
2122 self
.found_iter
= iter
2124 self
.model
.foreach(is_node
, node
)
2125 return self
.found_iter
2127 def _add_actions(self
):
2128 self
.post_button
= gtk
.Button(label
=_('New post'), use_underline
=True)
2129 self
.post_button
.set_sensitive(False)
2130 self
.post_button
.connect('clicked', self
.on_post_button_clicked
)
2131 self
.window
.action_buttonbox
.add(self
.post_button
)
2132 self
.post_button
.show_all()
2134 self
.subscribe_button
= gtk
.Button(label
=_('_Subscribe'), use_underline
=True)
2135 self
.subscribe_button
.set_sensitive(False)
2136 self
.subscribe_button
.connect('clicked', self
.on_subscribe_button_clicked
)
2137 self
.window
.action_buttonbox
.add(self
.subscribe_button
)
2138 self
.subscribe_button
.show_all()
2140 self
.unsubscribe_button
= gtk
.Button(label
=_('_Unsubscribe'), use_underline
=True)
2141 self
.unsubscribe_button
.set_sensitive(False)
2142 self
.unsubscribe_button
.connect('clicked', self
.on_unsubscribe_button_clicked
)
2143 self
.window
.action_buttonbox
.add(self
.unsubscribe_button
)
2144 self
.unsubscribe_button
.show_all()
2146 def _clean_actions(self
):
2147 if self
.post_button
is not None:
2148 self
.post_button
.destroy()
2149 self
.post_button
= None
2151 if self
.subscribe_button
is not None:
2152 self
.subscribe_button
.destroy()
2153 self
.subscribe_button
= None
2155 if self
.unsubscribe_button
is not None:
2156 self
.unsubscribe_button
.destroy()
2157 self
.unsubscribe_button
= None
2159 def update_actions(self
):
2161 Called when user selected a row. Make subscribe/unsubscribe buttons
2162 sensitive appropriatelly
2164 # we have nothing to do if we don't have buttons...
2165 if self
.subscribe_button
is None: return
2167 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
2168 if not iter_
or self
.subscriptions
is None:
2169 # no item selected or no subscriptions info, all buttons are insensitive
2170 self
.post_button
.set_sensitive(False)
2171 self
.subscribe_button
.set_sensitive(False)
2172 self
.unsubscribe_button
.set_sensitive(False)
2174 subscribed
= model
.get_value(iter_
, 4) # 4 = subscribed?
2175 self
.post_button
.set_sensitive(subscribed
)
2176 self
.subscribe_button
.set_sensitive(not subscribed
)
2177 self
.unsubscribe_button
.set_sensitive(subscribed
)
2179 def on_post_button_clicked(self
, widget
):
2181 Called when 'post' button is pressed. Open window to create post
2183 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
2184 if iter_
is None: return
2186 groupnode
= model
.get_value(iter_
, 1) # 1 = groupnode
2188 groups
.GroupsPostWindow(self
.account
, self
.jid
, groupnode
)
2190 def on_subscribe_button_clicked(self
, widget
):
2192 Called when 'subscribe' button is pressed. Send subscribtion request
2194 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
2195 if iter_
is None: return
2197 groupnode
= model
.get_value(iter_
, 1) # 1 = groupnode
2199 gajim
.connections
[self
.account
].send_pb_subscribe(self
.jid
, groupnode
, self
._on
_pep
_subscribe
, groupnode
)
2201 def on_unsubscribe_button_clicked(self
, widget
):
2203 Called when 'unsubscribe' button is pressed. Send unsubscription request
2205 model
, iter_
= self
.window
.services_treeview
.get_selection().get_selected()
2206 if iter_
is None: return
2208 groupnode
= model
.get_value(iter_
, 1) # 1 = groupnode
2210 gajim
.connections
[self
.account
].send_pb_unsubscribe(self
.jid
, groupnode
, self
._on
_pep
_unsubscribe
, groupnode
)
2212 def _on_pep_subscriptions(self
, conn
, request
):
2214 We got the subscribed groups list stanza. Now, if we already have items
2215 on the list, we should actualize them
2218 subscriptions
= request
.getTag('pubsub').getTag('subscriptions')
2223 for child
in subscriptions
.getTags('subscription'):
2224 groups
.add(child
['node'])
2226 self
.subscriptions
= groups
2228 # try to setup existing items in model
2229 model
= self
.window
.services_treeview
.get_model()
2232 # 3 = insensitive checkbox for subscribed
2236 row
[4] = groupnode
in groups
2238 # we now know subscriptions, update button states
2239 self
.update_actions()
2241 raise xmpp
.NodeProcessed
2243 def _on_pep_subscribe(self
, conn
, request
, groupnode
):
2245 We have just subscribed to a node. Update UI
2247 self
.subscriptions
.add(groupnode
)
2249 model
= self
.window
.services_treeview
.get_model()
2251 if row
[1] == groupnode
: # 1 = groupnode
2255 self
.update_actions()
2257 raise xmpp
.NodeProcessed
2259 def _on_pep_unsubscribe(self
, conn
, request
, groupnode
):
2261 We have just unsubscribed from a node. Update UI
2263 self
.subscriptions
.remove(groupnode
)
2265 model
= self
.window
.services_treeview
.get_model()
2267 if row
[1] == groupnode
: # 1 = groupnode
2271 self
.update_actions()
2273 raise xmpp
.NodeProcessed
2275 # Fill the global agent type info dictionary
2276 _agent_type_info
= _gen_agent_type_info()