handle nested roster group. TODO: Improve the way it's displayed in roster. Fixes #1381

This commit is contained in:
Yann Leboulanger 2010-12-11 12:57:09 +01:00
parent e9eb73d21c
commit 2653c160f2
3 changed files with 196 additions and 65 deletions

View File

@ -161,6 +161,8 @@ class CommonConnection:
self.awaiting_cids = {} # Used for XEP-0231
self.nested_group_delimiter = '::'
self.get_config_values_or_default()
def _compute_resource(self):
@ -2103,6 +2105,32 @@ class Connection(CommonConnection, ConnectionHandlers):
iq4.setData(self.annotations[jid])
self.connection.send(iq)
def get_roster_delimiter(self):
"""
Get roster group delimiter from storage as described in XEP 0083
"""
if not gajim.account_is_connected(self.name):
return
iq = common.xmpp.Iq(typ='get')
iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
iq2.addChild(name='roster', namespace='roster:delimiter')
id_ = self.connection.getAnID()
iq.setID(id_)
self.awaiting_answers[id_] = (DELIMITER_ARRIVED, )
self.connection.send(iq)
def set_roster_delimiter(self, delimiter='::'):
"""
Set roster group delimiter to the storage namespace
"""
if not gajim.account_is_connected(self.name):
return
iq = common.xmpp.Iq(typ='set')
iq2 = iq.addChild(name='query', namespace=common.xmpp.NS_PRIVATE)
iq3 = iq2.addChild(name='roster', namespace='roster:delimiter')
iq3.setData(delimiter)
self.connection.send(iq)
def get_metacontacts(self):
"""
@ -2136,6 +2164,22 @@ class Connection(CommonConnection, ConnectionHandlers):
iq3.addChild(name = 'meta', attrs = dict_)
self.connection.send(iq)
def request_roster(self):
version = None
features = self.connection.Dispatcher.Stream.features
if features and features.getTag('ver',
namespace=common.xmpp.NS_ROSTER_VER):
version = gajim.config.get_per('accounts', self.name,
'roster_version')
if version and not gajim.contacts.get_contacts_jid_list(
self.name):
gajim.config.set_per('accounts', self.name, 'roster_version',
'')
version = None
iq_id = self.connection.initRoster(version=version)
self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
def send_agent_status(self, agent, ptype):
if not gajim.account_is_connected(self.name):
return

View File

@ -84,6 +84,7 @@ VCARD_ARRIVED = 'vcard_arrived'
AGENT_REMOVED = 'agent_removed'
METACONTACTS_ARRIVED = 'metacontacts_arrived'
ROSTER_ARRIVED = 'roster_arrived'
DELIMITER_ARRIVED = 'delimiter_arrived'
PRIVACY_ARRIVED = 'privacy_arrived'
PEP_CONFIG = 'pep_config'
HAS_IDLE = True
@ -525,21 +526,22 @@ class ConnectionVcard:
else:
if iq_obj.getErrorCode() not in ('403', '406', '404'):
self.private_storage_supported = False
self.get_roster_delimiter()
elif self.awaiting_answers[id_][0] == DELIMITER_ARRIVED:
if not self.connection:
return
if iq_obj.getType() == 'result':
query = iq_obj.getTag('query')
delimiter = query.getTagData('roster')
if delimiter:
self.nested_group_delimiter = delimiter
else:
self.set_roster_delimiter('::')
else:
self.private_storage_supported = False
# We can now continue connection by requesting the roster
version = None
if con.Stream.features and con.Stream.features.getTag('ver',
namespace=common.xmpp.NS_ROSTER_VER):
version = gajim.config.get_per('accounts', self.name,
'roster_version')
if version and not gajim.contacts.get_contacts_jid_list(
self.name):
gajim.config.set_per('accounts', self.name,
'roster_version', '')
version = None
iq_id = self.connection.initRoster(version=version)
self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
self.request_roster()
elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED:
if iq_obj.getType() == 'result':
if not iq_obj.getTag('query'):

View File

@ -284,6 +284,36 @@ class RosterWindow:
self.starting = False
def _add_group_iter(self, account, group):
"""
Add a group iter in roster and return the newly created iter
"""
if self.regroup:
account_group = 'MERGED'
else:
account_group = account
delimiter = gajim.connections[account].nested_group_delimiter
group_splited = group.split(delimiter)
parent_group = delimiter.join(group_splited[:-1])
if parent_group in self._iters[account_group]['groups']:
iter_parent = self._iters[account_group]['groups'][parent_group]
elif parent_group:
iter_parent = self._add_group_iter(account, parent_group)
if parent_group not in gajim.groups[account]:
if account + parent_group in self.collapsed_rows:
is_expanded = False
else:
is_expanded = True
gajim.groups[account][parent_group] = {'expand': is_expanded}
else:
iter_parent = self._get_account_iter(account, self.model)
iter_group = self.model.append(iter_parent,
[gajim.interface.jabber_state_images['16']['closed'],
gobject.markup_escape_text(group), 'group', group, account, None,
None, None, None, None, None] + [None] * self.nb_ext_renderers)
self.draw_group(group, account)
self._iters[account_group]['groups'][group] = iter_group
return iter_group
def _add_entity(self, contact, account, groups=None,
big_brother_contact=None, big_brother_account=None):
@ -328,23 +358,12 @@ class RosterWindow:
# We are a normal contact. Add us to our groups.
if not groups:
groups = contact.get_shown_groups()
if self.regroup:
account_group = 'MERGED'
else:
account_group = account
for group in groups:
child_iterG = self._get_group_iter(group, account,
model=self.model)
if not child_iterG:
# Group is not yet in roster, add it!
child_iterA = self._get_account_iter(account, self.model)
child_iterG = self.model.append(child_iterA,
[gajim.interface.jabber_state_images['16']['closed'],
gobject.markup_escape_text(group),
'group', group, account, None, None, None, None, None,
None] + [None] * self.nb_ext_renderers)
self.draw_group(group, account)
self._iters[account_group]['groups'][group] = child_iterG
child_iterG = self._add_group_iter(account, group)
if contact.is_transport():
typestr = 'agent'
@ -419,8 +438,10 @@ class RosterWindow:
"Invalidated iters of %s" % contact.jid
parent_i = self.model.iter_parent(i)
parent_type = self.model[parent_i][C_TYPE]
if parent_type == 'group' and \
to_be_removed = i
while parent_type == 'group' and \
self.model.iter_n_children(parent_i) == 1:
if self.regroup:
account_group = 'MERGED'
@ -429,10 +450,12 @@ class RosterWindow:
group = self.model[parent_i][C_JID].decode('utf-8')
if group in gajim.groups[account]:
del gajim.groups[account][group]
self.model.remove(parent_i)
to_be_removed = parent_i
del self._iters[account_group]['groups'][group]
else:
self.model.remove(i)
parent_i = self.model.iter_parent(parent_i)
parent_type = self.model[parent_i][C_TYPE]
self.model.remove(to_be_removed)
del self._iters[account]['contacts'][contact.jid]
return True
@ -1248,13 +1271,19 @@ class RosterWindow:
if family and not is_big_brother and not self.starting:
self.draw_parent_contact(jid, account)
delimiter = gajim.connections[account].nested_group_delimiter
for group in contact.get_shown_groups():
# We need to make sure that _visible_func is called for
# our groups otherwise we might not be shown
iterG = self._get_group_iter(group, account, model=self.model)
if iterG:
# it's not self contact
self.model[iterG][C_JID] = self.model[iterG][C_JID]
group_splited = group.split(delimiter)
i = 1
while i < len(group_splited) + 1:
g = delimiter.join(group_splited[:i])
iterG = self._get_group_iter(g, account, model=self.model)
if iterG:
# it's not self contact
self.model[iterG][C_JID] = self.model[iterG][C_JID]
i += 1
gajim.plugin_manager.gui_extension_point('roster_draw_contact', self,
jid, account, contact)
@ -1446,16 +1475,21 @@ class RosterWindow:
"""
if not self.tree.get_model():
return
iterG = self._get_group_iter(group, account)
if not iterG:
# Group not visible
return
path = self.modelfilter.get_path(iterG)
if account + group in self.collapsed_rows:
self.tree.collapse_row(path)
else:
self.tree.expand_row(path, False)
return False
delimiter = gajim.connections[account].nested_group_delimiter
group_splited = group.split(delimiter)
i = 1
while i < len(group_splited) + 1:
g = delimiter.join(group_splited[:i])
iterG = self._get_group_iter(g, account)
if not iterG:
# Group not visible
return
path = self.modelfilter.get_path(iterG)
if account + g in self.collapsed_rows:
self.tree.collapse_row(path)
else:
self.tree.expand_row(path, False)
i += 1
##############################################################################
### Roster and Modelfilter handling
@ -1544,11 +1578,16 @@ class RosterWindow:
else:
accounts = [account]
for _acc in accounts:
delimiter = gajim.connections[_acc].nested_group_delimiter
for contact in gajim.contacts.iter_contacts(_acc):
if not self.contact_is_visible(contact, _acc):
continue
# Is this contact in this group?
if group in contact.get_shown_groups():
if self.contact_is_visible(contact, _acc):
return True
for grp in contact.get_shown_groups():
while grp:
if group == grp:
return True
grp = delimiter.join(grp.split(delimiter)[:-1])
return False
if type_ == 'contact':
if gajim.config.get('showoffline'):
@ -2101,7 +2140,7 @@ class RosterWindow:
ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account)
if ctrl and ctrl.type_id != message_control.TYPE_GC:
ctrl.contact = gajim.contacts.get_contact_with_highest_priority(
account, contact.jid)
account, contact.jid)
ctrl.update_status_display(name, uf_show, status)
if contact.resource:
@ -2111,7 +2150,7 @@ class RosterWindow:
# Delete pep if needed
keep_pep = any(c.show not in ('error', 'offline') for c in
contact_instances)
contact_instances)
if not keep_pep and contact.jid != gajim.get_jid_from_account(account) \
and not contact.is_groupchat():
self.delete_pep(contact.jid, account)
@ -3384,7 +3423,7 @@ class RosterWindow:
if (self.tree.row_expanded(path)):
self.tree.collapse_row(path)
else:
self.tree.expand_row(path, False)
self.expand_group_row(path)
elif type_ == 'contact' and x > x_min and x < x_min + 27:
if (self.tree.row_expanded(path)):
@ -3392,6 +3431,18 @@ class RosterWindow:
else:
self.tree.expand_row(path, False)
def expand_group_row(self, path):
self.tree.expand_row(path, False)
iter = self.modelfilter.get_iter(path)
child_iter = self.modelfilter.iter_children(iter)
while child_iter:
type_ = self.modelfilter[child_iter][C_TYPE]
account = self.modelfilter[child_iter][C_ACCOUNT]
group = self.modelfilter[child_iter][C_JID]
if type_ == 'group' and account + group not in self.collapsed_rows:
self.expand_group_row(self.modelfilter.get_path(child_iter))
child_iter = self.modelfilter.iter_next(child_iter)
def on_req_usub(self, widget, list_):
"""
Remove a contact. list_ is a list of (contact, account) tuples
@ -3966,7 +4017,7 @@ class RosterWindow:
type_ = model[titer][C_TYPE]
if type_ == 'group':
child_model[child_iter][C_IMG] = gajim.interface.\
jabber_state_images['16']['closed']
jabber_state_images['16']['closed']
group = model[titer][C_JID].decode('utf-8')
for account in accounts:
if group in gajim.groups[account]: # This account has this group
@ -4133,7 +4184,7 @@ class RosterWindow:
return
path = list_of_paths[0]
data = ''
if len(path) >= 3:
if len(path) >= 2:
data = model[path][C_JID]
selection.set(selection.target, 8, data)
@ -4306,6 +4357,12 @@ class RosterWindow:
context.finish(False, True)
return True
def move_group(self, old_name, new_name, account):
for group in gajim.groups[account].keys():
if group.startswith(old_name):
self.rename_group(group, group.replace(old_name, new_name),
account)
def drag_data_received_data(self, treeview, context, x, y, selection, info,
etime):
treeview.stop_emission('drag_data_received')
@ -4395,26 +4452,46 @@ class RosterWindow:
type_source = model[iter_source][C_TYPE]
account_source = model[iter_source][C_ACCOUNT].decode('utf-8')
# Only normal contacts can be dragged
if type_source != 'contact':
return
if gajim.config.get_per('accounts', account_source, 'is_zeroconf'):
return
if type_dest == 'self_contact':
# drop on self contact row
return
if type_dest == 'groupchat':
# drop on a minimized groupchat
# TODO: Invite to groupchat if type_dest = contact
return
if type_source == 'group':
if account_source != account_dest:
# drop on another account
return
grp_source = model[iter_source][C_JID].decode('utf-8')
delimiter = gajim.connections[account_source].nested_group_delimiter
grp_source_list = grp_source.split(delimiter)
new_grp = None
if type_dest == 'account':
new_grp = grp_source_list[-1]
elif type_dest == 'group':
new_grp = model[iter_dest][C_JID].decode('utf-8') + delimiter +\
grp_source_list[-1]
if new_grp:
self.move_group(grp_source, new_grp, account_source)
# Only normal contacts and group can be dragged
if type_source != 'contact':
return
# A contact was dropped
if gajim.config.get_per('accounts', account_dest, 'is_zeroconf'):
# drop on zeroconf account, adding not possible
return
if type_dest == 'self_contact':
# drop on self contact row
return
if type_dest == 'account' and account_source == account_dest:
# drop on the account it was dragged from
return
if type_dest == 'groupchat':
# drop on a minimized groupchat
# TODO: Invite to groupchat
return
# Get valid source group, jid and contact
it = iter_source
@ -4683,7 +4760,11 @@ class RosterWindow:
renderer.set_property('xalign', 0)
elif type_ == 'group':
self._set_group_row_background_color(renderer)
renderer.set_property('xalign', 0.2)
parent_iter = model.iter_parent(titer)
if model[parent_iter][C_TYPE] == 'group':
renderer.set_property('xalign', 0.4)
else:
renderer.set_property('xalign', 0.2)
elif type_:
# prevent type_ = None, see http://trac.gajim.org/ticket/2534
if not model[titer][C_JID] or not model[titer][C_ACCOUNT]:
@ -4696,7 +4777,7 @@ class RosterWindow:
if model[parent_iter][C_TYPE] == 'contact':
renderer.set_property('xalign', 1)
else:
renderer.set_property('xalign', 0.4)
renderer.set_property('xalign', 0.6)
renderer.set_property('width', 26)
def _nameCellDataFunc(self, column, renderer, model, titer, data=None):
@ -4724,7 +4805,11 @@ class RosterWindow:
self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False)
renderer.set_property('font',
gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
renderer.set_property('xpad', 4)
parent_iter = model.iter_parent(titer)
if model[parent_iter][C_TYPE] == 'group':
renderer.set_property('xpad', 8)
else:
renderer.set_property('xpad', 4)
self._set_group_row_background_color(renderer)
elif type_:
# prevent type_ = None, see http://trac.gajim.org/ticket/2534
@ -4755,7 +4840,7 @@ class RosterWindow:
if model[parent_iter][C_TYPE] == 'contact':
renderer.set_property('xpad', 16)
else:
renderer.set_property('xpad', 8)
renderer.set_property('xpad', 12)
def _fill_pep_pixbuf_renderer(self, column, renderer, model, titer,
data=None):