diff --git a/src/chat_control.py b/src/chat_control.py index 6cfd56226..1989fb20f 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -2108,7 +2108,7 @@ class ChatControl(ChatControlBase): if (not show_transports and gajim.jid_is_transport(jid)) or \ (not show_offline and typ == 'chat' and \ len(gajim.contacts.get_contacts(self.account, jid)) < 2): - gajim.interface.roster.really_remove_contact(self.contact, + gajim.interface.roster.remove_to_be_removed(self.contact.jid, self.account) elif typ == 'pm': control.remove_contact(nick) diff --git a/src/common/contacts.py b/src/common/contacts.py index 33ca5adde..1b9937e55 100644 --- a/src/common/contacts.py +++ b/src/common/contacts.py @@ -245,6 +245,11 @@ class Contacts: return c return None + def iter_contacts(self, account): + for jid in self._contacts[account]: + for contact in self._contacts[account][jid]: + yield contact + def get_contact_from_full_jid(self, account, fjid): ''' Get Contact object for specific resource of given jid''' barejid, resource = common.gajim.get_room_and_nick_from_fjid(fjid) @@ -292,10 +297,6 @@ class Contacts: for account in accounts: our_jid = common.gajim.get_jid_from_account(account) for jid in self.get_jid_list(account): - if self.has_brother(account, jid) and not \ - self.is_big_brother(account, jid): - # count metacontacts only once - continue if jid == our_jid: continue if common.gajim.jid_is_transport(jid) and not \ diff --git a/src/dialogs.py b/src/dialogs.py index 8ebfc0c44..b13113488 100644 --- a/src/dialogs.py +++ b/src/dialogs.py @@ -91,63 +91,21 @@ class EditGroupsDialog: if response_id == gtk.RESPONSE_CLOSE: self.dialog.destroy() - def update_contact(self): - for (contact, account) in self.list_: - tag = gajim.contacts.get_metacontacts_tag(account, contact.jid) - if not tag: - gajim.interface.roster.remove_contact(contact, account) - gajim.interface.roster.add_contact_to_roster(contact.jid, account) - gajim.connections[account].update_contact(contact.jid, contact.name, - contact.groups) - continue - all_jid = gajim.contacts.get_metacontacts_jids(tag) - for _account in all_jid: - if not gajim.interface.roster.regroup and _account != account: - continue - for _jid in all_jid[_account]: - c = gajim.contacts.get_first_contact_from_jid(_account, _jid) - if not c: - continue - gajim.interface.roster.remove_contact(c, _account) - gajim.interface.roster.add_contact_to_roster(_jid, _account) - gajim.connections[_account].update_contact(_jid, c.name, - c.groups) - def remove_group(self, group): '''remove group group from all contacts and all their brothers''' for (contact, account) in self.list_: - tag = gajim.contacts.get_metacontacts_tag(account, contact.jid) - if not tag: - if group in contact.groups: - contact.groups.remove(group) - continue - all_jid = gajim.contacts.get_metacontacts_jids(tag) - for _account in all_jid: - if not gajim.interface.roster.regroup and _account != account: - continue - for _jid in all_jid[_account]: - contacts = gajim.contacts.get_contacts(_account, _jid) - for c in contacts: - if group in c.groups: - c.groups.remove(group) + gajim.interface.roster.remove_contact_from_groups(contact.jid, account, [group]) + + # FIXME: Ugly workaround. + gajim.interface.roster.draw_group(_('General'), account) def add_group(self, group): '''add group group to all contacts and all their brothers''' for (contact, account) in self.list_: - tag = gajim.contacts.get_metacontacts_tag(account, contact.jid) - if not tag: - if group not in contact.groups: - contact.groups.append(group) - continue - all_jid = gajim.contacts.get_metacontacts_jids(tag) - for _account in all_jid: - if not gajim.interface.roster.regroup and _account != account: - continue - for _jid in all_jid[_account]: - contacts = gajim.contacts.get_contacts(_account, _jid) - for c in contacts: - if not group in c.groups: - c.groups.append(group) + gajim.interface.roster.add_contact_to_groups(contact.jid, account, [group]) + + # FIXME: Ugly workaround. Maybe we haven't been in any group (defaults to General) + gajim.interface.roster.draw_group(_('General'), account) def on_add_button_clicked(self, widget): group = self.xml.get_widget('group_entry').get_text().decode('utf-8') @@ -166,7 +124,6 @@ class EditGroupsDialog: self.changes_made = True model.append((group, True, False)) self.add_group(group) - self.update_contact() self.init_list() # Re-draw list to sort new item def group_toggled_cb(self, cell, path): @@ -182,7 +139,6 @@ class EditGroupsDialog: self.add_group(group) else: self.remove_group(group) - self.update_contact() def init_list(self): store = gtk.ListStore(str, bool, bool) @@ -200,7 +156,11 @@ class EditGroupsDialog: if g in groups: continue groups[g] = 0 - for g in contact.groups: + c_groups = contact.groups + # FIXME: Move to backend + if not c_groups: + c_groups = [_('General')] + for g in c_groups: groups[g] += 1 group_list = [] # Remove special groups if they are empty diff --git a/src/gajim.py b/src/gajim.py index e6d4e7df4..5e3187510 100755 --- a/src/gajim.py +++ b/src/gajim.py @@ -644,7 +644,6 @@ class Interface: old_show = 0 gajim.contacts.add_contact(account, contact1) lcontact.append(contact1) - self.roster.add_self_contact(account) elif contact1.show in statuss: old_show = statuss.index(contact1.show) if (resources != [''] and (len(lcontact) != 1 or @@ -653,6 +652,9 @@ class Interface: contact1 = gajim.contacts.copy_contact(contact1) lcontact.append(contact1) contact1.resource = resource + # FIXME ugly workaround for self contact + self.roster.add_contact(contact1.jid, account) + if contact1.jid.find('@') > 0 and len(lcontact) == 1: # It's not an agent if old_show == 0 and new_show > 1: @@ -669,8 +671,8 @@ class Interface: if contact1.jid in gajim.newly_added[account]: gajim.newly_added[account].remove(contact1.jid) self.roster.draw_contact(contact1.jid, account) - gobject.timeout_add_seconds(5, self.roster.really_remove_contact, - contact1, account) + gobject.timeout_add_seconds(5, self.roster.remove_to_be_removed, + contact1.jid, account) contact1.show = array[1] contact1.status = status_message contact1.priority = priority @@ -686,6 +688,7 @@ class Interface: # It must be an agent if ji in jid_list: # Update existing iter + self.roster.modelfilter.refilter() self.roster.draw_contact(ji, account) self.roster.draw_group(_('Transports'), account) if new_show > 1 and ji in gajim.transport_avatar[account]: @@ -942,10 +945,10 @@ class Interface: if jid in gajim.contacts.get_jid_list(account): c = gajim.contacts.get_first_contact_from_jid(account, jid) c.resource = array[1] - self.roster.remove_contact(c, account) + self.roster.remove_contact(c.jid, account) if _('Not in Roster') in c.groups: c.groups.remove(_('Not in Roster')) - self.roster.add_contact_to_roster(c.jid, account) + self.roster.add_contact(c.jid, account) else: keyID = '' attached_keys = gajim.config.get_per('accounts', account, @@ -958,7 +961,7 @@ class Interface: groups = [], show = 'online', status = 'online', ask = 'to', resource = array[1], keyID = keyID) gajim.contacts.add_contact(account, contact1) - self.roster.add_contact_to_roster(jid, account) + self.roster.add_contact(jid, account) dialogs.InformationDialog(_('Authorization accepted'), _('The contact "%s" has authorized you to see his or her status.') % jid) @@ -1013,7 +1016,7 @@ class Interface: # This way we'll really remove it gajim.to_be_removed[account].remove(c.jid) gajim.contacts.remove_jid(account, c.jid) - self.roster.remove_contact(c, account) + self.roster.remove_contact(c.jid, account) def handle_event_register_agent_info(self, account, array): # ('REGISTER_AGENT_INFO', account, (agent, infos, is_form)) @@ -1476,7 +1479,7 @@ class Interface: not name and not groups: if contacts: c = contacts[0] - self.roster.remove_contact(c, account) + self.roster.remove_contact(c.jid, account) gajim.contacts.remove_jid(account, jid) self.roster.draw_account(account) if gajim.events.get_events(account, c.jid): @@ -1490,7 +1493,7 @@ class Interface: show = 'not in roster', status = '', sub = 'none', keyID = keyID) gajim.contacts.add_contact(account, contact) - self.roster.add_contact_to_roster(contact.jid, account) + self.roster.add_contact(contact.jid, account) #FIXME if it was the only one in its group, remove the group return elif not contacts: @@ -1500,12 +1503,12 @@ class Interface: contact = gajim.contacts.create_contact(jid = jid, name = name, groups = groups, show = 'offline', sub = sub, ask = ask) gajim.contacts.add_contact(account, contact) - self.roster.add_contact_to_roster(jid, account) + self.roster.add_contact(jid, account) else: re_add = False # if sub changed: remove and re-add, maybe observer status changed if contacts[0].sub != sub: - self.roster.remove_contact(contacts[0], account) + self.roster.remove_contact(contacts[0].jid, account) re_add = True for contact in contacts: if not name: @@ -1516,7 +1519,7 @@ class Interface: if groups: contact.groups = groups if re_add: - self.roster.add_contact_to_roster(jid, account) + self.roster.add_contact(jid, account) self.roster.draw_contact(jid, account) if self.remote_ctrl: self.remote_ctrl.raise_signal('RosterInfo', (account, array)) @@ -1666,7 +1669,7 @@ class Interface: if no_queue: # We didn't have a queue: we change icons if not gajim.contacts.get_contact_with_highest_priority(account, jid): if type_ == 'gc-invitation': - self.roster.add_groupchat_to_roster(account, jid, + self.roster.add_groupchat(account, jid, status='offline') else: # add contact to roster ("Not In The Roster") if he is not @@ -1674,8 +1677,7 @@ class Interface: self.roster.draw_contact(jid, account) # Show contact in roster (if he is invisible for example) and select line - path = self.roster.get_path(jid, account) - self.roster.show_and_select_path(path, jid, account) + self.roster.show_and_select_contact_if_having_events(jid, account) def remove_first_event(self, account, jid, type_ = None): event = gajim.events.get_first_event(account, jid, type_) @@ -1693,7 +1695,7 @@ class Interface: if contact and (contact.show in ('error', 'offline') and \ not gajim.config.get('showoffline') or ( gajim.jid_is_transport(jid) and not show_transport)): - self.roster.really_remove_contact(contact, account) + self.roster.remove_contact(contact.jid, account) self.roster.show_title() self.roster.draw_contact(jid, account) @@ -1739,7 +1741,7 @@ class Interface: groups = [_('Not in Roster')], show = 'not in roster', status = '', sub = 'none', keyID = keyID) gajim.contacts.add_contact(account, contact) - self.roster.add_contact_to_roster(contact.jid, account) + self.roster.add_contact(contact.jid, account) file_props = array[1] contact = gajim.contacts.get_first_contact_from_jid(account, jid) diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 740f9f444..003c06b74 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -1587,7 +1587,7 @@ class GroupchatControl(ChatControlBase): del win._controls[self.account][self.contact.jid] - gajim.interface.roster.add_groupchat_to_roster(self.account, + gajim.interface.roster.add_groupchat(self.account, self.contact.jid, status = self.subject) def shutdown(self, status='offline'): diff --git a/src/roster_window.py b/src/roster_window.py index fda80adfd..0587cf4c3 100644 --- a/src/roster_window.py +++ b/src/roster_window.py @@ -64,8 +64,7 @@ from lastfm_track_listener import LastFMTrackListener if sys.platform == 'darwin': from osx import syncmenu - - + #(icon, name, type, jid, account, editable, second pixbuf) ( C_IMG, # image to show state (online, new message etc) @@ -80,25 +79,41 @@ C_PADLOCK_PIXBUF, # use for account row only class RosterWindow: '''Class for main window of the GTK+ interface''' - def get_account_iter(self, name): - ''' Returns a gtk.TreeIter of accounts in roster data model or None ''' - model = self.tree.get_model() + def _get_account_iter(self, name, model = None): + ''' Return the gtk.TreeIter of the given account or None if not found. + + Keyword arguments: + name -- the account name + model -- the data model (default TreeFilterModel) + + ''' + if not model: + model = self.tree.get_model() if model is None: return account_iter = model.get_iter_root() if self.regroup: return account_iter while account_iter: - account_name = model[account_iter][C_ACCOUNT].decode('utf-8') - if name == account_name: + account_name = model[account_iter][C_ACCOUNT] + if account_name and name == account_name.decode('utf-8'): break account_iter = model.iter_next(account_iter) return account_iter - def get_group_iter(self, name, account): - ''' Returns a gtk.TreeIter of groups in roster data model or None ''' - model = self.tree.get_model() - root = self.get_account_iter(account) + + def _get_group_iter(self, name, account, model = None): + ''' Return the gtk.TreeIter of the given group or None if not found. + + Keyword arguments: + name -- the group name + account -- the account name + model -- the data model (default TreeFilterModel) + + ''' + if not model: + model = self.tree.get_model() + root = self._get_account_iter(account, model) group_iter = model.iter_children(root) # C_NAME column contacts the pango escaped group name while group_iter: @@ -107,16 +122,44 @@ class RosterWindow: break group_iter = model.iter_next(group_iter) return group_iter + - def get_contact_iter(self, jid, account): + def _get_self_contact_iter(self, account, model = None): + ''' Return the gtk.TreeIter of SelfContact or None if not found. + + Keyword arguments: + account -- the account of SelfContact + model -- the data model (default TreeFilterModel) + + ''' + + if not model: + model = self.tree.get_model() + iterAcct = self._get_account_iter(account, model) + iter = model.iter_children(iterAcct) + if iter and model[iter][C_TYPE] == 'self_contact': + return iter + return None + + + def _get_contact_iter(self, jid, account, model = None): + ''' Return a list of gtk.TreeIter of the given jid. + + Keyword arguments: + jid -- the jid without resource + account -- the account + model -- the data model (default TreeFilterModel) + + ''' + if not model: + model = self.tree.get_model() if jid == gajim.get_jid_from_account(account): - iter = self.get_self_contact_iter(account) + iter = self._get_self_contact_iter(account, model) if iter: return [iter] else: return [] - model = self.tree.get_model() - acct = self.get_account_iter(account) + acct = self._get_account_iter(account, model) found = [] if model is None: # when closing Gajim model can be none (async pbs?) return found @@ -124,7 +167,8 @@ class RosterWindow: while group_iter: contact_iter = model.iter_children(group_iter) while contact_iter: - if jid == model[contact_iter][C_JID].decode('utf-8') and \ + iter_jid = model[contact_iter][C_JID] + if iter_jid and jid == iter_jid.decode('utf-8') and \ account == model[contact_iter][C_ACCOUNT].decode('utf-8'): found.append(contact_iter) # find next contact iter @@ -152,74 +196,612 @@ class RosterWindow: contact_iter = next_contact_iter group_iter = model.iter_next(group_iter) return found + - def get_path(self, jid, account): - ''' Try to get line of contact in roster ''' - iters = self.get_contact_iter(jid, account) - if iters: - path = self.tree.get_model().get_path(iters[0]) - else: - path = None - return path + def _iter_is_separator(self, model, iter): + ''' Return True if the given iter is a separator. + + Keyword arguments: + model -- the data model + iter -- the gtk.TreeIter to test + ''' + if model[iter][0] == 'SEPARATOR': + return True + return False - def show_and_select_path(self, path, jid, account): - '''Show contact in roster (if he is invisible for example) - and select line''' - if not path: - # contact is in roster but we curently don't see him online - # show him - self.add_contact_to_roster(jid, account) - iters = self.get_contact_iter(jid, account) - path = self.tree.get_model().get_path(iters[0]) - if self.dragging or not gajim.config.get('scroll_roster_to_last_message'): - # do not change selection while DND'ing + + def _iter_contact_rows(self, model = None): + '''Iterate over all contact rows in given model. + + Keyword arguments: + model -- the data model (default TreeFilterModel) + ''' + if not model: + model = self.tree.get_model() + account_iter = model.get_iter_root() + while account_iter: + group_iter = model.iter_children(account_iter) + while group_iter: + contact_iter = model.iter_children(group_iter) + while contact_iter: + yield model[contact_iter] + contact_iter = model.iter_next(contact_iter) + group_iter = model.iter_next(group_iter) + account_iter = model.iter_next(account_iter) + + +################################################################################ +### Methods for adding and removing roster window items +################################################################################ + + def add_account(self, account): + '''Add account to roster but do nothing if it is already in.''' + # no redraw, add only + if self._get_account_iter(account): + # Will happen on reconnect or for merged accounts return - # popup == False so we show awaiting event in roster - # show and select contact line in roster (even if he is not in roster) - self.tree.expand_row(path[0:1], False) - self.tree.expand_row(path[0:2], False) - self.tree.scroll_to_cell(path) - self.tree.set_cursor(path) - - def add_account_to_roster(self, account): - ''' Add an account to roster data model. ''' - model = self.tree.get_model() - if self.get_account_iter(account): - return - - # if we merge accounts... + if self.regroup: + # Merged accounts view show = helpers.get_global_show() - model.append(None, [gajim.interface.jabber_state_images['16'][show], + self.model.append(None, [gajim.interface.jabber_state_images['16'][show], _('Merged accounts'), 'account', '', 'all', None, None]) - self.draw_account(account) + else: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + our_jid = gajim.get_jid_from_account(account) + + tls_pixbuf = None + if gajim.account_is_securely_connected(account): + tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, + gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock + + self.model.append(None, [gajim.interface.jabber_state_images['16'][show], + gobject.markup_escape_text(account), + 'account', our_jid, account, None, tls_pixbuf]) + + + def add_account_contacts(self, account): + '''Add all contacts and groups of the given account to roster + and draw them. + ''' + c1 = time.clock() + jids = gajim.contacts.get_jid_list(account) + + self.starting = True # don't draw contacts + self.tree.freeze_child_notify() + c5 = time.clock() + for jid in jids: + self.add_contact(jid, account) + c6 = time.clock() + self.tree.thaw_child_notify() + self.starting = False + + c9 = time.clock() + # Draw all known groups + for group in gajim.groups[account].keys(): + self.draw_group(group, account) + self.draw_account(account) + c10 = time.clock() + + # Do not freeze the GUI when drawing the contacts + if jids: + # Overhead is big, only invoke when needed + self._idle_draw_jids_of_account(jids, account) + + c4 = time.clock() + + print "" + print "--- Add account contacts -----------------------------" + print "Total Time", c4-c1 + print "Add contact without draw", c6-c5 + print "Draw groups and account", c10-c9 + print "--- contacts added -----------------------------" + print "" + + + def _add_entity(self, contact, account, groups = None, big_brother_contact = None): + '''Add the given contact to roster data model. + + Contact is added regardless if he is already in roster or not. + Return list of newly added iters. + + Keyword arguments: + contact -- the contact to add + account -- the contacts account + groups -- list of groups to add the contact to. (default groups in contact.groups). + Parameter ignored when big_brother_contact is specified. + big_brother_contact -- if specified contact is added as child big_brother_contact. (default None) + ''' + added_iters = [] + if big_brother_contact: + # Add contact under big brother + + parent_iters = self._get_contact_iter(big_brother_contact.jid, + account, self.model) + assert len(parent_iters) > 0,\ + "Big brother is not yet in roster!" + + for child_iter in parent_iters: + it = self.model.append(child_iter, (None, contact.get_shown_name(), + 'contact', contact.jid, account, None, None)) + added_iters.append(it) + + else: + # We are a normal contact. Add us to our groups. + if not groups: + if contact.is_transport(): + contact.groups = [_('Transports')] + if contact.is_observer(): + contact.groups = [_('Observers')] + groups = contact.groups + if not groups: + groups = [_('General')] + for group in groups: + child_iterG = self._get_group_iter(group, account, 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]) + + if contact.is_transport(): + typestr = 'agent' + elif contact.is_groupchat(): + typestr = 'groupchat' + else: + typestr = 'contact' + + # we add some values here. see draw_contact for more + i_ = self.model.append(child_iterG, (None, contact.get_shown_name(), + typestr, contact.jid, account, None, None)) + added_iters.append(i_) + + assert len(added_iters), "%s has not been added to roster!" % contact.jid + return added_iters + + + def _remove_entity(self, contact, account, groups = None): + '''Remove the given contact from roster data model. + + Empty groups after contact removal are removed too. + Return False if contact still has children and deletion was not performed. + Return True on success. + + Keyword arguments: + contact -- the contact to add + account -- the contacts account + groups -- list of groups to remove the contact from. (default groups in contact.groups). + + ''' + iters = self._get_contact_iter(contact.jid, account, self.model) + assert iters, "%s shall be removed but is not in roster" % contact.jid + + parent_iter = self.model.iter_parent(iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + + if groups: + # Only remove from specified groups + all_iters = iters[:] + group_iters = [self._get_group_iter(group, account) for group in groups] + iters = [iter for iter in all_iters if self.model.iter_parent(iter) in group_iters] + + iter_children = self.model.iter_children(iters[0]) + + if iter_children: + # We have children. We cannot be removed! + return False + else: + # Remove us and empty groups from the model + for i in iters: + parent_i = self.model.iter_parent(i) + self.model.remove(i) + + if parent_type == 'group' and \ + self.model.iter_n_children(parent_i) == 0: + group = self.model[parent_i][C_JID].decode('utf-8') + if gajim.groups[account].has_key(group): + del gajim.groups[account][group] + self.model.remove(parent_i) + return True + + + def _add_metacontact_family(self, family): + '''Add the give Metacontact family to roster data model. + + Add Big Brother to his groups and all others under him. + Return list of all added (contact, account) tuples with Big Brother + as first element. + + Keyword arguments: + family -- the family, see Contacts.get_metacontacts_family() + ''' + big_brother_data = gajim.contacts.get_metacontacts_big_brother(family) + big_brother_jid = big_brother_data['jid'] + big_brother_account = big_brother_data['account'] + big_brother_contact = gajim.contacts.get_first_contact_from_jid(big_brother_account, big_brother_jid) + + assert len(self._get_contact_iter(big_brother_jid, big_brother_account, self.model)) == 0,\ + "Big brother %s already in roster" % big_brother_jid + self._add_entity(big_brother_contact, big_brother_account) + + brothers = [] + for data in family: + if data == big_brother_data: + continue # already added + + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) + + if not _contact: + # Corresponding account is not connected + continue + + assert len(self._get_contact_iter(_jid, _account, self.model)) == 0,\ + "%s already in roster" % _jid + self._add_entity(_contact, _account, big_brother_contact = big_brother_contact) + brothers.append((_contact, _account)) + + brothers.insert(0, (big_brother_contact, big_brother_account)) + return brothers + + + def _remove_metacontact_family(self, family): + '''Remove the give Metacontact family from roster data model. + + See Contacts.get_metacontacts_family() and RosterWindow._remove_entity() + ''' + # Family might has changed (actual big brother not on top). + # Remove in correct order: Childs first + for data in family: + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) + + if not _contact: + # Corresponding account is not online + continue + + iters = self._get_contact_iter(_jid, _account, self.model) + assert iters, "%s shall be removed but is not in roster" % _jid + + parent_iter = self.model.iter_parent(iters[0]) + parent_type = self.model[parent_iter][C_TYPE] + + if parent_type != 'contact': + # The contact on top + old_big_account = _account + old_big_contact = _contact + old_big_jid = _jid + continue + + ok = self._remove_entity(_contact, _account) + assert ok, "%s was not removed" % _jid + assert len(self._get_contact_iter(_jid, _account, self.model)) == 0,\ + "%s is removed but still in roster" % _jid + + iters = self._get_contact_iter(old_big_jid, old_big_account, self.model) + assert len(iters) > 0, "Old Big Brother %s is not in roster anymore" % old_big_jid + assert not self.model.iter_children(iters[0]),\ + "Old Big Brother still has children" % old_big_jid + + # This one is strange but necessary: + # Refilter filtered model to not crash hard. It thinks it still has children. + self.refilter_shown_roster_items() + + ok = self._remove_entity(old_big_contact, old_big_account) + assert ok, "Old Big Brother %s not removed" % old_big_jid + assert len(self._get_contact_iter(old_big_jid, old_big_account, self.model)) == 0,\ + "Old Big Brother %s is removed but still in roster" % old_big_jid + + return True + + + def _add_self_contact(self, account): + '''Add account's SelfContact to roster and draw it and the account. + + Return the SelfContact contact instance + ''' + jid = gajim.get_jid_from_account(account) + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + + assert len(self._get_contact_iter(jid, account, self.model)) == 0,\ + "Self contact %s already in roster" % jid + + child_iterA = self._get_account_iter(account, self.model) + self.model.append(child_iterA, (None, gajim.nicks[account], + 'self_contact', jid, account, None, None)) + + self.draw_contact(jid, account) + self.draw_avatar(jid, account) + self.draw_account(account) + + return contact + + + def add_contact(self, jid, account): + '''Add contact to roster and draw him. + + Add contact to all its group and redraw the groups, the contact and the + account. If it's a Metacontact, add and draw the whole family. + Do nothing if the contact is already in roster. + + Return the added contact instance. If it is a Metacontact return + Big Brother. + + Keyword arguments: + jid -- the contact's jid or SelfJid to add SelfContact + account -- the corresponding account. + + ''' + if len(self._get_contact_iter(jid, account, self.model)): + # If contact already in roster, do nothing return - show = gajim.SHOW_LIST[gajim.connections[account].connected] + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - tls_pixbuf = None - if gajim.account_is_securely_connected(account): - tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, - gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock + if jid == gajim.get_jid_from_account(account): + if contact.resource != gajim.connections[account].server_resource: + return self._add_self_contact(account) + #FIXME When does this happen? + assert False + return + + if contact.is_observer(): + # if he has a tag, remove it + tag = gajim.contacts.get_metacontacts_tag(account, jid) + if tag: + gajim.contacts.remove_metacontact(account, jid) + + # Add contact to roster + family = gajim.contacts.get_metacontacts_family(account, jid) + contacts = [] + if family: + # We have a family. So we are a metacontact. + # Add our whole family + contacts = self._add_metacontact_family(family) + else: + # We are a normal contact + contacts = [(contact, account),] + self._add_entity(contact, account) - our_jid = gajim.get_jid_from_account(account) + # Draw all groups of the contact + if contact.is_transport(): + contact.groups = [_('Transports')] + if contact.is_observer(): + contact.groups = [_('Observers')] + groups = contact.groups + if not groups: + groups = [_('General')] + for group in groups: + # Restore the group expand state + if group not in gajim.groups[account]: + #if account + group in self.collapsed_rows: + #ishidden = False + #else: + ishidden = True + gajim.groups[account][group] = {'expand': ishidden} - model.append(None, [gajim.interface.jabber_state_images['16'][show], - gobject.markup_escape_text(account), - 'account', our_jid, account, None, tls_pixbuf]) + #if not self.starting: + self.draw_group(group, account) + + if not self.starting: + for c, acc in contacts: + self.draw_contact(c.jid, acc) + self.draw_avatar(c.jid, acc) + self.draw_account(account) + + return contacts[0][0] # it's contact/big brother with highest priority + + + def remove_contact(self, jid, account): + '''Remove contact from roster. + + Remove contact from all its group. Remove empty groups or redraw otherwise. + Draw the account. + If it's a Metacontact, remove the whole family. + Do nothing if the contact is not in roster. + + Keyword arguments: + jid -- the contact's jid or SelfJid to remove SelfContact + account -- the corresponding account. + + ''' + iters = self._get_contact_iter(jid, account, self.model) + if not iters: + return + + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + + # Remove contact from roster + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: + # We have a family. So we are a metacontact. + self._remove_metacontact_family(family) + else: + self._remove_entity(contact, account) + + # Draw all groups of the contact + groups = contact.groups + if contact.is_transport(): + contact.groups = [_('Transports')] + if contact.is_observer(): + contact.groups = [_('Observers')] + if not groups: + groups = [_('General')] + for group in groups: + self.draw_group(group, account) + + self.draw_account(account) + + return True + + + def add_groupchat(self, account, jid, resource = '', status = ''): + '''Add groupchat to roster and draw it. + Return the added contact instance. + ''' + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if contact == None: + contact = gajim.contacts.create_contact(jid = jid, name = jid, + groups = [_('Groupchats')], show = 'online', + status = status, sub = 'none', + resource = resource) + gajim.contacts.add_contact(account, contact) + else: + contact.show = 'online' + self.add_contact(jid, account) + return contact + + + def remove_groupchat(self, jid, account): + '''Remove groupchat from roster and redraw account and group.''' + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + self.remove_contact(jid, account) + gajim.contacts.remove_contact(account, contact) + #FIXME: stupid hack. + # When we redraw the group in remove_contact the + # contact does still exist and so the group is still showing + # the old numbers. + # Maybe use timeout to draw groups so that there is enough time + # to remove the contact instance. + self.draw_group(_('Groupchats'), account) + return True + + + def add_transport(self, account, transport): + '''Add transport to roster and draw it. + Return the added contact instance.''' + contact = gajim.contacts.create_contact(jid = transport, name = transport, + groups = [_('Transports')], show = 'offline', + status = 'offline', sub = 'from') + gajim.contacts.add_contact(account, contact) + self.add_contact(transport, account) + return contact + + # FIXME: remove_transport method is missing + + #FIXME: Better never even remove.... + def _readd_contact_to_roster_if_needed(self, contact, account): + # FIXME What am I REALLY needed for + need_readd = False + if len(gajim.events.get_events(account, contact.jid)): + need_readd = True + elif gajim.interface.msg_win_mgr.has_window(contact.jid, account): + if _('Not in Roster') in contact.groups: + # Close chat window + msg_win = gajim.interface.msg_win_mgr.get_window(contact.jid, + account) + ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) + msg_win.remove_tab(ctrl, msg_win.CLOSE_CLOSE_BUTTON) + else: + need_readd = True + if need_readd: + c = gajim.contacts.create_contact(jid = contact.jid, + name = '', groups = [_('Not in Roster')], + show = 'not in roster', status = '', ask = 'none', + keyID = contact.keyID) + gajim.contacts.add_contact(account, c) + self.add_contact(contact.jid, account) + + + def add_contact_to_groups(self, jid, account, groups): + '''Add contact to given groups and redraw them. + + Contact on server is updated too. When the contact has a family, + the action will be performed for all members. + + Keyword Arguments: + jid -- the jid + account -- the corresponding account + groups -- list of Groups to add the contact too. + + ''' + self.remove_contact(jid, account) + + for contact in gajim.contacts.get_contacts(account, jid): + for group in groups: + if group not in contact.groups: + # statement needed for drap from meta to group + contact.groups.append(group) + gajim.connections[account].update_contact(jid, contact.name, + contact.groups) + + self.add_contact(jid, account) + + + def remove_contact_from_groups(self, jid, account, groups): + '''Remove contact from given groups and redraw them. + + Contact on server is updated too. When the contact has a family, + the action will be performed for all members. + + Keyword Arguments: + jid -- the jid + account -- the corresponding account + groups -- list of Groups to remove the contact from + + ''' + self.remove_contact(jid, account) + + for contact in gajim.contacts.get_contacts(account, jid): + for group in groups: + if group in contact.groups: + # Needed when we remove from "General" + contact.groups.remove(group) + gajim.connections[account].update_contact(jid, contact.name, + contact.groups) + + self.add_contact(jid, account) + + for group in groups: + self.draw_group(group, account) + + + # FIXME: maybe move to gajim.py + def remove_newly_added(self, jid, account): + if jid in gajim.newly_added[account]: + gajim.newly_added[account].remove(jid) + self.draw_contact(jid, account) + + + # FIXME: maybe move to gajim.py + def remove_to_be_removed(self, jid, account): + if not gajim.interface.instances.has_key(account): + # Account has been deleted during the timeout that called us + return + if jid in gajim.newly_added[account]: + return + if jid in gajim.to_be_removed[account]: + gajim.to_be_removed[account].remove(jid) + self.draw_contact(jid, account) + self.refilter_shown_roster_items() + + + #FIXME: integrate into add_contact() + def add_to_not_in_the_roster(self, account, jid, nick = '', resource = ''): + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_contact(jid = jid, name = nick, + groups = [_('Not in Roster')], show = 'not in roster', status = '', + sub = 'none', resource = resource, keyID = keyID) + gajim.contacts.add_contact(account, contact) + self.add_contact(contact.jid, account) + return contact + + +################################################################################ +### Methods for adding and removing roster window items +################################################################################ def draw_account(self, account): - if account in self.draw_account_id: - return - self.draw_account_id[account] = gobject.timeout_add(500, - self.really_draw_account, account) + child_iter = self._get_account_iter(account, self.model) - def really_draw_account(self, account): - if account in self.draw_account_id: - del self.draw_account_id[account] - model = self.tree.get_model() - iter = self.get_account_iter(account) - num_of_accounts = gajim.get_number_of_connected_accounts() num_of_secured = gajim.get_number_of_securely_connected_accounts() @@ -227,17 +809,17 @@ class RosterWindow: self.regroup and num_of_secured and num_of_secured == num_of_accounts: tls_pixbuf = self.window.render_icon(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_MENU) # the only way to create a pixbuf from stock - model[iter][C_PADLOCK_PIXBUF] = tls_pixbuf + self.model[child_iter][C_PADLOCK_PIXBUF] = tls_pixbuf else: - model[iter][C_PADLOCK_PIXBUF] = None - path = model.get_path(iter) + self.model[child_iter][C_PADLOCK_PIXBUF] = None + path = self.model.get_path(child_iter) account_name = account accounts = [account] if self.regroup: account_name = _('Merged accounts') accounts = [] - if not self.tree.row_expanded(path) and model.iter_has_child(iter): - # account row not expanded + iter = self._get_account_iter(account) + if not self.tree.row_expanded(path): account_name = '[%s]' % account_name if (gajim.account_is_connected(account) or (self.regroup and \ gajim.get_number_of_connected_accounts())) and gajim.config.get( @@ -245,176 +827,18 @@ class RosterWindow: nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( accounts = accounts) account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - model[iter][C_NAME] = account_name - - def remove_newly_added(self, jid, account): - if jid in gajim.newly_added[account]: - gajim.newly_added[account].remove(jid) - self.draw_contact(jid, account) - - def add_contact_to_roster(self, jid, account): - '''Add a contact to the roster and add groups if they aren't in roster - force is about force to add it, even if it is offline and show offline - is False, because it has online children, so we need to show it. - If add_children is True, we also add all children, even if they were not - already drawn''' - showOffline = gajim.config.get('showoffline') - model = self.tree.get_model() - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - return - nb_events = gajim.events.get_nb_roster_events(account, contact.jid) - # count events from all resources - for contact_ in gajim.contacts.get_contacts(account, jid): - if contact_.resource: - nb_events += gajim.events.get_nb_roster_events(account, - contact_.get_full_jid()) - # If contact already in roster, do not add it - if len(self.get_contact_iter(jid, account)): - return - if jid == gajim.get_jid_from_account(account): - if contact.resource != gajim.connections[account].server_resource: - self.add_self_contact(account) - return - if gajim.jid_is_transport(contact.jid): - # if jid is transport, check if we wanna show it in roster - if not gajim.config.get('show_transports_group') and not nb_events: - return - contact.groups = [_('Transports')] - elif not showOffline and not gajim.account_is_connected(account) and \ - nb_events == 0: - return - - # XEP-0162 - hide = contact.is_hidden_from_roster() - if hide and contact.sub != 'from': - return - observer = contact.is_observer() - - if observer: - # if he has a tag, remove it - tag = gajim.contacts.get_metacontacts_tag(account, jid) - if tag: - gajim.contacts.remove_metacontact(account, jid) - - # family is [{'account': acct, 'jid': jid, 'priority': prio}, ] - # 'priority' is optional - family = gajim.contacts.get_metacontacts_family(account, jid) - - # family members that are in roster and belong to the same account. - shown_family = [] - if family: - for data in family: - _account = data['account'] - # Metacontacts over different accounts only in merged mode - if _account != account and not self.regroup: - continue - _jid = data['jid'] - - if self.get_contact_iter(_jid, _account): - shown_family.append(data) - if _jid == jid and _account == account: - our_data = data - shown_family.append(our_data) - big_brother_data = gajim.contacts.get_metacontacts_big_brother( - shown_family) - big_brother_jid = big_brother_data['jid'] - big_brother_account = big_brother_data['account'] - if big_brother_jid != jid or big_brother_account != account: - # We are adding a child contact - if contact.show in ('offline', 'error') and \ - not showOffline and len(gajim.events.get_events(account, jid)) == 0: - return - parent_iters = self.get_contact_iter(big_brother_jid, - big_brother_account) - name = contact.get_shown_name() - for i in parent_iters: - # we add some values here. see draw_contact for more - model.append(i, (None, name, 'contact', jid, account, None, - None)) - self.draw_contact(jid, account) - self.draw_avatar(jid, account) - self.draw_account(account) - # Redraw parent to change icon - self.draw_contact(big_brother_jid, big_brother_account) - return - - if (contact.show in ('offline', 'error') or hide) and \ - not showOffline and (not _('Transports') in contact.groups or \ - gajim.connections[account].connected < 2) and \ - len(gajim.contacts.get_contacts(account, jid)) == 1 and nb_events == 0 and\ - not _('Not in Roster') in contact.groups: - return - - # Remove brother contacts that are already in roster to add them - # under this iter - for data in shown_family: - contacts = gajim.contacts.get_contacts(data['account'], - data['jid']) - for c in contacts: - self.remove_contact(c, data['account']) - groups = contact.groups - if observer: - groups = [_('Observers')] - elif not groups: - groups = [_('General')] - for group in groups: - self.draw_group(group, account) - iterG = self.get_group_iter(group, account) - if not iterG: - IterAcct = self.get_account_iter(account) - iterG = model.append(IterAcct, [ - gajim.interface.jabber_state_images['16']['closed'], - gobject.markup_escape_text(group), 'group', - group, account, None, None]) - self.draw_group(group, account) - if model.iter_n_children(IterAcct) == 1: # We added the first one - self.draw_account(account) - if group not in gajim.groups[account]: # It can probably never append - if account + group in self.collapsed_rows: - ishidden = False - else: - ishidden = True - gajim.groups[account][group] = {'expand': ishidden} - if not account in self.collapsed_rows: - self.tree.expand_row((model.get_path(iterG)[0]), False) - - typestr = 'contact' - if group == _('Transports'): - typestr = 'agent' - if gajim.gc_connected[account].has_key(jid): - typestr = 'groupchat' - - name = contact.get_shown_name() - # we add some values here. see draw_contact for more - model.append(iterG, (None, name, typestr, contact.jid, account, None, - None)) - - if gajim.groups[account][group]['expand']: - self.tree.expand_row(model.get_path(iterG), False) - self.draw_contact(jid, account) - self.draw_avatar(jid, account) - self.draw_account(account) - # put the children under this iter - for data in shown_family: - contacts = gajim.contacts.get_contacts(data['account'], - data['jid']) - self.add_contact_to_roster(data['jid'], data['account']) - + self.model[child_iter][C_NAME] = account_name + return False + def draw_group(self, group, account): - key = (group, account) - if key in self.draw_group_id: + child_iter = self._get_group_iter(group, account, self.model) + if not child_iter: + # Eg. We redraw groups after we removed a entitiy + # and its empty groups return - self.draw_group_id[key] = gobject.timeout_add(500, - self.really_draw_group, group, account) - - def really_draw_group(self, group, account): key = (group, account) if key in self.draw_group_id: del self.draw_group_id[key] - iter = self.get_group_iter(group, account) - if not iter: - return if self.regroup: accounts = [] else: @@ -426,201 +850,35 @@ class RosterWindow: nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( accounts = accounts, groups = [group]) text += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - model = self.tree.get_model() - model.set_value(iter, 1 , text) - - def add_to_not_in_the_roster(self, account, jid, nick = '', resource = ''): - ''' add jid to group "not in the roster", he MUST not be in roster yet, - return contact ''' - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid = jid, name = nick, - groups = [_('Not in Roster')], show = 'not in roster', status = '', - sub = 'none', resource = resource, keyID = keyID) - gajim.contacts.add_contact(account, contact) - self.add_contact_to_roster(contact.jid, account) - return contact - - def add_groupchat_to_roster(self, account, jid, resource = '', status = ''): - ''' add groupchat to roster ''' - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if contact == None: - contact = gajim.contacts.create_contact(jid = jid, name = jid, - groups = [_('Groupchats')], show = 'online', - status = status, sub = 'none', - resource = resource) - gajim.contacts.add_contact(account, contact) - self.add_contact_to_roster(jid, account) - self.draw_group(_('Groupchats'), account) - else: - contact.show = 'online' - self.draw_contact(jid, account) - self.add_contact_to_roster(jid, account) - for group in contact.groups: - self.draw_group(group, account) - self.draw_account(account) - return contact - - def get_self_contact_iter(self, account): - model = self.tree.get_model() - iterAcct = self.get_account_iter(account) - iter = model.iter_children(iterAcct) - if not iter: - return None - if model[iter][C_TYPE] == 'self_contact': - return iter - return None - - def add_self_contact(self, account): - jid = gajim.get_jid_from_account(account) - if self.get_self_contact_iter(account): - self.draw_contact(jid, account) - self.draw_avatar(jid, account) + + self.model.set_value(child_iter, 1 , gobject.markup_escape_text(text)) + return False + + def draw_parent_contact(self, jid, account): + child_iters = self._get_contact_iter(jid, account, self.model) + parent_iter = self.model.iter_parent(child_iters[0]) + if self.model[parent_iter][C_TYPE] != 'contact': + # parent is not a contact return - - contact = gajim.contacts.get_first_contact_from_jid(account, jid) - if not contact: - return - showOffline = gajim.config.get('showoffline') - if (contact.show in ('offline', 'error')) and not showOffline and \ - len(gajim.events.get_events(account, jid)) == 0: - return - - model = self.tree.get_model() - iterAcct = self.get_account_iter(account) - model.append(iterAcct, (None, gajim.nicks[account], 'self_contact', jid, - account, None, None)) - self.draw_contact(jid, account) - self.draw_avatar(jid, account) - - def add_transport_to_roster(self, account, transport): - c = gajim.contacts.create_contact(jid = transport, name = transport, - groups = [_('Transports')], show = 'offline', status = 'offline', - sub = 'from') - gajim.contacts.add_contact(account, c) - self.add_contact_to_roster(transport, account) - - def really_remove_contact(self, contact, account): - if not gajim.interface.instances.has_key(account): - # Account has been deleted during the timeout that called us - return - if contact.jid in gajim.newly_added[account]: - return - if gajim.jid_is_transport(contact.jid) and gajim.account_is_connected( - account) and gajim.config.get('show_transports_group'): - # It's an agent and we show them - return - if contact.jid in gajim.to_be_removed[account]: - gajim.to_be_removed[account].remove(contact.jid) - - - hide = contact.is_hidden_from_roster() - - show_offline = gajim.config.get('showoffline') - show_transports = gajim.config.get('show_transports_group') - - nb_events = 0 - jid_list = [contact.jid] - if contact.get_full_jid() != contact.jid: - jid_list.append(contact.get_full_jid()) - for jid in jid_list: - # dont't count printed_chat messages - nb_events += gajim.events.get_nb_roster_events(account, jid, ['chat']) - - if (_('Transports') in contact.groups and not show_transports) or \ - ((contact.show in ('offline', 'error') or hide) and not show_offline and \ - (not _('Transports') in contact.groups or \ - gajim.account_is_disconnected(account))) and nb_events == 0: - self.remove_contact(contact, account) - else: - # If it's a metacontact, big brother may have changed, so remove and - # re-add - model = self.tree.get_model() - iters = self.get_contact_iter(contact.jid, account) - if iters and model.iter_has_child(iters[0]): - self.remove_contact(contact, account) - self.add_contact_to_roster(contact.jid, account) - else: - self.draw_contact(contact.jid, account) - - def remove_contact(self, contact, account): - '''Remove a contact from the roster''' - if contact.jid in gajim.to_be_removed[account]: - return - model = self.tree.get_model() - iters = self.get_contact_iter(contact.jid, account) - if not iters: - return - parent_iter = model.iter_parent(iters[0]) - parent_type = model[parent_iter][C_TYPE] - # remember children to re-add them - children = [] - child_iter = model.iter_children(iters[0]) - while child_iter: - c_jid = model[child_iter][C_JID].decode('utf-8') - c_account = model[child_iter][C_ACCOUNT].decode('utf-8') - children.append((c_jid, c_account)) - child_iter = model.iter_next(child_iter) - - # Remove iters and group iter if they are empty - for i in iters: - parent_i = model.iter_parent(i) - model.remove(i) - if parent_type == 'group': - group = model[parent_i][C_JID].decode('utf-8') - if model.iter_n_children(parent_i) == 0: - model.remove(parent_i) - # We need to check all contacts, even offline contacts - for jid in gajim.contacts.get_jid_list(account): - if group in gajim.contacts.get_contact_with_highest_priority( - account, jid).groups: - break - else: - if gajim.groups[account].has_key(group): - del gajim.groups[account][group] - else: - self.draw_group(group, account) - - # re-add children - for child in children: - self.add_contact_to_roster(child[0], child[1]) - # redraw parent - if parent_type == 'contact': - parent_jid = model[parent_iter][C_JID].decode('utf-8') - parent_account = model[parent_iter][C_ACCOUNT].decode('utf-8') - self.draw_contact(parent_jid, parent_account) - - def get_appropriate_state_images(self, jid, size = '16', - icon_name = 'online'): - '''check jid and return the appropriate state images dict for - the demanded size. icon_name is taken into account when jid is from - transport: transport iconset doesn't contain all icons, so we fall back - to jabber one''' - transport = gajim.get_transport_name_from_jid(jid) - if transport and self.transports_state_images.has_key(size): - if not self.transports_state_images[size].has_key(transport): - # we don't have iconset for this transport loaded yet. Let's do it - self.make_transport_state_images(transport) - if self.transports_state_images[size].has_key(transport) and \ - icon_name in self.transports_state_images[size][transport]: - return self.transports_state_images[size][transport] - return gajim.interface.jabber_state_images[size] - + parent_jid = self.model[parent_iter][C_JID].decode('utf-8') + parent_account = self.model[parent_iter][C_ACCOUNT].decode('utf-8') + self.draw_contact(parent_jid, parent_account) + def draw_contact(self, jid, account, selected = False, focus = False): '''draw the correct state image, name BUT not avatar''' # focus is about if the roster window has toplevel-focus or not - model = self.tree.get_model() - iters = self.get_contact_iter(jid, account) - if len(iters) == 0: - return + + # FIXME Remove functionality but check before if all cases + # are handled by GTK + selected = False + + child_iters = self._get_contact_iter(jid, account, self.model) + if not child_iters: + return False + contact_instances = gajim.contacts.get_contacts(account, jid) contact = gajim.contacts.get_highest_prio_contact_from_contacts( contact_instances) - if not contact: - return name = gobject.markup_escape_text(contact.get_shown_name()) # gets number of unread gc marked messages @@ -634,7 +892,8 @@ class RosterWindow: name = '%s *' % name elif nb_unread > 1: name = '%s [%s]' % (name, str(nb_unread)) - + + # Strike name if blocked strike = False if jid in gajim.connections[account].blocked_contacts: strike = True @@ -651,6 +910,7 @@ class RosterWindow: if strike: name = '%s' % name + # Show resource counter nb_connected_contact = 0 for c in contact_instances: if c.show not in ('error', 'offline'): @@ -662,16 +922,15 @@ class RosterWindow: if self.regroup: add_acct = False # look through all contacts of all accounts - for account_iter in gajim.connections: - if account_iter == account: # useless to add accout name + for account_ in gajim.connections: + if account_ == account: # useless to add accout name continue - for jid_iter in gajim.contacts.get_jid_list(account_iter): + for jid_ in gajim.contacts.get_jid_list(account_): # [0] cause it'fster than highest_prio - contact_iter = gajim.contacts.\ - get_first_contact_from_jid(account_iter, jid_iter) - if contact_iter.get_shown_name() == \ - contact.get_shown_name() and\ - (jid_iter, account_iter) != (jid, account): + contact_ = gajim.contacts.get_first_contact_from_jid(account_, + jid_) + if contact_.get_shown_name() == contact.get_shown_name() and \ + (jid_, account_) != (jid, account): add_acct = True break if add_acct: @@ -691,12 +950,34 @@ class RosterWindow: name += \ '\n%s' \ % (colorstring, gobject.markup_escape_text(status)) + + # Check if our metacontacts familiy has changed + brothers = [] + family = gajim.contacts.get_metacontacts_family(account, jid) + if family: # Are we a metacontact (have a familiy) + big_brother_data = gajim.contacts.get_metacontacts_big_brother(family) + big_brother_jid = big_brother_data['jid'] + big_brother_account = big_brother_data['account'] - iter = iters[0] # choose the icon with the first iter - - if gajim.gc_connected[account].has_key(jid): - contact.show = 'online' - model[iter][C_TYPE] = 'groupchat' + if big_brother_jid == jid and big_brother_account == account: + # We are the big brother. But have also been before? + iters = self._get_contact_iter(jid, account, self.model) + if self.model.iter_has_child(iters[0]): + # We have children. We must have been + # big brother even before + pass + else: + # We are the new big brother but haven't been before + # Remove us and all our brothers and then re-add us so that + self._remove_metacontact_family(family) + brothers = self._add_metacontact_family(family) + big_brother_c, big_brother_acc = brothers[0] + brothers = brothers[1:] + self.draw_avatar(big_brother_c.jid, big_brother_acc) + else: + # We are a simple brother + # Let our big brother know of our existence (and possible events) + self.draw_contact(big_brother_jid, big_brother_account) icon_name = helpers.get_icon_name_to_show(contact, account) # look if another resource has awaiting events @@ -705,29 +986,36 @@ class RosterWindow: if c_icon_name in ('event', 'muc_active', 'muc_inactive'): icon_name = c_icon_name break - path = model.get_path(iter) - if model.iter_has_child(iter): + + iters = self._get_contact_iter(jid, account) + if iters and self.modelfilter.iter_has_child(iters[0]): + # We are big brother contact and visible in the roster + iter = iters[0] + path = self.modelfilter.get_path(iter) + if not self.tree.row_expanded(path) and \ icon_name not in ('event', 'muc_active', 'muc_inactive'): - child_iter = model.iter_children(iter) + + iterC = self.modelfilter.iter_children(iter) if icon_name in ('error', 'offline'): # get the icon from the first child as they are sorted by show - child_jid = model[child_iter][C_JID].decode('utf-8') - child_account = model[child_iter][C_ACCOUNT].decode('utf-8') - child_contact = gajim.contacts.get_contact_with_highest_priority( - child_account, child_jid) - child_icon_name = helpers.get_icon_name_to_show(child_contact, - child_account) - if child_icon_name not in ('error', 'not in roster'): - icon_name = child_icon_name - while child_iter: + jidC = self.modelfilter[iterC][C_JID].decode('utf-8') + accountC = self.modelfilter[iterC][C_ACCOUNT].decode( + 'utf-8') + contactC = gajim.contacts.get_contact_with_highest_priority( + accountC, jidC) + icon_nameC = helpers.get_icon_name_to_show(contactC, accountC) + if icon_nameC not in ('error', 'not in roster'): + icon_name = icon_nameC + while iterC: # a child has awaiting messages ? - child_jid = model[child_iter][C_JID].decode('utf-8') - child_account = model[child_iter][C_ACCOUNT].decode('utf-8') - if len(gajim.events.get_events(child_account, child_jid)): + jidC = self.modelfilter[iterC][C_JID].decode('utf-8') + accountC = self.modelfilter[iterC][C_ACCOUNT].decode('utf-8') + if len(gajim.events.get_events(accountC, jidC)): icon_name = 'event' break - child_iter = model.iter_next(child_iter) + iterC = self.modelfilter.iter_next(iterC) + if self.tree.row_expanded(path): state_images = self.get_appropriate_state_images(jid, size = 'opened', icon_name = icon_name) @@ -735,47 +1023,815 @@ class RosterWindow: state_images = self.get_appropriate_state_images(jid, size = 'closed', icon_name = icon_name) else: - # redraw parent - self.draw_parent_contact(jid, account) + # We might be a normal contact or a little brother + #self.draw_parent_contact(jid, account) # simply call, it's save state_images = self.get_appropriate_state_images(jid, icon_name = icon_name) img = state_images[icon_name] + child_iters = self._get_contact_iter(jid, account, self.model) + for child_iter in child_iters: + self.model[child_iter][C_IMG] = img + self.model[child_iter][C_NAME] = name - for iter in iters: - model[iter][C_IMG] = img - model[iter][C_NAME] = name + for c, acc in brothers: + self.draw_contact(c.jid, acc) + self.draw_avatar(c.jid, acc) + return False - def draw_parent_contact(self, jid, account): - model = self.tree.get_model() - iters = self.get_contact_iter(jid, account) - if not len(iters): - return - parent_iter = model.iter_parent(iters[0]) - if model[parent_iter][C_TYPE] != 'contact': - # parent is not a contact - return - parent_jid = model[parent_iter][C_JID].decode('utf-8') - parent_account = model[parent_iter][C_ACCOUNT].decode('utf-8') - self.draw_contact(parent_jid, parent_account) def draw_avatar(self, jid, account): - '''draw the avatar''' + iters = self._get_contact_iter(jid, account, self.model) + self.draw_avatar_from_iters(iters) + + def draw_avatar_from_iters(self, iters): + '''draw the avatar + iters are iters in treemodel, not treemodelfilter''' if not gajim.config.get('show_avatars_in_roster'): return - model = self.tree.get_model() - iters = self.get_contact_iter(jid, account) - if gajim.config.get('show_avatars_in_roster'): - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) - if pixbuf in ('ask', None): - scaled_pixbuf = None - else: - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') - else: + jid = self.model[iters[0]][C_JID] + jid = jid.decode('utf-8') + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(jid) + if pixbuf is None or pixbuf == 'ask': scaled_pixbuf = None - for iter in iters: - model[iter][C_AVATAR_PIXBUF] = scaled_pixbuf + else: + scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'roster') + for child_iter in iters: + self.model[child_iter][C_AVATAR_PIXBUF] = scaled_pixbuf + return False + def draw_completely_and_show_if_needed(self, jid, account): + '''Draw contact, account and groups of given jid + Show contact if it has pending events + ''' + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + self.draw_contact(jid, account) + + groups = contact.groups + if contact.is_transport(): + contact.groups = [_('Transports')] + if contact.is_observer(): + contact.groups = [_('Observers')] + if not groups: + groups = [_('General')] + for group in groups: + self.draw_group(group, account) + + self.draw_account(account) + self.draw_contact(jid, account) + self.refilter_shown_roster_items() + + def _idle_draw_jids_of_account(self, jids, account): + '''Draw given contacts and their avatars in a lazy fashion. + + Keyword arguments: + jids -- a list of jids to draw + account -- the corresponding account + ''' + def _draw_all_contacts(jids, account, t): + for jid in jids: + self.draw_contact(jid, account) + self.draw_avatar(jid, account) + yield True + print "--- Idle draw -----------------" + print "Draw contact and avatar", time.clock() - t + print "-------------------------------" + yield False + + t = time.clock() + task = _draw_all_contacts(jids, account, t) + gobject.idle_add(task.next) + + def draw_roster(self): + '''clear and draw roster''' + # clear the model, only if it is not empty + if self.model: + self.model.clear() + for acct in gajim.connections: + self.add_account(acct) + self.add_account_contacts(acct) + # Recalculate column width for ellipsizing + self.tree.columns_autosize() + + + def show_and_select_contact_if_having_events(self, jid, account): + '''Select contact in roster. If contact is hidden but has eventsi, + show him.''' + self.refilter_shown_roster_items() + iters = self._get_contact_iter(jid, account) + if not iters: + # Not visible in roster + return + path = self.tree.get_model().get_path(iters[0]) + if self.dragging or not gajim.config.get('scroll_roster_to_last_message'): + # do not change selection while DND'ing + return + self.tree.expand_row(path[0:1], False) + self.tree.expand_row(path[0:2], False) + self.tree.scroll_to_cell(path) + self.tree.set_cursor(path) + +################################################################################ +### Roster and Modelfilter handling +################################################################################ + + def _search_roster_func(self, model, column, key, iter): + if model[iter][C_NAME].decode('utf-8').lower().startswith( + gobject.markup_escape_text(key.lower())): + return False + return True + + def refilter_shown_roster_items(self): + self.filtering = True + self.modelfilter.refilter() + self.filtering = False + + def contact_is_visible(self, contact, account): + # show it if pending events + if gajim.events.get_nb_roster_events(account, contact.jid) > 0: + return True + # count events from all resources + for contact_ in gajim.contacts.get_contacts(account, contact.jid): + if contact_.resource and gajim.events.get_nb_roster_events(account, + contact_.get_full_jid()) > 0: + return True + # XEP-0162 + hide = contact.is_hidden_from_roster() + if hide and contact.sub != 'from': + return False + + showOffline = gajim.config.get('showoffline') + if (contact.show in ('offline', 'error') or hide) and not showOffline: + if contact.jid in gajim.to_be_removed[account]: + return True + return False + return True + + def _visible_func(self, model, iter): + '''Determine whether iter should be visible in the treeview''' + type_ = model[iter][C_TYPE] + if not type_: + return False + if type_ == 'account': + return True + account = model[iter][C_ACCOUNT] + if not account: + return False + account = account.decode('utf-8') + jid = model[iter][C_JID] + if not jid: + return False + jid = jid.decode('utf-8') + + if type_ == 'group': + group = jid + if group == _('Transports'): + return gajim.config.get('show_transports_group') + for contact in gajim.contacts.iter_contacts(account): + # Is this contact in this group ? + if group in contact.groups or (group == _('General') and not \ + contact.groups): + if self.contact_is_visible(contact, account): + return True + return False + if type_ == 'contact': + contact = gajim.contacts.get_first_contact_from_jid(account, jid) + return self.contact_is_visible(contact, account) + if type_ == 'agent': + return gajim.config.get('show_transports_group') + return True + + def _compareIters(self, model, iter1, iter2, data = None): + '''Compare two iters to sort them''' + name1 = model[iter1][C_NAME] + name2 = model[iter2][C_NAME] + if not name1 or not name2: + return 0 + name1 = name1.decode('utf-8') + name2 = name2.decode('utf-8') + type1 = model[iter1][C_TYPE] + type2 = model[iter2][C_TYPE] + if type1 == 'self_contact': + return -1 + if type2 == 'self_contact': + return 1 + if type1 == 'group': + name1 = model[iter1][C_JID] + name2 = model[iter2][C_JID] + if name1 == _('Transports'): + return 1 + if name2 == _('Transports'): + return -1 + if name1 == _('Not in Roster'): + return 1 + if name2 == _('Not in Roster'): + return -1 + if name1 == _('Groupchats'): + return 1 + if name2 == _('Groupchats'): + return -1 + account1 = model[iter1][C_ACCOUNT] + account2 = model[iter2][C_ACCOUNT] + if not account1 or not account2: + return 0 + account1 = account1.decode('utf-8') + account2 = account2.decode('utf-8') + if type1 == 'account': + if account1 < account2: + return -1 + return 1 + jid1 = model[iter1][C_JID].decode('utf-8') + jid2 = model[iter2][C_JID].decode('utf-8') + if type1 == 'contact': + lcontact1 = gajim.contacts.get_contacts(account1, jid1) + contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1) + if not contact1: + return 0 + name1 = contact1.get_shown_name() + if type2 == 'contact': + lcontact2 = gajim.contacts.get_contacts(account2, jid2) + contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2) + if not contact2: + return 0 + name2 = contact2.get_shown_name() + # We first compare by show if sort_by_show is True or if it's a child + # contact + if type1 == 'contact' and type2 == 'contact' and \ + gajim.config.get('sort_by_show'): + cshow = {'online':0, 'chat': 1, 'away': 2, 'xa': 3, 'dnd': 4, + 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8} + s = self.get_show(lcontact1) + if s in cshow: + show1 = cshow[s] + else: + show1 = 9 + s = self.get_show(lcontact2) + if s in cshow: + show2 = cshow[s] + else: + show2 = 9 + removing1 = False + removing2 = False + if show1 == 6 and jid1 in gajim.to_be_removed[account1]: + removing1 = True + if show2 == 6 and jid2 in gajim.to_be_removed[account2]: + removing2 = True + if removing1 and not removing2: + return -1 + if removing2 and not removing1: + return 1 + if show1 < show2: + return -1 + elif show1 > show2: + return 1 + # We compare names + if name1.lower() < name2.lower(): + return -1 + if name2.lower() < name1.lower(): + return 1 + if type1 == 'contact' and type2 == 'contact': + # We compare account names + if account1.lower() < account2.lower(): + return -1 + if account2.lower() < account1.lower(): + return 1 + # We compare jids + if jid1.lower() < jid2.lower(): + return -1 + if jid2.lower() < jid1.lower(): + return 1 + return 0 + +################################################################################ +### FIXME: Methods that don't belong to roster window... +### ... atleast not in there current form +################################################################################ + + def fire_up_unread_messages_events(self, account): + '''reads from db the unread messages, and fire them up, and + if we find very old unread messages, delete them from unread table''' + results = gajim.logger.get_unread_msgs() + for result in results: + jid = result[4] + if gajim.contacts.get_first_contact_from_jid(account, jid): + # We have this jid in our contacts list + # XXX unread messages should probably have their session saved with + # them + session = gajim.connections[account].make_new_session(jid) + + tim = time.localtime(float(result[2])) + self.on_message(jid, result[1], tim, account, msg_type = 'chat', + msg_id = result[0], session = session) + + elif (time.time() - result[2]) > 2592000: + # ok, here we see that we have a message in unread messages table + # that is older than a month. It is probably from someone not in our + # roster for accounts we usually launch, so we will delete this id + # from unread message tables. + gajim.logger.set_read_messages([result[0]]) + + def fill_contacts_and_groups_dicts(self, array, account): + '''fill gajim.contacts and gajim.groups''' + if account not in gajim.contacts.get_accounts(): + gajim.contacts.add_account(account) + if not gajim.groups.has_key(account): + gajim.groups[account] = {} + for jid in array.keys(): + # Remove old Contact instances + gajim.contacts.remove_jid(account, jid, remove_meta=False) + jids = jid.split('/') + # get jid + ji = jids[0] + # get resource + resource = '' + if len(jids) > 1: + resource = '/'.join(jids[1:]) + # get name + name = array[jid]['name'] + if not name: + name = '' + show = 'offline' # show is offline by default + status = '' # no status message by default + + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact1 = gajim.contacts.create_contact(jid = ji, name = name, + groups = array[jid]['groups'], show = show, status = status, + sub = array[jid]['subscription'], ask = array[jid]['ask'], + resource = resource, keyID = keyID) + gajim.contacts.add_contact(account, contact1) + + # when we draw the roster, we avoid having the same contact + # more than once (f.e. we avoid showing it twice when 2 resources) + for g in array[jid]['groups']: + if g in gajim.groups[account].keys(): + continue + + #if account + g in self.collapsed_rows: + #ishidden = False + #else: + ishidden = True + gajim.groups[account][g] = { 'expand': ishidden } + if gajim.config.get('ask_avatars_on_startup'): + pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji) + if pixbuf == 'ask': + transport = gajim.get_transport_name_from_jid(contact1.jid) + if not transport or gajim.jid_is_transport(contact1.jid): + jid_with_resource = contact1.jid + if contact1.resource: + jid_with_resource += '/' + contact1.resource + gajim.connections[account].request_vcard(jid_with_resource) + else: + host = gajim.get_server_from_jid(contact1.jid) + if not gajim.transport_avatar[account].has_key(host): + gajim.transport_avatar[account][host] = [contact1.jid] + else: + gajim.transport_avatar[account][host].append(contact1.jid) + # If we already have a chat window opened, update it with new contact + # instance + chat_control = gajim.interface.msg_win_mgr.get_control(ji, account) + if chat_control: + chat_control.contact = contact1 + + def enable_syncing_status_msg_from_current_music_track(self, enabled): + '''enable setting status msg from currently playing music track + + if enabled is True, we listen to events from music players about + currently played music track, and we update our + status message accordinly''' + if not dbus_support.supported: + # do nothing if user doesn't have D-Bus bindings + return + if enabled: + listener = MusicTrackListener.get() + if self._music_track_changed_signal is None: + self._music_track_changed_signal = listener.connect( + 'music-track-changed', self._music_track_changed) + track = listener.get_playing_track() + self._music_track_changed(listener, track) + else: + if self._music_track_changed_signal is not None: + listener = MusicTrackListener.get() + listener.disconnect(self._music_track_changed_signal) + self._music_track_changed_signal = None + self._music_track_changed(None, None) + + def enable_syncing_status_msg_from_lastfm(self, enabled): + ''' enable setting status msg from a Last.fm account + + if enabled is True, we start polling the Last.fm server, + and we update our status message accordinly''' + if enabled: + if self._music_track_changed_signal is None: + listener = LastFMTrackListener.get( + gajim.config.get('lastfm_username')) + self._music_track_changed_signal = listener.connect( + 'music-track-changed', self._music_track_changed) + track = listener.get_playing_track() + self._music_track_changed(listener, track) + else: + if self._music_track_changed_signal is not None: + listener = LastFMTrackListener.get( + gajim.config.get('lastfm_username')) + listener.disconnect(self._music_track_changed_signal) + self._music_track_changed_signal = None + self._music_track_changed(None, None) + + def _change_awn_icon_status(self, status): + if not dbus_support.supported: + # do nothing if user doesn't have D-Bus bindings + return + try: + bus = dbus.SessionBus() + if not 'com.google.code.Awn' in bus.list_names(): + # Awn is not installed + return + except: + return + iconset = gajim.config.get('iconset') + prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32') + if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'): + status = status + '.png' + elif status == 'online': + prefix = os.path.join(gajim.DATA_DIR, 'pixmaps') + status = 'gajim.png' + path = os.path.join(prefix, status) + try: + obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn') + awn = dbus.Interface(obj, 'com.google.code.Awn') + awn.SetTaskIconByName('Gajim', os.path.abspath(path)) + except Exception, e: + pass + + def _music_track_changed(self, unused_listener, music_track_info, + account=''): + from common import pep + if account == '': + accounts = gajim.connections.keys() + if music_track_info is None: + artist = '' + title = '' + source = '' + track = '' + length = '' + elif hasattr(music_track_info, 'paused') and music_track_info.paused == 0: + artist = '' + title = '' + source = '' + track = '' + length = '' + else: + artist = music_track_info.artist + title = music_track_info.title + source = music_track_info.album + if account == '': + for account in accounts: + if not gajim.account_is_connected(account): + continue + if not gajim.connections[account].pep_supported: + continue + pep.user_send_tune(account, artist, title, source) + elif gajim.connections[account].pep_supported: + pep.user_send_tune(account, artist, title, source) + + + def connected_rooms(self, account): + if account in gajim.gc_connected[account].values(): + return True + return False + + def auto_join_bookmarks(self, account): + '''autojoin bookmarks that have 'auto join' on for this account''' + for bm in gajim.connections[account].bookmarks: + if bm['autojoin'] in ('1', 'true'): + jid = bm['jid'] + if not gajim.gc_connected[account].has_key(jid) or\ + not gajim.gc_connected[account][jid]: + # we are not already connected + minimize = bm['minimize'] in ('1', 'true') + self.join_gc_room(account, jid, bm['nick'], + bm['password'], minimize = minimize) + + def open_event(self, account, jid, event): + '''If an event was handled, return True, else return False''' + data = event.parameters + ft = gajim.interface.instances['file_transfers'] + if event.type_ == 'normal': + dialogs.SingleMessageWindow(account, jid, + action='receive', from_whom=jid, subject=data[1], message=data[0], + resource=data[5], session=data[8], form_node=data[9]) + gajim.interface.remove_first_event(account, jid, event.type_) + return True + elif event.type_ == 'file-request': + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + gajim.interface.remove_first_event(account, jid, event.type_) + ft.show_file_request(account, contact, data) + return True + elif event.type_ in ('file-request-error', 'file-send-error'): + gajim.interface.remove_first_event(account, jid, event.type_) + ft.show_send_error(data) + return True + elif event.type_ in ('file-error', 'file-stopped'): + gajim.interface.remove_first_event(account, jid, event.type_) + ft.show_stopped(jid, data) + return True + elif event.type_ == 'file-completed': + gajim.interface.remove_first_event(account, jid, event.type_) + ft.show_completed(jid, data) + return True + elif event.type_ == 'gc-invitation': + dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], + data[1]) + gajim.interface.remove_first_event(account, jid, event.type_) + return True + return False + +################################################################################ +### This and that... random. +################################################################################ + + def show_roster_vbox(self, active): + if active: + self.xml.get_widget('roster_vbox2').show() + else: + self.xml.get_widget('roster_vbox2').hide() + + + def show_tooltip(self, contact): + pointer = self.tree.get_pointer() + props = self.tree.get_path_at_pos(pointer[0], pointer[1]) + # check if the current pointer is at the same path + # as it was before setting the timeout + if props and self.tooltip.id == props[0]: + # bounding rectangle of coordinates for the cell within the treeview + rect = self.tree.get_cell_area(props[0], props[1]) + + # position of the treeview on the screen + position = self.tree.window.get_origin() + self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y) + else: + self.tooltip.hide_tooltip() + + + def authorize(self, widget, jid, account): + '''Authorize a contact (by re-sending auth menuitem)''' + gajim.connections[account].send_authorization(jid) + dialogs.InformationDialog(_('Authorization has been sent'), + _('Now "%s" will know your status.') %jid) + + def req_sub(self, widget, jid, txt, account, groups = [], nickname = None, + auto_auth = False): + '''Request subscription to a contact''' + gajim.connections[account].request_subscription(jid, txt, nickname, + groups, auto_auth, gajim.nicks[account]) + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if not contact: + keyID = '' + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + if jid in attached_keys: + keyID = attached_keys[attached_keys.index(jid) + 1] + contact = gajim.contacts.create_contact(jid = jid, name = nickname, + groups = groups, show = 'requested', status = '', ask = 'none', + sub = 'subscribe', keyID = keyID) + gajim.contacts.add_contact(account, contact) + else: + if not _('Not in Roster') in contact.groups: + dialogs.InformationDialog(_('Subscription request has been sent'), + _('If "%s" accepts this request you will know his or her status.' + ) % jid) + return + contact.groups = groups + if nickname: + contact.name = nickname + self.remove_contact(contact.jid, account) + self.add_contact(jid, account) + + def revoke_auth(self, widget, jid, account): + '''Revoke a contact's authorization''' + gajim.connections[account].refuse_authorization(jid) + dialogs.InformationDialog(_('Authorization has been removed'), + _('Now "%s" will always see you as offline.') %jid) + + def set_connecting_state(self, account): + model = self.tree.get_model() + child_model = model.get_model() + IterA = self._get_account_iter(account) + if IterA: + child_iterA = model.convert_iter_to_child_iter(IterA) + child_model[child_iterA][0] = \ + gajim.interface.jabber_state_images['16']['connecting'] + if gajim.interface.systray_enabled: + gajim.interface.systray.change_status('connecting') + + def send_status(self, account, status, txt, auto = False, to = None): + model = self.tree.get_model() + child_model = model.get_model() + iterA = self._get_account_iter(account) + if status != 'offline': + if gajim.connections[account].connected < 2: + self.set_connecting_state(account) + + if not gajim.connections[account].password: + passphrase = '' + text = _('Enter your password for account %s') % account + if passwords.USER_HAS_GNOMEKEYRING and \ + not passwords.USER_USES_GNOMEKEYRING: + text += '\n' + _('Gnome Keyring is installed but not correctly started\ + (environment variable probably not correctly set)') + w = dialogs.PassphraseDialog(_('Password Required'), text, + _('Save password')) + passphrase, save = w.run() + if passphrase == -1: + if iterA: + child_iterA = model.convert_iter_to_child_iter(iterA) + child_model[child_iterA][0] = gajim.interface.jabber_state_images[ + '16']['offline'] + if gajim.interface.systray_enabled: + gajim.interface.systray.change_status('offline') + self.update_status_combobox() + return + gajim.connections[account].password = passphrase + if save: + gajim.config.set_per('accounts', account, 'savepass', True) + passwords.save_password(account, passphrase) + + keyid = gajim.config.get_per('accounts', account, 'keyid') + if keyid and not gajim.connections[account].gpg: + dialog = dialogs.WarningDialog(_('GPG is not usable'), + _('You will be connected to %s without OpenPGP.') % account) + + if gajim.account_is_connected(account): + if status == 'online' and gajim.interface.sleeper.getState() != \ + common.sleepy.STATE_UNKNOWN: + gajim.sleeper_state[account] = 'online' + elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa'): + gajim.sleeper_state[account] = 'off' + if to: + gajim.connections[account].send_custom_status(status, txt, to) + else: + was_invisible = gajim.connections[account].connected == \ + gajim.SHOW_LIST.index('invisible') + gajim.connections[account].change_status(status, txt, auto) + + if gajim.interface.status_sent_to_users.has_key(account): + gajim.interface.status_sent_to_users[account] = {} + if gajim.interface.status_sent_to_groups.has_key(account): + gajim.interface.status_sent_to_groups[account] = {} + for gc_control in gajim.interface.msg_win_mgr.get_controls( + message_control.TYPE_GC) + \ + gajim.interface.minimized_controls[account].values(): + if gc_control.account == account: + if gajim.gc_connected[account][gc_control.room_jid]: + gajim.connections[account].send_gc_status(gc_control.nick, + gc_control.room_jid, status, txt) + else: + # for some reason, we are not connected to the room even if + # tab is opened, send initial join_gc() + gajim.connections[account].join_gc(gc_control.nick, + gc_control.room_jid, None) + if was_invisible and status != 'offline': + # We come back from invisible, join bookmarks + self.auto_join_bookmarks(account) + + def chg_contact_status(self, contact, show, status, account): + '''When a contact changes his or her status''' + contact_instances = gajim.contacts.get_contacts(account, contact.jid) + contact.show = show + contact.status = status + # name is to show in conversation window + name = contact.get_shown_name() + + if len(contact_instances) > 1: + if contact.resource != '': + name += '/' + contact.resource + if show in ('offline', 'error') and \ + len(gajim.events.get_events(account, contact.get_full_jid())) == 0: + jid_with_resource = contact.jid + '/' + contact.resource + if gajim.interface.msg_win_mgr.has_window(jid_with_resource, + account): + win = gajim.interface.msg_win_mgr.get_window(jid_with_resource, + account) + ctrl = win.get_control(jid_with_resource, account) + ctrl.update_ui() + win.redraw_tab(ctrl) + gajim.contacts.remove_contact(account, contact) + elif contact.jid == gajim.get_jid_from_account(account) and show == 'offline': + # Our SelfContact went offline. Remove him + self.remove_contact(contact.jid, account) + + # print status in chat window and update status/GPG image + if gajim.interface.msg_win_mgr.has_window(contact.jid, account): + win = gajim.interface.msg_win_mgr.get_window(contact.jid, account) + ctrl = win.get_control(contact.jid, account) + ctrl.contact = gajim.contacts.get_contact_with_highest_priority( + account, contact.jid) + ctrl.update_ui() + win.redraw_tab(ctrl) + + uf_show = helpers.get_uf_show(show) + ctrl.print_conversation(_('%s is now %s') % (name, uf_show), + 'status') + if status: + ctrl.print_conversation(' (', 'status', simple=True) + ctrl.print_conversation('%s' % (status), 'status', simple=True) + ctrl.print_conversation(')', 'status', simple=True) + + # unset custom status + if gajim.interface.status_sent_to_users.has_key(account) and \ + contact.jid in gajim.interface.status_sent_to_users[account]: + del gajim.interface.status_sent_to_users[account][contact.jid] + + # Redraw everything and select the sender + self.draw_completely_and_show_if_needed(contact.jid, account) + + + def on_status_changed(self, account, status): + '''the core tells us that our status has changed''' + if account not in gajim.contacts.get_accounts(): + return + child_iterA = self._get_account_iter(account, self.model) + self.set_account_status_icon(account) + if status == 'offline': + if self.quit_on_next_offline > -1: + # we want to quit, we are waiting for all accounts to be offline + self.quit_on_next_offline -= 1 + if self.quit_on_next_offline < 1: + # all accounts offline, quit + self.quit_gtkgui_interface() + else: + # No need to redraw contacts if we're quitting + if child_iterA: + self.model[child_iterA][C_AVATAR_PIXBUF] = None + if gajim.con_types.has_key(account): + gajim.con_types[account] = None + for jid in gajim.contacts.get_jid_list(account): + lcontact = gajim.contacts.get_contacts(account, jid) + for contact in [c for c in lcontact if (c.show != 'offline' or \ + c.is_transport())]: + self.chg_contact_status(contact, 'offline', '', account) + # Remove SelfContact from roster. It might be gone when we return + self.remove_contact(gajim.get_jid_from_account(account), account) + + self.actions_menu_needs_rebuild = True + self.update_status_combobox() + # Force the rebuild now since the on_activates on the menu itself does + # not work with the os/x top level menubar + if sys.platform == 'darwin': + self.make_menu(force = True) + + def get_status_message(self, show): + if show in gajim.config.get_per('defaultstatusmsg'): + if gajim.config.get_per('defaultstatusmsg', show, 'enabled'): + return gajim.config.get_per('defaultstatusmsg', show, 'message') + if (show == 'online' and not gajim.config.get('ask_online_status')) or \ + (show in ('offline', 'invisible') + and not gajim.config.get('ask_offline_status')): + return '' + dlg = dialogs.ChangeStatusMessageDialog(show) + dlg.window.present() # show it on current workspace + message = dlg.run() + return message + + def change_status(self, widget, account, status): + def change(account, status): + message = self.get_status_message(status) + if message is None: + # user pressed Cancel to change status message dialog + return + self.send_status(account, status, message) + + if status == 'invisible' and self.connected_rooms(account): + dialogs.ConfirmationDialog( + _('You are participating in one or more group chats'), + _('Changing your status to invisible will result in disconnection ' + 'from those group chats. Are you sure you want to go invisible?'), + on_response_ok = (change, account, status)) + else: + change(account, status) + + def on_open_chat_window(self, widget, contact, account, resource = None, session = None): + # Get the window containing the chat + fjid = contact.jid + if resource: + fjid += '/' + resource + win = gajim.interface.msg_win_mgr.get_window(fjid, account) + if not win: + self.new_chat(contact, account, resource = resource, session = session) + win = gajim.interface.msg_win_mgr.get_window(fjid, account) + ctrl = win.get_control(fjid, account) + # last message is long time ago + gajim.last_message_time[account][ctrl.get_full_jid()] = 0 + win.set_active_tab(fjid, account) + if gajim.connections[account].is_zeroconf and \ + gajim.connections[account].status in ('offline', 'invisible'): + win.get_control(fjid, account).got_disconnected() + win.window.present() + def join_gc_room(self, account, room_jid, nick, password, minimize=False, is_continued=False): '''joins the room immediatelly''' @@ -802,7 +1858,7 @@ class RosterWindow: gajim.connections[account].join_gc(nick, room_jid, password) if password: gajim.gc_passwords[room_jid] = password - self.add_groupchat_to_roster(account, room_jid) + self.add_groupchat(account, room_jid) return if not minimized_control_exists and \ not gajim.interface.msg_win_mgr.has_window(room_jid, account): @@ -817,7 +1873,346 @@ class RosterWindow: contact = gajim.contacts.get_contact_with_highest_priority(account, \ room_jid) if contact or minimized_control_exists: - self.add_groupchat_to_roster(account, room_jid) + self.add_groupchat(account, room_jid) + + def update_status_combobox(self): + # table to change index in connection.connected to index in combobox + table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2, + 'xa':3, 'dnd':4, 'invisible':5} + + # we check if there are more options in the combobox that it should + # if yes, we remove the first ones + while len(self.status_combobox.get_model()) > len(table)+2: + self.status_combobox.remove_text(0) + + show = helpers.get_global_show() + # temporarily block signal in order not to send status that we show + # in the combobox + self.combobox_callback_active = False + if helpers.statuses_unified(): + self.status_combobox.set_active(table[show]) + else: + uf_show = helpers.get_uf_show(show) + liststore = self.status_combobox.get_model() + liststore.prepend(['SEPARATOR', None, '', True]) + liststore.prepend([uf_show + ' ' +"(desync'ed)", + gajim.interface.jabber_state_images['16'][show], show, False]) + self.status_combobox.set_active(0) + self._change_awn_icon_status(show) + self.combobox_callback_active = True + if gajim.interface.systray_enabled: + gajim.interface.systray.change_status(show) + + def new_private_chat(self, gc_contact, account, session = None): + contact = gajim.contacts.contact_from_gc_contact(gc_contact) + type_ = message_control.TYPE_PM + fjid = gc_contact.room_jid + '/' + gc_contact.name + mw = gajim.interface.msg_win_mgr.get_window(fjid, account) + if not mw: + mw = gajim.interface.msg_win_mgr.create_window(contact, account, type_) + + chat_control = PrivateChatControl(mw, gc_contact, contact, account, session) + mw.new_tab(chat_control) + if len(gajim.events.get_events(account, fjid)): + # We call this here to avoid race conditions with widget validation + chat_control.read_queue() + + def new_chat(self, contact, account, resource = None, session = None): + # Get target window, create a control, and associate it with the window + type_ = message_control.TYPE_CHAT + + fjid = contact.jid + if resource: + fjid += '/' + resource + + mw = gajim.interface.msg_win_mgr.get_window(fjid, account) + if not mw: + mw = gajim.interface.msg_win_mgr.create_window(contact, account, type_) + + chat_control = ChatControl(mw, contact, account, session, resource) + + mw.new_tab(chat_control) + + if len(gajim.events.get_events(account, fjid)): + # We call this here to avoid race conditions with widget validation + chat_control.read_queue() + + def new_chat_from_jid(self, account, fjid): + jid, resource = gajim.get_room_and_nick_from_fjid(fjid) + contact = gajim.contacts.get_contact(account, jid, resource) + added_to_roster = False + if not contact: + added_to_roster = True + contact = self.add_to_not_in_the_roster(account, jid, + resource = resource) + + if not gajim.interface.msg_win_mgr.has_window(fjid, account): + self.new_chat(contact, account, resource = resource) + mw = gajim.interface.msg_win_mgr.get_window(fjid, account) + mw.set_active_tab(fjid, account) + mw.window.present() + # For JEP-0172 + if added_to_roster: + mc = mw.get_control(fjid, account) + mc.user_nick = gajim.nicks[account] + + def new_room(self, room_jid, nick, account, is_continued=False): + # Get target window, create a control, and associate it with the window + contact = gajim.contacts.create_contact(jid = room_jid, name = nick) + mw = gajim.interface.msg_win_mgr.get_window(contact.jid, account) + if not mw: + mw = gajim.interface.msg_win_mgr.create_window(contact, account, + GroupchatControl.TYPE_ID) + gc_control = GroupchatControl(mw, contact, account, + is_continued=is_continued) + mw.new_tab(gc_control) + + def get_show(self, lcontact): + prio = lcontact[0].priority + show = lcontact[0].show + for u in lcontact: + if u.priority > prio: + prio = u.priority + show = u.show + return show + + def on_message_window_delete(self, win_mgr, msg_win): + if gajim.config.get('one_message_window') == 'always_with_roster': + self.show_roster_vbox(True) + gtkgui_helpers.resize_window(self.window, + gajim.config.get('roster_width'), + gajim.config.get('roster_height')) + + def on_message(self, jid, msg, tim, account, encrypted=False, msg_type='', + subject=None, resource='', msg_id=None, user_nick='', + advanced_notif_num=None, xhtml=None, session=None, form_node=None): + '''when we receive a message''' + contact = None + # if chat window will be for specific resource + resource_for_chat = resource + fjid = jid + # Try to catch the contact with correct resource + if resource: + fjid = jid + '/' + resource + contact = gajim.contacts.get_contact(account, jid, resource) + highest_contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) + if not contact: + # If there is another resource, it may be a message from an invisible + # resource + lcontact = gajim.contacts.get_contacts(account, jid) + if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \ + lcontact[0].show != 'offline')) and jid.find('@') > 0: + contact = gajim.contacts.copy_contact(highest_contact) + contact.resource = resource + if resource: + fjid = jid + '/' + resource + contact.priority = 0 + contact.show = 'offline' + contact.status = '' + gajim.contacts.add_contact(account, contact) + + else: + # Default to highest prio + fjid = jid + resource_for_chat = None + contact = highest_contact + if not contact: + # contact is not in roster + contact = self.add_to_not_in_the_roster(account, jid, user_nick) + + + # If visible, try to get first line of contact in roster + iters = self._get_contact_iter(jid, account) + if iters: + path = self.modelfilter.get_path(iters[0]) + + # Look for a chat control that has the given resource + ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) + if not ctrl: + # if not, if message comes from highest prio, get control or open one + # without resource + if highest_contact and contact.resource == highest_contact.resource \ + and not jid == gajim.get_jid_from_account(account): + ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) + fjid = jid + resource_for_chat = None + + # Do we have a queue? + no_queue = len(gajim.events.get_events(account, fjid)) == 0 + + popup = helpers.allow_popup_window(account, advanced_notif_num) + + if msg_type == 'normal' and popup: # it's single message to be autopopuped + dialogs.SingleMessageWindow(account, contact.jid, action='receive', + from_whom=jid, subject=subject, message=msg, resource=resource, + session=session, form_node=form_node) + return + + # We print if window is opened and it's not a single message + if ctrl and msg_type != 'normal': + typ = '' + if msg_type == 'error': + typ = 'status' + if session: + ctrl.set_session(session) + ctrl.print_conversation(msg, typ, tim = tim, encrypted = encrypted, + subject = subject, xhtml = xhtml) + if msg_id: + gajim.logger.set_read_messages([msg_id]) + return + + # We save it in a queue + type_ = 'chat' + event_type = 'message_received' + if msg_type == 'normal': + type_ = 'normal' + event_type = 'single_message_received' + show_in_roster = notify.get_show_in_roster(event_type, account, contact) + show_in_systray = notify.get_show_in_systray(event_type, account, contact) + event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, + encrypted, resource, msg_id, xhtml, session, form_node), + show_in_roster=show_in_roster, show_in_systray=show_in_systray) + gajim.events.add_event(account, fjid, event) + if popup: + # FIXME: What is happening here. What does "OR he is not in the roster at all" mean + if not ctrl: + self.new_chat(contact, account, resource=resource_for_chat) + if path and not self.dragging and gajim.config.get( + 'scroll_roster_to_last_message'): + # we curently see contact in our roster OR he + # is not in the roster at all. + # show and select his line in roster + # do not change selection while DND'ing + self.tree.expand_row(path[0:1], False) + self.tree.expand_row(path[0:2], False) + self.tree.scroll_to_cell(path) + self.tree.set_cursor(path) + else: + if no_queue: # We didn't have a queue: we change icons + self.draw_contact(jid, account) + self.show_title() # we show the * or [n] + # Show contact in roster (if he is invisible for example) and select + # line + self.show_and_select_contact_if_having_events(jid, account) + + def close_all_from_dict(self, dic): + '''close all the windows in the given dictionary''' + for w in dic.values(): + if type(w) == type({}): + self.close_all_from_dict(w) + else: + w.window.destroy() + + def close_all(self, account, force = False): + '''close all the windows from an account + if force is True, do not ask confirmation before closing chat/gc windows + ''' + if account in gajim.interface.instances: + self.close_all_from_dict(gajim.interface.instances[account]) + for ctrl in gajim.interface.msg_win_mgr.get_controls(acct = account): + ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON, + force = force) + + def on_roster_window_delete_event(self, widget, event): + '''Main window X button was clicked''' + if gajim.interface.systray_enabled and not gajim.config.get( + 'quit_on_roster_x_button'): + self.tooltip.hide_tooltip() + self.window.hide() + else: + self.on_quit_request() + return True # do NOT destroy the window + + def quit_gtkgui_interface(self): + '''When we quit the gtk interface : + tell that to the core and exit gtk''' + msgwin_width_adjust = 0 + + # in case show_roster_on_start is False and roster is never shown + # window.window is None + if self.window.window is not None: + x, y = self.window.window.get_root_origin() + gajim.config.set('roster_x-position', x) + gajim.config.set('roster_y-position', y) + width, height = self.window.get_size() + # For the width use the size of the vbox containing the tree and + # status combo, this will cancel out any hpaned width + width = self.xml.get_widget('roster_vbox2').allocation.width + gajim.config.set('roster_width', width) + gajim.config.set('roster_height', height) + if not self.xml.get_widget('roster_vbox2').get_property('visible'): + # The roster vbox is hidden, so the message window is larger + # then we want to save (i.e. the window will grow every startup) + # so adjust. + msgwin_width_adjust = -1 * width + gajim.config.set('show_roster_on_startup', + self.window.get_property('visible')) + gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust) + + gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows)) + gajim.interface.save_config() + for account in gajim.connections: + gajim.connections[account].quit(True) + self.close_all(account) + if gajim.interface.systray_enabled: + gajim.interface.hide_systray() + gtk.main_quit() + + def on_quit_request(self, widget = None): + ''' user want to quit. Check if he should be warned about messages + pending. Send offline to all connected account. We do NOT really quit + gajim here ''' + accounts = gajim.connections.keys() + get_msg = False + for acct in accounts: + if gajim.connections[acct].connected: + get_msg = True + break + if get_msg: + message = self.get_status_message('offline') + if message is None: + # user pressed Cancel to change status message dialog + return + + # check if we have unread messages + unread = gajim.events.get_nb_events() + if not gajim.config.get('notify_on_all_muc_messages'): + unread_not_to_notify = gajim.events.get_nb_events(['printed_gc_msg']) + unread -= unread_not_to_notify + + # check if we have recent messages + recent = False + for win in gajim.interface.msg_win_mgr.windows(): + for ctrl in win.controls(): + fjid = ctrl.get_full_jid() + if gajim.last_message_time[ctrl.account].has_key(fjid): + if time.time() - gajim.last_message_time[ctrl.account][fjid] < 2: + recent = True + break + if recent: + break + + if unread or recent: + dialog = dialogs.ConfirmationDialog(_('You have unread messages'), + _('Messages will only be available for reading them later if you' + ' have history enabled and contact is in your roster.')) + if dialog.get_response() != gtk.RESPONSE_OK: + return + + self.quit_on_next_offline = 0 + for acct in accounts: + if gajim.connections[acct].connected: + self.quit_on_next_offline += 1 + self.send_status(acct, 'offline', message) + + if not self.quit_on_next_offline: + self.quit_gtkgui_interface() + +################################################################################ +### Menu and GUI callbacks +### FIXME: order callbacks in itself... +################################################################################ def on_actions_menuitem_activate(self, widget): self.make_menu() @@ -872,61 +2267,1986 @@ class RosterWindow: helpers.exec_command('python history_manager.py') else: # Unix user helpers.exec_command('python history_manager.py &') - - def get_and_connect_advanced_menuitem_menu(self, account): - '''adds FOR ACCOUNT options''' - xml = gtkgui_helpers.get_glade('advanced_menuitem_menu.glade') - advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu') - - xml_console_menuitem = xml.get_widget('xml_console_menuitem') - privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem') - administrator_menuitem = xml.get_widget('administrator_menuitem') - send_server_message_menuitem = xml.get_widget( - 'send_server_message_menuitem') - set_motd_menuitem = xml.get_widget('set_motd_menuitem') - update_motd_menuitem = xml.get_widget('update_motd_menuitem') - delete_motd_menuitem = xml.get_widget('delete_motd_menuitem') - - xml_console_menuitem.connect('activate', - self.on_xml_console_menuitem_activate, account) - - if gajim.connections[account] and gajim.connections[account].\ - privacy_rules_supported: - privacy_lists_menuitem.connect('activate', - self.on_privacy_lists_menuitem_activate, account) - else: - privacy_lists_menuitem.set_sensitive(False) - + + def on_info(self, widget, contact, account): + '''Call vcard_information_window class to display contact's information''' if gajim.connections[account].is_zeroconf: - administrator_menuitem.set_sensitive(False) - send_server_message_menuitem.set_sensitive(False) - set_motd_menuitem.set_sensitive(False) - update_motd_menuitem.set_sensitive(False) - delete_motd_menuitem.set_sensitive(False) + self.on_info_zeroconf(widget, contact, account) + return + + info = gajim.interface.instances[account]['infos'] + if info.has_key(contact.jid): + info[contact.jid].window.present() else: - send_server_message_menuitem.connect('activate', - self.on_send_server_message_menuitem_activate, account) + info[contact.jid] = vcard.VcardWindow(contact, account) - set_motd_menuitem.connect('activate', - self.on_set_motd_menuitem_activate, account) + def on_info_zeroconf(self, widget, contact, account): + info = gajim.interface.instances[account]['infos'] + if info.has_key(contact.jid): + info[contact.jid].window.present() + else: + contact = gajim.contacts.get_first_contact_from_jid(account, + contact.jid) + if contact.show in ('offline', 'error'): + # don't show info on offline contacts + return + info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account) - update_motd_menuitem.connect('activate', - self.on_update_motd_menuitem_activate, account) + def on_roster_treeview_leave_notify_event(self, widget, event): + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id == props[0]: + self.tooltip.hide_tooltip() - delete_motd_menuitem.connect('activate', - self.on_delete_motd_menuitem_activate, account) + def on_roster_treeview_motion_notify_event(self, widget, event): + model = widget.get_model() + props = widget.get_path_at_pos(int(event.x), int(event.y)) + if self.tooltip.timeout > 0: + if not props or self.tooltip.id != props[0]: + self.tooltip.hide_tooltip() + if props: + [row, col, x, y] = props + iter = None + try: + iter = model.get_iter(row) + except: + self.tooltip.hide_tooltip() + return + if model[iter][C_TYPE] in ('contact', 'self_contact'): + # we're on a contact entry in the roster + account = model[iter][C_ACCOUNT].decode('utf-8') + jid = model[iter][C_JID].decode('utf-8') + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + contacts = gajim.contacts.get_contacts(account, jid) + connected_contacts = [] + for c in contacts: + if c.show not in ('offline', 'error'): + connected_contacts.append(c) + if not connected_contacts: + # no connected contacts, show the ofline one + connected_contacts = contacts + self.tooltip.account = account + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, connected_contacts) + elif model[iter][C_TYPE] == 'groupchat': + account = model[iter][C_ACCOUNT].decode('utf-8') + jid = model[iter][C_JID].decode('utf-8') + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + contact = gajim.contacts.get_contacts(account, jid) + self.tooltip.account = account + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, contact) + elif model[iter][C_TYPE] == 'account': + # we're on an account entry in the roster + account = model[iter][C_ACCOUNT].decode('utf-8') + if account == 'all': + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.account = None + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, []) + return + jid = gajim.get_jid_from_account(account) + contacts = [] + connection = gajim.connections[account] + # get our current contact info - advanced_menuitem_menu.show_all() + nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( + accounts = [account]) + account_name = account + if gajim.account_is_connected(account): + account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) + contact = gajim.contacts.create_contact(jid = jid, + name = account_name, show = connection.get_status(), sub = '', + status = connection.status, + resource = connection.server_resource, + priority = connection.priority, + mood = connection.mood, + tune = connection.tune, + activity = connection.activity) + if gajim.connections[account].gpg: + contact.keyID = gajim.config.get_per('accounts', connection.name, + 'keyid') + contacts.append(contact) + # if we're online ... + if connection.connection: + roster = connection.connection.getRoster() + # in threadless connection when no roster stanza is sent, + # 'roster' is None + if roster and roster.getItem(jid): + resources = roster.getResources(jid) + # ...get the contact info for our other online resources + for resource in resources: + # Check if we already have this resource + found = False + for contact_ in contacts: + if contact_.resource == resource: + found = True + break + if found: + continue + show = roster.getShow(jid+'/'+resource) + if not show: + show = 'online' + contact = gajim.contacts.create_contact(jid = jid, + name = account, show = show, + status = roster.getStatus(jid+'/'+resource), + resource = resource, + priority = roster.getPriority(jid+'/'+resource)) + contacts.append(contact) + if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: + self.tooltip.id = row + self.tooltip.account = None + self.tooltip.timeout = gobject.timeout_add(500, + self.show_tooltip, contacts) - return advanced_menuitem_menu + def on_agent_logging(self, widget, jid, state, account): + '''When an agent is requested to log in or off''' + gajim.connections[account].send_agent_status(jid, state) - def set_actions_menu_needs_rebuild(self): - self.actions_menu_needs_rebuild = True - # Force the rebuild now since the on_activates on the menu itself does - # not work with the os/x top level menubar - if sys.platform == 'darwin': - self.make_menu(force = True) - return + def on_edit_agent(self, widget, contact, account): + '''When we want to modify the agent registration''' + gajim.connections[account].request_register_agent_info(contact.jid) + + def on_remove_agent(self, widget, list_): + '''When an agent is requested to be removed. list_ is a list of + (contact, account) tuple''' + for (contact, account) in list_: + if gajim.config.get_per('accounts', account, 'hostname') == \ + contact.jid: + # We remove the server contact + # remove it from treeview + gajim.connections[account].unsubscribe(contact.jid) + self.remove_contact(contact.jid, account) + gajim.contacts.remove_contact(account, contact) + return + + def remove(list_): + for (contact, account) in list_: + full_jid = contact.get_full_jid() + gajim.connections[account].unsubscribe_agent(full_jid) + # remove transport from treeview + self.remove_contact(contact.jid, account) + gajim.contacts.remove_jid(account, contact.jid) + gajim.contacts.remove_contact(account, contact) + + # Check if there are unread events from some contacts + has_unread_events = False + for (contact, account) in list_: + for jid in gajim.events.get_events(account): + if jid.endswith(contact.jid): + has_unread_events = True + break + if has_unread_events: + dialogs.ErrorDialog(_('You have unread messages'), + _('You must read them before removing this transport.')) + return + if len(list_) == 1: + pritext = _('Transport "%s" will be removed') % contact.jid + sectext = _('You will no longer be able to send and receive messages ' + 'from contacts using this transport.') + else: + pritext = _('Transports will be removed') + jids = '' + for (contact, account) in list_: + jids += '\n ' + contact.get_shown_name() + ',' + jids = jids[:-1] + '.' + sectext = _('You will no longer be able to send and receive messages ' + 'to contacts from these transports: %s') % jids + dialogs.ConfirmationDialog(pritext, sectext, + on_response_ok = (remove, list_)) + + def on_block(self, widget, iter, group_list): + ''' When clicked on the 'block' button in context menu. ''' + model = self.tree.get_model() + accounts = [] + msg = self.get_status_message('offline') + if group_list == None: + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + accounts.append(account) + self.send_status(account, 'offline', msg, to = jid) + new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', + 'value' : jid, 'child': [u'message', u'iq', u'presence-out']} + gajim.connections[account].blocked_list.append(new_rule) + # needed for draw_contact: + gajim.connections[account].blocked_contacts.append(jid) + self.draw_contact(jid, account) + else: + if iter == None: + for (contact, account) in group_list: + if account not in accounts: + if not gajim.connections[account].privacy_rules_supported: + continue + accounts.append(account) + self.send_status(account, 'offline', msg, to=contact.jid) + new_rule = {'order': u'1', 'type': u'jid', + 'action': u'deny', 'value' : contact.jid, + 'child': [u'message', u'iq', u'presence-out']} + gajim.connections[account].blocked_list.append(new_rule) + # needed for draw_contact: + gajim.connections[account].blocked_contacts.append(contact.jid) + self.draw_contact(contact.jid, account) + else: + group = model[iter][C_JID].decode('utf-8') + for (contact, account) in group_list: + if account not in accounts: + if not gajim.connections[account].privacy_rules_supported: + continue + accounts.append(account) + # needed for draw_group: + gajim.connections[account].blocked_groups.append(group) + self.draw_group(group, account) + self.send_status(account, 'offline', msg, to=contact.jid) + self.draw_contact(contact.jid, account) + new_rule = {'order': u'1', 'type': u'group', 'action': u'deny', + 'value' : group, 'child': [u'message', u'iq', u'presence-out']} + gajim.connections[account].blocked_list.append(new_rule) + for account in accounts: + gajim.connections[account].set_privacy_list( + 'block', gajim.connections[account].blocked_list) + if len(gajim.connections[account].blocked_list) == 1: + gajim.connections[account].set_active_list('block') + gajim.connections[account].set_default_list('block') + gajim.connections[account].get_privacy_list('block') + + def on_unblock(self, widget, iter, group_list): + ''' When clicked on the 'unblock' button in context menu. ''' + model = self.tree.get_model() + accounts = [] + if group_list == None: + jid = model[iter][C_JID].decode('utf-8') + jid_account = model[iter][C_ACCOUNT].decode('utf-8') + accounts.append(jid_account) + gajim.connections[jid_account].new_blocked_list = [] + for rule in gajim.connections[jid_account].blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'jid' \ + or rule['value'] != jid: + gajim.connections[jid_account].new_blocked_list.append(rule) + # needed for draw_contact: + if jid in gajim.connections[jid_account].blocked_contacts: + gajim.connections[jid_account].blocked_contacts.remove(jid) + self.draw_contact(jid, jid_account) + else: + if iter == None: + for (contact, account) in group_list: + if account not in accounts: + if gajim.connections[account].privacy_rules_supported: + accounts.append(account) + gajim.connections[account].new_blocked_list = [] + gajim.connections[account].to_unblock = [] + gajim.connections[account].to_unblock.append(contact.jid) + else: + gajim.connections[account].to_unblock.append(contact.jid) + # needed for draw_contact: + if contact.jid in gajim.connections[account].blocked_contacts: + gajim.connections[account].blocked_contacts.remove( + contact.jid) + self.draw_contact(contact.jid, account) + for account in accounts: + for rule in gajim.connections[account].blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'jid' \ + or rule['value'] not in gajim.connections[account].to_unblock: + gajim.connections[account].new_blocked_list.append(rule) + else: + group = model[iter][C_JID].decode('utf-8') + for (contact, account) in group_list: + if account not in accounts: + if gajim.connections[account].privacy_rules_supported: + accounts.append(account) + # needed for draw_group: + if group in gajim.connections[account].blocked_groups: + gajim.connections[account].blocked_groups.remove(group) + self.draw_group(group, account) + gajim.connections[account].new_blocked_list = [] + for rule in gajim.connections[account].blocked_list: + if rule['action'] != 'deny' or rule['type'] != 'group' \ + or rule['value'] != group: + gajim.connections[account].new_blocked_list.append( + rule) + self.draw_contact(contact.jid, account) + for account in accounts: + gajim.connections[account].set_privacy_list( + 'block', gajim.connections[account].new_blocked_list) + gajim.connections[account].get_privacy_list('block') + if len(gajim.connections[account].new_blocked_list) == 0: + gajim.connections[account].blocked_list = [] + gajim.connections[account].blocked_contacts = [] + gajim.connections[account].blocked_groups = [] + gajim.connections[account].set_default_list('') + gajim.connections[account].set_active_list('') + gajim.connections[account].del_privacy_list('block') + if gajim.interface.instances[account].has_key('blocked_contacts'): + gajim.interface.instances[account]['blocked_contacts'].\ + privacy_list_received([]) + if group_list == None: + status = gajim.connections[jid_account].connected + msg = gajim.connections[jid_account].status + if not self.regroup: + show = gajim.SHOW_LIST[status] + else: # accounts merged + show = helpers.get_global_show() + self.send_status(jid_account, show, msg, to=jid) + else: + for (contact, account) in group_list: + if not self.regroup: + show = gajim.SHOW_LIST[gajim.connections[account].connected] + else: # accounts merged + show = helpers.get_global_show() + if account not in accounts: + if gajim.connections[account].privacy_rules_supported: + accounts.append(account) + self.send_status(account, show, + gajim.connections[account].status, to=contact.jid) + else: + self.send_status(account, show, + gajim.connections[account].status, to=contact.jid) + + def on_rename(self, widget, iter, path): + # this function is called either by F2 or by Rename menuitem + if gajim.interface.instances.has_key('rename'): + gajim.interface.instances['rename'].dialog.present() + return + model = self.tree.get_model() + + row_type = model[iter][C_TYPE] + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + # account is offline, don't allow to rename + if gajim.connections[account].connected < 2: + return + if row_type in ('contact', 'agent'): + # it's jid + title = _('Rename Contact') + message = _('Enter a new nickname for contact %s') % jid + old_text = gajim.contacts.get_contact_with_highest_priority(account, + jid).name + elif row_type == 'group': + if jid in helpers.special_groups + (_('General'),): + return + old_text = model[iter][C_JID].decode('utf-8') + title = _('Rename Group') + message = _('Enter a new name for group %s') % old_text + + def on_renamed(new_text, account, row_type, jid, old_text): + if gajim.interface.instances.has_key('rename'): + del gajim.interface.instances['rename'] + if row_type in ('contact', 'agent'): + if old_text == new_text: + return + for u in gajim.contacts.get_contacts(account, jid): + u.name = new_text + gajim.connections[account].update_contact(jid, new_text, u.groups) + self.draw_contact(jid, account) + # Update opened chat + ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) + if ctrl: + ctrl.update_ui() + win = gajim.interface.msg_win_mgr.get_window(jid, account) + win.redraw_tab(ctrl) + win.show_title() + elif row_type == 'group': + # in C_JID column, we hold the group name (which is not escaped) + if old_text == new_text: + return + # Groups may not change name from or to a special groups + for g in helpers.special_groups: + if g in (new_text, old_text): + return + # get all contacts in that group + for jid in gajim.contacts.get_jid_list(account): + contact = gajim.contacts.get_contact_with_highest_priority( + account, jid) + if old_text in contact.groups: + # set them in the new one and remove it from the old + contact.groups.remove(old_text) + self.remove_contact(contact.jid, account) + if new_text not in contact.groups: + contact.groups.append(new_text) + self.add_contact(contact.jid, account) + gajim.connections[account].update_contact(contact.jid, + contact.name, contact.groups) + # If last removed iter was not visible, gajim.groups is not cleaned + if gajim.groups[account].has_key(old_text): + del gajim.groups[account][old_text] + self.draw_group(new_text, account) + + def on_canceled(): + if gajim.interface.instances.has_key('rename'): + del gajim.interface.instances['rename'] + + gajim.interface.instances['rename'] = dialogs.InputDialog(title, message, + old_text, False, (on_renamed, account, row_type, jid, old_text), + on_canceled) + + def on_remove_group_item_activated(self, widget, group, account): + dlg = dialogs.ConfirmationDialogCheck(_('Remove Group'), + _('Do you want to remove group %s from the roster?' % group), + _('Remove also all contacts in this group from your roster')) + dlg.set_default_response(gtk.BUTTONS_OK_CANCEL) + response = dlg.run() + if response == gtk.RESPONSE_OK: + for contact in gajim.contacts.get_contacts_from_group(account, group): + if not dlg.is_checked(): + self.remove_contact_from_groups(contact.jid,account, [group]) + gajim.connections[account].update_contact(contact.jid, + contact.name, contact.groups) + self.add_contact(contact.jid, account) + else: + gajim.connections[account].unsubscribe(contact.jid) + for c in gajim.contacts.get_contacts(account, contact.jid): + self.remove_contact(c.jid, account) + gajim.contacts.remove_jid(account, c.jid) + self._readd_contact_to_roster_if_needed(contact, account) + self.draw_account(account) + + def on_assign_pgp_key(self, widget, contact, account): + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + keys = {} + #GPG Key + keyID = _('None') + for i in xrange(len(attached_keys)/2): + keys[attached_keys[2*i]] = attached_keys[2*i+1] + if attached_keys[2*i] == contact.jid: + keyID = attached_keys[2*i+1] + public_keys = gajim.connections[account].ask_gpg_keys() + #GPG Key + public_keys[_('None')] = _('None') + instance = dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'), + _('Select a key to apply to the contact'), public_keys, keyID) + keyID = instance.run() + if keyID is None: + return + #GPG Key + if keyID[0] == _('None'): + if contact.jid in keys: + del keys[contact.jid] + keyID = '' + else: + keyID = keyID[0] + keys[contact.jid] = keyID + + if gajim.interface.msg_win_mgr.has_window(contact.jid, account): + ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) + ctrl.update_ui() + keys_str = '' + for jid in keys: + keys_str += jid + ' ' + keys[jid] + ' ' + gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) + for u in gajim.contacts.get_contacts(account, contact.jid): + u.keyID = helpers.prepare_and_validate_gpg_keyID(account, + contact.jid, keyID) + + def on_set_custom_avatar_activate(self, widget, contact, account): + def on_ok(widget, path_to_file): + filesize = os.path.getsize(path_to_file) # in bytes + invalid_file = False + msg = '' + if os.path.isfile(path_to_file): + stat = os.stat(path_to_file) + if stat[6] == 0: + invalid_file = True + msg = _('File is empty') + else: + invalid_file = True + msg = _('File does not exist') + if invalid_file: + dialogs.ErrorDialog(_('Could not load image'), msg) + return + try: + pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) + if filesize > 16384: # 16 kb + # get the image at 'tooltip size' + # and hope that user did not specify in ACE crazy size + pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip') + except gobject.GError, msg: # unknown format + # msg should be string, not object instance + msg = str(msg) + dialogs.ErrorDialog(_('Could not load image'), msg) + return + gajim.interface.save_avatar_files(contact.jid, pixbuf, local = True) + dlg.destroy() + self.update_avatar_in_gui(contact.jid, account) + + def on_clear(widget): + dlg.destroy() + # Delete file: + gajim.interface.remove_avatar_files(contact.jid, local = True) + self.update_avatar_in_gui(contact.jid, account) + + dlg = dialogs.AvatarChooserDialog(on_response_ok = on_ok, + on_response_clear = on_clear) + + def on_edit_groups(self, widget, list_): + dlg = dialogs.EditGroupsDialog(list_) + dlg.run() + + def on_history(self, widget, contact, account): + '''When history menuitem is activated: call log window''' + if gajim.interface.instances.has_key('logs'): + gajim.interface.instances['logs'].window.present() + gajim.interface.instances['logs'].open_history(contact.jid, account) + else: + gajim.interface.instances['logs'] = history_window.\ + HistoryWindow(contact.jid, account) + + def on_disconnect(self, widget, jid, account): + '''When disconnect menuitem is activated: disconect from room''' + ctrl = gajim.interface.minimized_controls[account][jid] + del gajim.interface.minimized_controls[account][jid] + ctrl.shutdown() + + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + if not contact: + return + if contact.groups == [_('Groupchats')]: + # FIXME: use proper API + gajim.contacts.remove_contact(account, contact) + self.remove_contact(contact.jid, account) + + def on_send_single_message_menuitem_activate(self, widget, account, + contact = None): + if contact is None: + dialogs.SingleMessageWindow(account, action = 'send') + elif type(contact) == type([]): + dialogs.SingleMessageWindow(account, contact, 'send') + else: + jid = contact.jid + if contact.jid == gajim.get_jid_from_account(account): + jid += '/' + contact.resource + dialogs.SingleMessageWindow(account, jid, 'send') + + def on_send_file_menuitem_activate(self, widget, contact, account, + resource=None): + gajim.interface.instances['file_transfers'].show_file_send_request( + account, contact) + + def on_add_special_notification_menuitem_activate(self, widget, jid): + dialogs.AddSpecialNotificationDialog(jid) + + def on_invite_to_new_room(self, widget, list_, resource = None): + ''' resource parameter MUST NOT be used if more than one contact in + list ''' + account_list = [] + jid_list = [] + for (contact, account) in list_: + if contact.jid not in jid_list: + if resource: # we MUST have one contact only in list_ + fjid = contact.jid + '/' + resource + jid_list.append(fjid) + else: + jid_list.append(contact.jid) + if account not in account_list: + account_list.append(account) + # transform None in 'jabber' + type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber' + for account in account_list: + if gajim.connections[account].muc_jid[type_]: + # create the room on this muc server + if gajim.interface.instances[account].has_key('join_gc'): + gajim.interface.instances[account]['join_gc'].window.destroy() + try: + gajim.interface.instances[account]['join_gc'] = \ + dialogs.JoinGroupchatWindow(account, + gajim.connections[account].muc_jid[type_], + automatic = {'invities': jid_list}) + except GajimGeneralException: + continue + break + + def on_invite_to_room(self, widget, list_, room_jid, room_account, + resource = None): + ''' resource parameter MUST NOT be used if more than one contact in + list ''' + for (contact, acct) in list_: + contact_jid = contact.jid + if resource: # we MUST have one contact only in list_ + contact_jid += '/' + resource + gajim.connections[room_account].send_invite(room_jid, contact_jid) + + def on_all_groupchat_maximized(self, widget, group_list): + for (contact, account) in group_list: + self.on_groupchat_maximized(widget, contact.jid, account) + + def on_groupchat_maximized(self, widget, jid, account): + '''When a groupchat is maximised''' + ctrl = gajim.interface.minimized_controls[account][jid] + mw = gajim.interface.msg_win_mgr.get_window(ctrl.contact.jid, ctrl.account) + if not mw: + mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact, + ctrl.account, ctrl.type_id) + ctrl.parent_win = mw + mw.new_tab(ctrl) + mw.set_active_tab(jid, account) + mw.window.present() + del gajim.interface.minimized_controls[account][jid] + + self.remove_groupchat(jid, account) + + def on_edit_account(self, widget, account): + if gajim.interface.instances.has_key('accounts'): + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + gajim.interface.instances['accounts'].select_account(account) + + def on_zeroconf_properties(self, widget, account): + if gajim.interface.instances.has_key('accounts'): + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + gajim.interface.instances['accounts'].select_account(account) + + def on_open_gmail_inbox(self, widget, account): + url = gajim.connections[account].gmail_url + if url: + helpers.launch_browser_mailer('url', url) + + def on_change_activity_activate(self, widget, account): + dlg = dialogs.ChangeActivityDialog(account) + + def on_change_mood_activate(self, widget, account): + dlg = dialogs.ChangeMoodDialog(account) + + def on_change_status_message_activate(self, widget, account): + show = gajim.SHOW_LIST[gajim.connections[account].connected] + dlg = dialogs.ChangeStatusMessageDialog(show) + message = dlg.run() + if message is not None: # None is if user pressed Cancel + self.send_status(account, show, message) + + def on_add_to_roster(self, widget, contact, account): + dialogs.AddNewContactWindow(account, contact.jid, contact.name) + + + def on_roster_treeview_scroll_event(self, widget, event): + self.tooltip.hide_tooltip() + + def on_roster_treeview_key_press_event(self, widget, event): + '''when a key is pressed in the treeviews''' + self.tooltip.hide_tooltip() + if event.keyval == gtk.keysyms.Escape: + self.tree.get_selection().unselect_all() + elif event.keyval == gtk.keysyms.F2: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + if len(list_of_paths) != 1: + return + path = list_of_paths[0] + type_ = model[path][C_TYPE] + if type_ in ('contact', 'group', 'agent'): + iter = model.get_iter(path) + self.on_rename(widget, iter, path) + + elif event.keyval == gtk.keysyms.Delete: + treeselection = self.tree.get_selection() + model, list_of_paths = treeselection.get_selected_rows() + if not len(list_of_paths): + return + type_ = model[list_of_paths[0]][C_TYPE] + account = model[list_of_paths[0]][C_ACCOUNT] + list_ = [] + for path in list_of_paths: + if model[path][C_TYPE] != type_: + return + jid = model[path][C_JID].decode('utf-8') + account = model[path][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + list_.append((contact, account)) + if type_ in ('account', 'group', 'self_contact') or \ + account == gajim.ZEROCONF_ACC_NAME: + return + if type_ == 'contact': + self.on_req_usub(widget, list_) + elif type_ == 'agent': + self.on_remove_agent(widget, list_) + + def on_roster_treeview_button_release_event(self, widget, event): + try: + path, column, x, y = self.tree.get_path_at_pos(int(event.x), + int(event.y)) + except TypeError: + return False + + if event.button == 1: # Left click + if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ + not event.state & gtk.gdk.CONTROL_MASK: + # Check if button has been pressed on the same row + if self.clicked_path == path: + self.on_row_activated(widget, path) + self.clicked_path = None + + def on_roster_treeview_button_press_event(self, widget, event): + # hide tooltip, no matter the button is pressed + self.tooltip.hide_tooltip() + try: + path, column, x, y = self.tree.get_path_at_pos(int(event.x), + int(event.y)) + except TypeError: + self.tree.get_selection().unselect_all() + return False + + if event.button == 3: # Right click + try: + model, list_of_paths = self.tree.get_selection().get_selected_rows() + except TypeError: + list_of_paths = [] + if path not in list_of_paths: + self.tree.get_selection().unselect_all() + self.tree.get_selection().select_path(path) + return self.show_treeview_menu(event) + + elif event.button == 2: # Middle click + try: + model, list_of_paths = self.tree.get_selection().get_selected_rows() + except TypeError: + list_of_paths = [] + if list_of_paths != [path]: + self.tree.get_selection().unselect_all() + self.tree.get_selection().select_path(path) + type_ = model[path][C_TYPE] + if type_ in ('agent', 'contact', 'self_contact', 'groupchat'): + self.on_row_activated(widget, path) + elif type_ == 'account': + account = model[path][C_ACCOUNT].decode('utf-8') + if account != 'all': + show = gajim.connections[account].connected + if show > 1: # We are connected + self.on_change_status_message_activate(widget, account) + return True + show = helpers.get_global_show() + if show == 'offline': + return True + dlg = dialogs.ChangeStatusMessageDialog(show) + message = dlg.run() + if not message: + return True + for acct in gajim.connections: + if not gajim.config.get_per('accounts', acct, + 'sync_with_global_status'): + continue + current_show = gajim.SHOW_LIST[gajim.connections[acct].connected] + self.send_status(acct, current_show, message) + return True + + elif event.button == 1: # Left click + model = self.tree.get_model() + type_ = model[path][C_TYPE] + # x_min is the x start position of status icon column + if gajim.config.get('avatar_position_in_roster') == 'left': + x_min = gajim.config.get('roster_avatar_width') + else: + x_min = 0 + if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ + not event.state & gtk.gdk.CONTROL_MASK: + # Don't handle dubble click if we press icon of a metacontact + iter = model.get_iter(path) + if x > x_min and x < x_min + 27 and type_ == 'contact' and \ + model.iter_has_child(iter): + account = model[path][C_ACCOUNT].decode('utf-8') + jid = model[path][C_JID].decode('utf-8') + # first cell in 1st column (the arrow SINGLE clicked) + iters = self._get_contact_iter(jid, account) + for iter in iters: + path = model.get_path(iter) + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return + # We just save on which row we press button, and open chat window on + # button release to be able to do DND without opening chat window + self.clicked_path = path + return + else: + if type_ == 'group' and x < 27: + # first cell in 1st column (the arrow SINGLE clicked) + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + + elif type_ == 'contact' and x > x_min and x < x_min + 27: + account = model[path][C_ACCOUNT].decode('utf-8') + jid = model[path][C_JID].decode('utf-8') + # first cell in 1st column (the arrow SINGLE clicked) + iters = self._get_contact_iter(jid, account) + for iter in iters: + path = model.get_path(iter) + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + + def on_req_usub(self, widget, list_): + '''Remove a contact. list_ is a list of (contact, account) tuples''' + def on_ok(is_checked, list_): + remove_auth = True + if len(list_) == 1: + contact = list_[0][0] + if contact.sub != 'to' and is_checked: + remove_auth = False + for (contact, account) in list_: + gajim.connections[account].unsubscribe(contact.jid, remove_auth) + for c in gajim.contacts.get_contacts(account, contact.jid): + self.remove_contact(c.jid, account) + gajim.contacts.remove_jid(account, contact.jid) + self.draw_account(account) + if not remove_auth and contact.sub == 'both': + contact.name = '' + contact.groups = [] + contact.sub = 'from' + gajim.contacts.add_contact(account, contact) + self.add_contact(contact.jid, account) + else: + if _('Not in Roster') in contact.groups: + gajim.events.remove_events(account, contact.jid) + self._readd_contact_to_roster_if_needed(contact, account) + + def on_ok2(list_): + on_ok(False, list_) + + if len(list_) == 1: + contact = list_[0][0] + account = list_[0][1] + pritext = _('Contact "%s" will be removed from your roster') % \ + contact.get_shown_name() + if contact.sub == 'to': + dialogs.ConfirmationDialog(pritext, + _('By removing this contact you also remove authorization ' + 'resulting in him or her always seeing you as offline.'), + on_response_ok = (on_ok2, list_)) + else: + dialogs.ConfirmationDialogCheck(pritext, + _('By removing this contact you also by default remove ' + 'authorization resulting in him or her always seeing you as ' + 'offline.'), + _('I want this contact to know my status after removal'), + on_response_ok = (on_ok, list_)) + else: + # several contact to remove at the same time + pritext = _('Contacts will be removed from your roster') + jids = '' + for (contact, account) in list_: + jids += '\n ' + contact.get_shown_name() + ',' + sectext = _('By removing these contacts:%s\nyou also remove ' + 'authorization resulting in them always seeing you as offline.') % \ + jids + dialogs.ConfirmationDialog(pritext, sectext, + on_response_ok = (on_ok2, list_)) + + def on_send_custom_status(self, widget, contact_list, show, group=None): + '''send custom status''' + dlg = dialogs.ChangeStatusMessageDialog(show) + message = dlg.run() + if message is not None: # None if user pressed Cancel + for (contact, account) in contact_list: + our_jid = gajim.get_jid_from_account(account) + accounts = [] + if group and account not in accounts: + if not gajim.interface.status_sent_to_groups.has_key(account): + gajim.interface.status_sent_to_groups[account] = {} + gajim.interface.status_sent_to_groups[account][group] = show + accounts.append(group) + jid = contact.jid + if jid == our_jid: + jid += '/' + contact.resource + self.send_status(account, show, message, to=jid) + if not gajim.interface.status_sent_to_users.has_key(account): + gajim.interface.status_sent_to_users[account] = {} + gajim.interface.status_sent_to_users[account][contact.jid] = show + + def on_status_combobox_changed(self, widget): + '''When we change our status via the combobox''' + model = self.status_combobox.get_model() + active = self.status_combobox.get_active() + if active == -1: # no active item + return + if not self.combobox_callback_active: + self.previous_status_combobox_active = active + return + accounts = gajim.connections.keys() + if len(accounts) == 0: + dialogs.ErrorDialog(_('No account available'), + _('You must create an account before you can chat with other contacts.')) + self.update_status_combobox() + return + status = model[active][2].decode('utf-8') + statuses_unified = helpers.statuses_unified() # status "desync'ed" or not + if (active == 7 and statuses_unified) or (active == 9 and not statuses_unified): + # 'Change status message' selected: + # do not change show, just show change status dialog + status = model[self.previous_status_combobox_active][2].decode('utf-8') + dlg = dialogs.ChangeStatusMessageDialog(status) + message = dlg.run() + if message is not None: # None if user pressed Cancel + for account in accounts: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + current_show = gajim.SHOW_LIST[ + gajim.connections[account].connected] + self.send_status(account, current_show, message) + self.combobox_callback_active = False + self.status_combobox.set_active( + self.previous_status_combobox_active) + self.combobox_callback_active = True + return + # we are about to change show, so save this new show so in case + # after user chooses "Change status message" menuitem + # we can return to this show + self.previous_status_combobox_active = active + connected_accounts = gajim.get_number_of_connected_accounts() + if status == 'invisible': + bug_user = False + for account in accounts: + if connected_accounts < 1 or gajim.account_is_connected(account): + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + # We're going to change our status to invisible + if self.connected_rooms(account): + bug_user = True + break + if bug_user: + dialog = dialogs.ConfirmationDialog( + _('You are participating in one or more group chats'), + _('Changing your status to invisible will result in ' + 'disconnection from those group chats. Are you sure you want to ' + 'go invisible?')) + if dialog.get_response() != gtk.RESPONSE_OK: + self.update_status_combobox() + return + message = self.get_status_message(status) + if message is None: # user pressed Cancel to change status message dialog + self.update_status_combobox() + return + global_sync_accounts = [] + for acct in accounts: + if gajim.config.get_per('accounts', acct, 'sync_with_global_status'): + global_sync_accounts.append(acct) + global_sync_connected_accounts = gajim.get_number_of_connected_accounts( + global_sync_accounts) + for account in accounts: + if not gajim.config.get_per('accounts', account, + 'sync_with_global_status'): + continue + # we are connected (so we wanna change show and status) + # or no account is connected and we want to connect with new show and + # status + + if not global_sync_connected_accounts > 0 or \ + gajim.connections[account].connected > 0: + self.send_status(account, status, message) + self.update_status_combobox() + + def on_preferences_menuitem_activate(self, widget): + if gajim.interface.instances.has_key('preferences'): + gajim.interface.instances['preferences'].window.present() + else: + gajim.interface.instances['preferences'] = config.PreferencesWindow() + + def on_pep_services_menuitem_activate(self, widget, account): + if gajim.interface.instances[account].has_key('pep_services'): + gajim.interface.instances[account]['pep_services'].window.present() + else: + gajim.interface.instances[account]['pep_services'] = \ + config.ManagePEPServicesWindow(account) + + def on_add_new_contact(self, widget, account): + dialogs.AddNewContactWindow(account) + + def on_join_gc_activate(self, widget, account): + '''when the join gc menuitem is clicked, show the join gc window''' + invisible_show = gajim.SHOW_LIST.index('invisible') + if gajim.connections[account].connected == invisible_show: + dialogs.ErrorDialog(_('You cannot join a group chat while you are ' + 'invisible')) + return + if gajim.interface.instances[account].has_key('join_gc'): + gajim.interface.instances[account]['join_gc'].window.present() + else: + # c http://nkour.blogspot.com/2005/05/pythons-init-return-none-doesnt-return.html + try: + gajim.interface.instances[account]['join_gc'] = \ + dialogs.JoinGroupchatWindow(account) + except GajimGeneralException: + pass + + def on_new_chat_menuitem_activate(self, widget, account): + dialogs.NewChatDialog(account) + + def on_contents_menuitem_activate(self, widget): + helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki') + + def on_faq_menuitem_activate(self, widget): + helpers.launch_browser_mailer('url', + 'http://trac.gajim.org/wiki/GajimFaq') + + def on_features_menuitem_activate(self, widget): + features_window.FeaturesWindow() + + def on_about_menuitem_activate(self, widget): + dialogs.AboutDialog() + + def on_accounts_menuitem_activate(self, widget): + if gajim.interface.instances.has_key('accounts'): + gajim.interface.instances['accounts'].window.present() + else: + gajim.interface.instances['accounts'] = config.AccountsWindow() + + def on_file_transfers_menuitem_activate(self, widget): + if gajim.interface.instances['file_transfers'].window.get_property( + 'visible'): + gajim.interface.instances['file_transfers'].window.present() + else: + gajim.interface.instances['file_transfers'].window.show_all() + + def on_history_menuitem_activate(self, widget): + if gajim.interface.instances.has_key('logs'): + gajim.interface.instances['logs'].window.present() + else: + gajim.interface.instances['logs'] = history_window.\ + HistoryWindow() + + def on_show_transports_menuitem_activate(self, widget): + gajim.config.set('show_transports_group', widget.get_active()) + self.refilter_shown_roster_items() + + def on_manage_bookmarks_menuitem_activate(self, widget): + config.ManageBookmarksWindow() + + def on_profile_avatar_menuitem_activate(self, widget, account): + gajim.interface.edit_own_details(account) + + def on_execute_command(self, widget, contact, account, resource=None): + '''Execute command. Full JID needed; if it is other contact, + resource is necessary. Widget is unnecessary, only to be + able to make this a callback.''' + jid = contact.jid + if resource is not None: + jid = jid + u'/' + resource + adhoc_commands.CommandWindow(account, jid) + + def on_roster_window_focus_in_event(self, widget, event): + # roster received focus, so if we had urgency REMOVE IT + # NOTE: we do not have to read the message to remove urgency + # so this functions does that + gtkgui_helpers.set_unset_urgency_hint(widget, False) + + # if a contact row is selected, update colors (eg. for status msg) + # because gtk engines may differ in bg when window is selected + # or not + if len(self._last_selected_contact): + for (jid, account) in self._last_selected_contact: + self.draw_contact(jid, account, selected = True, + focus = True) + + def on_roster_window_focus_out_event(self, widget, event): + # if a contact row is selected, update colors (eg. for status msg) + # because gtk engines may differ in bg when window is selected + # or not + if len(self._last_selected_contact): + for (jid, account) in self._last_selected_contact: + self.draw_contact(jid, account, selected = True, + focus = False) + + def on_roster_window_key_press_event(self, widget, event): + if event.keyval == gtk.keysyms.Escape: + if gajim.interface.msg_win_mgr.mode == \ + MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \ + gajim.interface.msg_win_mgr.one_window_opened(): + # let message window close the tab + return + model, list_of_paths = self.tree.get_selection().get_selected_rows() + if not len(list_of_paths) and gajim.interface.systray_enabled and \ + not gajim.config.get('quit_on_roster_x_button'): + self.tooltip.hide_tooltip() + self.window.hide() + + def on_roster_window_popup_menu(self, widget): + event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) + self.show_treeview_menu(event) + + def on_row_activated(self, widget, path): + '''When an iter is activated (dubblick or single click if gnome is set + this way''' + model = self.tree.get_model() + account = model[path][C_ACCOUNT].decode('utf-8') + type_ = model[path][C_TYPE] + jid = model[path][C_JID].decode('utf-8') + resource = None + contact = None + iter = model.get_iter(path) + if type_ in ('group', 'account'): + if self.tree.row_expanded(path): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + elif jid in gajim.interface.minimized_controls[account]: + self.on_groupchat_maximized(None, jid, account) + else: + first_ev = gajim.events.get_first_event(account, jid) + if not first_ev: + # look in other resources + for c in gajim.contacts.get_contacts(account, jid): + fjid = c.get_full_jid() + first_ev = gajim.events.get_first_event(account, fjid) + if first_ev: + resource = c.resource + break + if not first_ev and model.iter_has_child(iter): + child_iter = model.iter_children(iter) + while not first_ev and child_iter: + child_jid = model[child_iter][C_JID].decode('utf-8') + first_ev = gajim.events.get_first_event(account, child_jid) + if first_ev: + jid = child_jid + else: + child_iter = model.iter_next(child_iter) + session = None + if first_ev: + if first_ev.type_ in ('chat', 'normal'): + session = first_ev.parameters[8] + fjid = jid + if resource: + fjid += '/' + resource + if self.open_event(account, fjid, first_ev): + return + # else + contact = gajim.contacts.get_contact(account, jid, resource) + if not contact or isinstance(contact, list): + contact = \ + gajim.contacts.get_contact_with_highest_priority(account, jid) + if jid == gajim.get_jid_from_account(account): + resource = contact.resource + self.on_open_chat_window(widget, contact, account, \ + resource = resource, session = session) + + def on_roster_treeview_row_activated(self, widget, path, col = 0): + '''When an iter is double clicked: open the first event window''' + if not gajim.single_click: + self.on_row_activated(widget, path) + + def on_roster_treeview_row_expanded(self, widget, iter, path): + '''When a row is expanded change the icon of the arrow''' + self._toggeling_row = True + model = self.tree.get_model() + child_model = model.get_model() + child_iter = model.convert_iter_to_child_iter(iter) + if self.regroup: # merged accounts + accounts = gajim.connections.keys() + else: + accounts = [model[iter][C_ACCOUNT].decode('utf-8')] + type_ = model[iter][C_TYPE] + if type_ == 'group': + child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images['16'][ + 'opened'] + jid = model[iter][C_JID].decode('utf-8') + for account in accounts: + if gajim.groups[account].has_key(jid): # This account has this group + gajim.groups[account][jid]['expand'] = True + #if account + jid in self.collapsed_rows: + # self.collapsed_rows.remove(account + jid) + elif type_ == 'account': + account = accounts[0] # There is only one cause we don't use merge + #if account in self.collapsed_rows: + # self.collapsed_rows.remove(account) + for g in gajim.groups[account]: + groupIter = self._get_group_iter(g, account) + if groupIter and gajim.groups[account][g]['expand']: + pathG = model.get_path(groupIter) + self.tree.expand_row(pathG, False) + self.draw_account(account) + elif type_ == 'contact': + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + self.draw_contact(jid, account) + + self._toggeling_row = False + + def on_roster_treeview_row_collapsed(self, widget, iter, path): + '''When a row is collapsed change the icon of the arrow''' + self._toggeling_row = True + model = self.tree.get_model() + child_model = model.get_model() + child_iter = model.convert_iter_to_child_iter(iter) + if self.regroup: # merged accounts + accounts = gajim.connections.keys() + else: + accounts = [model[iter][C_ACCOUNT].decode('utf-8')] + type_ = model[iter][C_TYPE] + if type_ == 'group': + child_model[child_iter][C_IMG] = gajim.interface.jabber_state_images['16'][ + 'closed'] + jid = model[iter][C_JID].decode('utf-8') + for account in accounts: + if gajim.groups[account].has_key(jid): # This account has this group + gajim.groups[account][jid]['expand'] = False + #if not account + jid in self.collapsed_rows: + # self.collapsed_rows.append(account + jid) + elif type_ == 'account': + account = accounts[0] # There is only one cause we don't use merge + #if not account in self.collapsed_rows: + # self.collapsed_rows.append(account) + self.draw_account(account) + elif type_ == 'contact': + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + self.draw_contact(jid, account) + + self._toggeling_row = False + + + def on_model_row_has_child_toggled(self, model, path, iter): + '''This signal is emitted when a row has gotten the first child row or lost its last child row. + Expand Parent if necessary. + ''' + if self._toggeling_row: + # Ugly hack: (workaround) + # Signal is emitted when we write to our model + return + + type_ = model[iter][C_TYPE] + account = model[iter][C_ACCOUNT] + if not account: + print "Yet another thing that SHOULD NOT happend....", model,path,iter,account,type_ + return + + account = account.decode('utf-8') + child_iter = model.convert_iter_to_child_iter(iter) + + if type_ == 'account': + if not self.filtering: + # Only expand row when we create and add the account to the roster + self.tree.expand_row(path, False) + + if type_ == 'group': + group = model[iter][C_JID].decode('utf-8') + if not group: + return + if gajim.groups[account].has_key(group) and gajim.groups[account][group]['expand']: + # Ugly workaround. When we expand the last groupchat the entry is removd from the dict + self.tree.expand_row(path, False) + else: + self.tree.collapse_row(path) + + #if account in self.collapsed_rows or\ + #account + group in self.collapsed_rows: + # print "collapsing group", group + # self.tree.collapse_row(path, False) + #else: + # #self.tree.collapse_row(path[:-1], False) + # print "expanding group", group + # self.tree.expand_to_path(path) + + if type_ == 'contact' and self.model.iter_has_child(child_iter): + # we are a bigbrother metacontact + # redraw us to show/hide expand icon + jid = model[iter][C_JID].decode('utf-8') + if self.filtering: + # Prevent endless loops + gobject.idle_add(self.draw_contact, jid, account) + + + # FIXME: Looks like it is unneeded: If yes, remove it + + #def on_treeview_selection_changed(self, selection): + # if self.filtering: + # print "async problem" + # return + # model, list_of_paths = selection.get_selected_rows() + # if len(self._last_selected_contact): + # # update unselected rows + # for (jid, account) in self._last_selected_contact: + # #try: + # self.draw_contact(jid, account) + # #except: + # # This can fail when last selected row was on an account we just + # # removed. So we don't care if that fail + # # pass + # self._last_selected_contact = [] + # if len(list_of_paths) == 0: + # return + # for path in list_of_paths: + # row = model[path] + # if row[C_TYPE] != 'contact': + # self._last_selected_contact = [] + # return + # jid = row[C_JID].decode('utf-8') + # account = row[C_ACCOUNT].decode('utf-8') + # self._last_selected_contact.append((jid, account)) + # self.draw_contact(jid, account, selected = True) + + def on_service_disco_menuitem_activate(self, widget, account): + server_jid = gajim.config.get_per('accounts', account, 'hostname') + if gajim.interface.instances[account]['disco'].has_key(server_jid): + gajim.interface.instances[account]['disco'][server_jid].\ + window.present() + else: + try: + # Object will add itself to the window dict + disco.ServiceDiscoveryWindow(account, address_entry = True) + except GajimGeneralException: + pass + + def on_show_offline_contacts_menuitem_activate(self, widget): + '''when show offline option is changed: + redraw the treeview''' + gajim.config.set('showoffline', not gajim.config.get('showoffline')) + self.refilter_shown_roster_items() + + def on_view_menu_activate(self, widget): + # Hide the show roster menu if we are not in the right windowing mode. + if self.hpaned.get_child2() is not None: + self.xml.get_widget('show_roster_menuitem').show() + else: + self.xml.get_widget('show_roster_menuitem').hide() + + def on_show_roster_menuitem_toggled(self, widget): + # when num controls is 0 this menuitem is hidden, but still need to + # disable keybinding + if self.hpaned.get_child2() is not None: + self.show_roster_vbox(widget.get_active()) + +################################################################################ +### Drag and Drop handling +################################################################################ + + def drag_data_get_data(self, treeview, context, selection, target_id, etime): + model, list_of_paths = self.tree.get_selection().get_selected_rows() + if len(list_of_paths) != 1: + return + path = list_of_paths[0] + data = '' + if len(path) >= 3: + data = model[path][C_JID] + selection.set(selection.target, 8, data) + + def drag_begin(self, treeview, context): + self.dragging = True + + def drag_end(self, treeview, context): + self.dragging = False + + def on_drop_in_contact(self, widget, account_source, c_source, account_dest, + c_dest, was_big_brother, context, etime): + + if not gajim.connections[account_source].private_storage_supported or not\ + gajim.connections[account_dest].private_storage_supported: + dialogs.WarningDialog(_('Metacontacts storage not supported by your ' + 'server'), + _('Your server does not support storing metacontacts information. ' + 'So those information will not be saved on next reconnection.')) + + def merge_contacts(is_checked=None): + if is_checked != None: # dialog has been shown + if is_checked: # user does not want to be asked again + gajim.config.set('confirm_metacontacts', 'no') + else: + gajim.config.set('confirm_metacontacts', 'yes') + + # We might have dropped on a metacontact. Readd it. + dest_family = gajim.contacts.get_metacontacts_family(account_dest, + c_dest.jid) + if dest_family: + self._remove_metacontact_family(dest_family) + else: + self._remove_entity(c_dest, account_dest) + + old_family = gajim.contacts.get_metacontacts_family(account_source, + c_source.jid) + old_groups = c_source.groups + + # Remove old source contact(s) + if was_big_brother: + # We have got little brothers. Readd them all + self._remove_metacontact_family(old_family) + else: + # We are only a litle brother. Simply remove us from our big brother + self._remove_entity(c_source, account_source) + + own_data = {} + own_data['jid'] = c_source.jid + own_data['account'] = account_source + old_family.append(own_data) + + # Apply new tag and update contact + for data in old_family: + _account = data['account'] + _jid = data['jid'] + _contact = gajim.contacts.get_first_contact_from_jid(_account, _jid) + + _contact.groups = c_dest.groups[:] + gajim.contacts.add_metacontact(account_dest, c_dest.jid, + _account, _contact.jid) + gajim.connections[account_source].update_contact(_contact.jid, + _contact.name, _contact.groups) + + # Re-add all and update GUI + new_family = gajim.contacts.get_metacontacts_family(account_source, + c_source.jid) + brothers = self._add_metacontact_family(new_family) + + for c, acc in brothers: + self.draw_contact(c.jid, acc) + self.draw_avatar(c.jid, acc) + + old_groups.extend(c_dest.groups) + for g in old_groups: + self.draw_group(g, account_source) + + self.draw_account(account_source) + context.finish(True, True, etime) + + confirm_metacontacts = gajim.config.get('confirm_metacontacts') + if confirm_metacontacts == 'no': + merge_contacts() + return + pritext = _('You are about to create a metacontact. Are you sure you want' + ' to continue?') + sectext = _('Metacontacts are a way to regroup several contacts in one ' + 'line. Generally it is used when the same person has several Jabber ' + 'accounts or transport accounts.') + dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, + _('Do _not ask me again'), on_response_ok = merge_contacts) + if not confirm_metacontacts: # First time we see this window + dlg.checkbutton.set_active(True) + + + def on_drop_in_group(self, widget, account, c_source, grp_dest, is_big_brother, + context, etime, grp_source = None): + if is_big_brother: + # add whole metacontact to new group + self.remove_contact_from_groups(c_source.jid, account, [grp_source,]) + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + else: + # Normal contact or little brother + family = gajim.contacts.get_metacontacts_family(account, + c_source.jid) + if family: + # Little brother + # Remove whole family. Remove us from the family. + # Then re-add other family members. + self._remove_metacontact_family(family) + gajim.contacts.remove_metacontact(account, c_source.jid) + for data in family: + if data['jid'] == c_source.jid: + continue + self.add_contact(data['jid'], data['account']) + break; + + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + + else: + # Simple contact + self.remove_contact_from_groups(c_source.jid, account, [grp_source,]) + self.add_contact_to_groups(c_source.jid, account, [grp_dest,]) + + if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY): + context.finish(True, True, etime) + + + def drag_drop(self, treeview, context, x, y, timestamp): + target_list = treeview.drag_dest_get_target_list() + target = treeview.drag_dest_find_target(context, target_list) + selection = treeview.drag_get_data(context, target) + context.finish(False, True) + return True + + def drag_data_received_data(self, treeview, context, x, y, selection, info, + etime): + treeview.stop_emission('drag_data_received') + drop_info = treeview.get_dest_row_at_pos(x, y) + if not drop_info: + return + if not selection.data: + return # prevents tb when several entrys are dragged + model = treeview.get_model() + data = selection.data + path_dest, position = drop_info + + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \ + and path_dest[1] == 0: # dropped before the first group + return + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: + # dropped before a group: we drop it in the previous group every time + path_dest = (path_dest[0], path_dest[1]-1) + # destination: the row something got dropped on + iter_dest = model.get_iter(path_dest) + type_dest = model[iter_dest][C_TYPE].decode('utf-8') + jid_dest = model[iter_dest][C_JID].decode('utf-8') + account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8') + + # drop on account row in merged mode, we cannot know the desired account + if account_dest == 'all': + return + # nothing can be done, if destination account is offline + if gajim.connections[account_dest].connected < 2: + return + + # A file got dropped on the roster + if info == self.TARGET_TYPE_URI_LIST: + if len(path_dest) < 3: + return + if type_dest != 'contact': + return + c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, + jid_dest) + uri = data.strip() + uri_splitted = uri.split() # we may have more than one file dropped + nb_uri = len(uri_splitted) + # Check the URIs + bad_uris = [] + for a_uri in uri_splitted: + path = helpers.get_file_path_from_dnd_dropped_uri(a_uri) + if not os.path.isfile(path): + bad_uris.append(a_uri) + if len(bad_uris): + dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) + return + def _on_send_files(account, jid, uris): + c = gajim.contacts.get_contact_with_highest_priority(account, jid) + for uri in uris: + path = helpers.get_file_path_from_dnd_dropped_uri(uri) + if os.path.isfile(path): # is it file? + gajim.interface.instances['file_transfers'].send_file( + account, c, path) + # Popup dialog to confirm sending + prim_text = 'Send file?' + sec_text = i18n.ngettext('Do you want to send this file to %s:', + 'Do you want to send those files to %s:', nb_uri) %\ + c_dest.get_shown_name() + for uri in uri_splitted: + path = helpers.get_file_path_from_dnd_dropped_uri(uri) + sec_text += '\n' + os.path.basename(path) + dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, + on_response_ok = (_on_send_files, account_dest, jid_dest, + uri_splitted)) + dialog.popup() + return + + # a roster entry was dragged and dropped somewhere in the roster + + # source: the row that was dragged + path_source = treeview.get_selection().get_selected_rows()[1][0] + iter_source = model.get_iter(path_source) + 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 + + # 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 + while model[it][C_TYPE] == 'contact': + it = model.iter_parent(it) + grp_source = model[it][C_JID].decode('utf-8') + if grp_source in helpers.special_groups and \ + grp_source not in ('Not in Roster', 'Observers'): + # a transport or a minimized groupchat was dragged + # we can add it to other accounts but not move it to another group, see below + return + jid_source = data.decode('utf-8') + c_source = gajim.contacts.get_contact_with_highest_priority( + account_source, jid_source) + + # Get destination group + grp_dest = None + if type_dest == 'group': + grp_dest = model[iter_dest][C_JID].decode('utf-8') + elif type_dest in ('contact', 'agent'): + it = iter_dest + while model[it][C_TYPE] != 'group': + it = model.iter_parent(it) + grp_dest = model[it][C_JID].decode('utf-8') + if grp_dest in helpers.special_groups: + return + + if jid_source == jid_dest: + if grp_source == grp_dest and account_source == account_dest: + # Drop on self + return + + # contact drop somewhere in or on a foreign account + if (type_dest == 'account' or not self.regroup) and \ + account_source != account_dest: + # add to account in specified group + dialogs.AddNewContactWindow(account = account_dest, jid = jid_source, + user_nick = c_source.name, group = grp_dest) + return + + # we may not add contacts from special_groups + if grp_source in helpers.special_groups : + return + + # Is the contact we drag a meta contact? + is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source) + + # Contact drop on group row or between two contacts + if type_dest == 'group' or position == gtk.TREE_VIEW_DROP_BEFORE or \ + position == gtk.TREE_VIEW_DROP_AFTER: + self.on_drop_in_group(None, account_source, c_source, grp_dest, + is_big_brother, context, etime, grp_source) + return + + # Contact drop on another contact, make meta contacts + if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \ + position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE: + c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, + jid_dest) + if not c_dest: + # c_dest is None if jid_dest doesn't belong to account + return + self.on_drop_in_contact(treeview, account_source, c_source, + account_dest, c_dest, is_big_brother, context, etime) + return + +################################################################################ +### Everything about images and icons.... +### Cleanup assigned to Jim++ :-) +################################################################################ + + def get_appropriate_state_images(self, jid, size = '16', + icon_name = 'online'): + '''check jid and return the appropriate state images dict for + the demanded size. icon_name is taken into account when jid is from + transport: transport iconset doesn't contain all icons, so we fall back + to jabber one''' + transport = gajim.get_transport_name_from_jid(jid) + if transport and self.transports_state_images.has_key(size): + if not self.transports_state_images[size].has_key(transport): + # we don't have iconset for this transport loaded yet. Let's do it + self.make_transport_state_images(transport) + if self.transports_state_images[size].has_key(transport) and \ + icon_name in self.transports_state_images[size][transport]: + return self.transports_state_images[size][transport] + return gajim.interface.jabber_state_images[size] + + + def make_transport_state_images(self, transport): + '''initialise opened and closed 'transport' iconset dict''' + if gajim.config.get('use_transports_iconsets'): + folder = os.path.join(helpers.get_transport_path(transport), + '16x16') + pixo, pixc = gtkgui_helpers.load_icons_meta() + self.transports_state_images['opened'][transport] = \ + gtkgui_helpers.load_iconset(folder, pixo, transport = True) + self.transports_state_images['closed'][transport] = \ + gtkgui_helpers.load_iconset(folder, pixc, transport = True) + folder = os.path.join(helpers.get_transport_path(transport), '32x32') + self.transports_state_images['32'][transport] = gtkgui_helpers.load_iconset( + folder, transport = True) + folder = os.path.join(helpers.get_transport_path(transport), '16x16') + self.transports_state_images['16'][transport] = gtkgui_helpers.load_iconset( + folder, transport = True) + + def update_jabber_state_images(self): + # Update the roster + self.draw_roster() + # Update the status combobox + model = self.status_combobox.get_model() + iter = model.get_iter_root() + while iter: + if model[iter][2] != '': + # If it's not change status message iter + # eg. if it has show parameter not '' + model[iter][1] = gajim.interface.jabber_state_images['16'][model[iter][2]] + iter = model.iter_next(iter) + # Update the systray + if gajim.interface.systray_enabled: + gajim.interface.systray.set_img() + + for win in gajim.interface.msg_win_mgr.windows(): + for ctrl in win.controls(): + ctrl.update_ui() + win.redraw_tab(ctrl) + + self.update_status_combobox() + + def set_account_status_icon(self, account): + status = gajim.connections[account].connected + model = self.tree.get_model() + child_model = model.get_model() + iterA = self._get_account_iter(account) + if not iterA: + return + child_iterA = model.convert_iter_to_child_iter(iterA) + if not self.regroup: + show = gajim.SHOW_LIST[status] + else: # accounts merged + show = helpers.get_global_show() + child_model[child_iterA][C_IMG] = gajim.interface.jabber_state_images['16'][show] + +################################################################################ +### Style and theme related methods +################################################################################ + + def show_title(self): + change_title_allowed = gajim.config.get('change_roster_title') + if not change_title_allowed: + return + + if gajim.config.get('one_message_window') == 'always_with_roster': + # always_with_roster mode defers to the MessageWindow + if not gajim.interface.msg_win_mgr.one_window_opened(): + # No MessageWindow to defer to + self.window.set_title('Gajim') + return + + nb_unread = 0 + start = '' + for account in gajim.connections: + # Count events in roster title only if we don't auto open them + if not helpers.allow_popup_window(account): + nb_unread += gajim.events.get_nb_events(['chat', 'normal', + 'file-request', 'file-error', 'file-completed', + 'file-request-error', 'file-send-error', 'file-stopped', + 'printed_chat'], account) + if nb_unread > 1: + start = '[' + str(nb_unread) + '] ' + elif nb_unread == 1: + start = '* ' + self.window.set_title(start + 'Gajim') + + gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread) + + def _change_style(self, model, path, iter, option): + if option is None or model[iter][C_TYPE] == option: + # We changed style for this type of row + model[iter][C_NAME] = model[iter][C_NAME] + + def change_roster_style(self, option): + model = self.tree.get_model() + child_model = model.get_model() + child_model.foreach(self._change_style, option) + for win in gajim.interface.msg_win_mgr.windows(): + win.repaint_themed_widgets() + + def repaint_themed_widgets(self): + '''Notify windows that contain themed widgets to repaint them''' + for win in gajim.interface.msg_win_mgr.windows(): + win.repaint_themed_widgets() + for account in gajim.connections: + for addr in gajim.interface.instances[account]['disco']: + gajim.interface.instances[account]['disco'][addr].paint_banner() + for ctrl in gajim.interface.minimized_controls[account].values(): + ctrl.repaint_themed_widgets() + + def update_avatar_in_gui(self, jid, account): + # Update roster + self.draw_avatar(jid, account) + # Update chat window + if gajim.interface.msg_win_mgr.has_window(jid, account): + win = gajim.interface.msg_win_mgr.get_window(jid, account) + ctrl = win.get_control(jid, account) + if win and ctrl.type_id != message_control.TYPE_GC: + ctrl.show_avatar() + + def on_roster_treeview_style_set(self, treeview, style): + '''When style (theme) changes, redraw all contacts''' + for contact in self._iter_contact_rows(): + self.draw_contact(contact[C_JID].decode('utf-8'), + contact[C_ACCOUNT].decode('utf-8')) + + def set_renderer_color(self, renderer, style, set_background = True): + '''set style for treeview cell, using PRELIGHT system color''' + if set_background: + bgcolor = self.tree.style.bg[style] + renderer.set_property('cell-background-gdk', bgcolor) + else: + fgcolor = self.tree.style.fg[style] + renderer.set_property('foreground-gdk', fgcolor) + + def _iconCellDataFunc(self, column, renderer, model, iter, data = None): + '''When a row is added, set properties for icon renderer''' + theme = gajim.config.get('roster_theme') + type_ = model[iter][C_TYPE] + if type_ == 'account': + color = gajim.config.get_per('themes', theme, 'accountbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE) + renderer.set_property('xalign', 0) + elif type_ == 'group': + color = gajim.config.get_per('themes', theme, 'groupbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) + renderer.set_property('xalign', 0.2) + elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[iter][C_JID] or not model[iter][C_ACCOUNT]: + # This can append when at the moment we add the row + return + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + if jid in gajim.newly_added[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_connected_bg_color')) + elif jid in gajim.to_be_removed[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_disconnected_bg_color')) + else: + color = gajim.config.get_per('themes', theme, 'contactbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + renderer.set_property('cell-background', None) + parent_iter = model.iter_parent(iter) + if model[parent_iter][C_TYPE] == 'contact': + renderer.set_property('xalign', 1) + else: + renderer.set_property('xalign', 0.4) + renderer.set_property('width', 26) + + def _nameCellDataFunc(self, column, renderer, model, iter, data = None): + '''When a row is added, set properties for name renderer''' + theme = gajim.config.get('roster_theme') + type_ = model[iter][C_TYPE] + if type_ == 'account': + color = gajim.config.get_per('themes', theme, 'accounttextcolor') + if color: + renderer.set_property('foreground', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False) + color = gajim.config.get_per('themes', theme, 'accountbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) + renderer.set_property('xpad', 0) + renderer.set_property('width', 3) + elif type_ == 'group': + color = gajim.config.get_per('themes', theme, 'grouptextcolor') + if color: + renderer.set_property('foreground', color) + else: + self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False) + color = gajim.config.get_per('themes', theme, 'groupbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + renderer.set_property('xpad', 4) + elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[iter][C_JID] or not model[iter][C_ACCOUNT]: + # This can append when at the moment we add the row + return + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + color = gajim.config.get_per('themes', theme, 'contacttextcolor') + if color: + renderer.set_property('foreground', color) + else: + renderer.set_property('foreground', None) + if jid in gajim.newly_added[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_connected_bg_color')) + elif jid in gajim.to_be_removed[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_disconnected_bg_color')) + else: + color = gajim.config.get_per('themes', theme, 'contactbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + renderer.set_property('cell-background', None) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + parent_iter = model.iter_parent(iter) + if model[parent_iter][C_TYPE] == 'contact': + renderer.set_property('xpad', 16) + else: + renderer.set_property('xpad', 8) + + def _fill_avatar_pixbuf_rederer(self, column, renderer, model, iter, + data = None): + '''When a row is added, set properties for avatar renderer''' + theme = gajim.config.get('roster_theme') + type_ = model[iter][C_TYPE] + if type_ in ('group', 'account'): + renderer.set_property('visible', False) + return + + # allocate space for the icon only if needed + if model[iter][C_AVATAR_PIXBUF] or \ + gajim.config.get('avatar_position_in_roster') == 'left': + renderer.set_property('visible', True) + else: + renderer.set_property('visible', False) + if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 + if not model[iter][C_JID] or not model[iter][C_ACCOUNT]: + # This can append at the moment we add the row + return + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + if jid in gajim.newly_added[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_connected_bg_color')) + elif jid in gajim.to_be_removed[account]: + renderer.set_property('cell-background', gajim.config.get( + 'just_disconnected_bg_color')) + else: + color = gajim.config.get_per('themes', theme, 'contactbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + renderer.set_property('cell-background', None) + if gajim.config.get('avatar_position_in_roster') == 'left': + renderer.set_property('width', gajim.config.get('roster_avatar_width')) + renderer.set_property('xalign', 0.5) + else: + renderer.set_property('xalign', 1) # align pixbuf to the right + + def _fill_padlock_pixbuf_rederer(self, column, renderer, model, iter, + data = None): + '''When a row is added, set properties for padlock renderer''' + theme = gajim.config.get('roster_theme') + type_ = model[iter][C_TYPE] + # allocate space for the icon only if needed + if type_ == 'account' and model[iter][C_PADLOCK_PIXBUF]: + renderer.set_property('visible', True) + color = gajim.config.get_per('themes', theme, 'accountbgcolor') + if color: + renderer.set_property('cell-background', color) + else: + self.set_renderer_color(renderer, gtk.STATE_ACTIVE) + renderer.set_property('xalign', 1) # align pixbuf to the right + else: + renderer.set_property('visible', False) + +################################################################################ +### Everything about building menus +### FIXME: We really need to make it simpler! 1465 lines are a few to much.... +################################################################################ def make_menu(self, force = False): '''create the main window\'s menus''' @@ -1172,7 +4492,7 @@ class RosterWindow: account) self.advanced_menus.append(advanced_menuitem_menu) - self._add_history_manager_menuitem(advanced_menuitem_menu) + self.add_history_manager_menuitem(advanced_menuitem_menu) advanced_menuitem.set_submenu(advanced_menuitem_menu) advanced_menuitem_menu.show_all() @@ -1190,7 +4510,7 @@ class RosterWindow: self.advanced_menus.append(advanced_menuitem_menu) advanced_item.set_submenu(advanced_menuitem_menu) - self._add_history_manager_menuitem(advanced_sub_menu) + self.add_history_manager_menuitem(advanced_sub_menu) advanced_menuitem.set_submenu(advanced_sub_menu) advanced_sub_menu.show_all() @@ -1198,1784 +4518,8 @@ class RosterWindow: if sys.platform == 'darwin': syncmenu.takeover_menu(self.xml.get_widget('menubar')) - self.actions_menu_needs_rebuild = False - - def _add_history_manager_menuitem(self, menu): - '''adds a seperator and History Manager menuitem BELOW for account - menuitems''' - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # History manager - item = gtk.ImageMenuItem(_('History Manager')) - icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, - gtk.ICON_SIZE_MENU) - item.set_image(icon) - menu.append(item) - item.connect('activate', self.on_history_manager_menuitem_activate) - - def add_bookmarks_list(self, gc_sub_menu, account): - '''Show join new group chat item and bookmarks list for an account''' - item = gtk.ImageMenuItem(_('_Join New Group Chat')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', self.on_join_gc_activate, account) - gc_sub_menu.append(item) - - # user has at least one bookmark - if len(gajim.connections[account].bookmarks) > 0: - item = gtk.SeparatorMenuItem() # separator - gc_sub_menu.append(item) - - for bookmark in gajim.connections[account].bookmarks: - item = gtk.MenuItem(bookmark['name'], False) # Do not use underline - item.connect('activate', self.on_bookmark_menuitem_activate, - account, bookmark) - gc_sub_menu.append(item) - - def _change_style(self, model, path, iter, option): - if option is None or model[iter][C_TYPE] == option: - # We changed style for this type of row - model[iter][C_NAME] = model[iter][C_NAME] - - def change_roster_style(self, option): - model = self.tree.get_model() - model.foreach(self._change_style, option) - for win in gajim.interface.msg_win_mgr.windows(): - win.repaint_themed_widgets() - - def draw_roster(self): - '''clear and draw roster''' - # clear the model, only if it is not empty - model = self.tree.get_model() - if model: - model.clear() - for acct in gajim.connections: - self.add_account_to_roster(acct) - self.add_account_contacts(acct) - # Recalculate column width for ellipsizing - self.tree.columns_autosize() - - def add_account_contacts(self, account): - '''adds contacts of group to roster treeview''' - for jid in gajim.contacts.get_jid_list(account): - self.add_contact_to_roster(jid, account) - self.draw_account(account) - - def fire_up_unread_messages_events(self, account): - '''reads from db the unread messages, and fire them up, and - if we find very old unread messages, delete them from unread table''' - results = gajim.logger.get_unread_msgs() - for result in results: - jid = result[4] - if gajim.contacts.get_first_contact_from_jid(account, jid): - # We have this jid in our contacts list - # XXX unread messages should probably have their session saved with - # them - session = gajim.connections[account].make_new_session(jid) - - tim = time.localtime(float(result[2])) - self.on_message(jid, result[1], tim, account, msg_type = 'chat', - msg_id = result[0], session = session) - - elif (time.time() - result[2]) > 2592000: - # ok, here we see that we have a message in unread messages table - # that is older than a month. It is probably from someone not in our - # roster for accounts we usually launch, so we will delete this id - # from unread message tables. - gajim.logger.set_read_messages([result[0]]) - - def fill_contacts_and_groups_dicts(self, array, account): - '''fill gajim.contacts and gajim.groups''' - if account not in gajim.contacts.get_accounts(): - gajim.contacts.add_account(account) - if not gajim.groups.has_key(account): - gajim.groups[account] = {} - for jid in array.keys(): - # Remove old Contact instances - gajim.contacts.remove_jid(account, jid, remove_meta=False) - jids = jid.split('/') - # get jid - ji = jids[0] - # get resource - resource = '' - if len(jids) > 1: - resource = '/'.join(jids[1:]) - # get name - name = array[jid]['name'] - if not name: - name = '' - show = 'offline' # show is offline by default - status = '' # no status message by default - - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact1 = gajim.contacts.create_contact(jid = ji, name = name, - groups = array[jid]['groups'], show = show, status = status, - sub = array[jid]['subscription'], ask = array[jid]['ask'], - resource = resource, keyID = keyID) - gajim.contacts.add_contact(account, contact1) - - # when we draw the roster, we avoid having the same contact - # more than once (f.e. we avoid showing it twice when 2 resources) - for g in array[jid]['groups']: - if g in gajim.groups[account].keys(): - continue - - if account + g in self.collapsed_rows: - ishidden = False - else: - ishidden = True - gajim.groups[account][g] = { 'expand': ishidden } - if gajim.config.get('ask_avatars_on_startup'): - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(ji) - if pixbuf == 'ask': - transport = gajim.get_transport_name_from_jid(contact1.jid) - if not transport or gajim.jid_is_transport(contact1.jid): - jid_with_resource = contact1.jid - if contact1.resource: - jid_with_resource += '/' + contact1.resource - gajim.connections[account].request_vcard(jid_with_resource) - else: - host = gajim.get_server_from_jid(contact1.jid) - if not gajim.transport_avatar[account].has_key(host): - gajim.transport_avatar[account][host] = [contact1.jid] - else: - gajim.transport_avatar[account][host].append(contact1.jid) - # If we already have a chat window opened, update it with new contact - # instance - chat_control = gajim.interface.msg_win_mgr.get_control(ji, account) - if chat_control: - chat_control.contact = contact1 - - def chg_contact_status(self, contact, show, status, account): - '''When a contact changes his or her status''' - contact_instances = gajim.contacts.get_contacts(account, contact.jid) - contact.show = show - contact.status = status - # name is to show in conversation window - name = contact.get_shown_name() - - if len(contact_instances) > 1: - if contact.resource != '': - name += '/' + contact.resource - if show in ('offline', 'error') and \ - len(gajim.events.get_events(account, contact.get_full_jid())) == 0: - jid_with_resource = contact.jid + '/' + contact.resource - if gajim.interface.msg_win_mgr.has_window(jid_with_resource, - account): - win = gajim.interface.msg_win_mgr.get_window(jid_with_resource, - account) - ctrl = win.get_control(jid_with_resource, account) - ctrl.update_ui() - win.redraw_tab(ctrl) - gajim.contacts.remove_contact(account, contact) - self.remove_contact(contact, account) - self.add_contact_to_roster(contact.jid, account) - # print status in chat window and update status/GPG image - if gajim.interface.msg_win_mgr.has_window(contact.jid, account): - win = gajim.interface.msg_win_mgr.get_window(contact.jid, account) - ctrl = win.get_control(contact.jid, account) - ctrl.contact = gajim.contacts.get_contact_with_highest_priority( - account, contact.jid) - ctrl.update_ui() - win.redraw_tab(ctrl) - - uf_show = helpers.get_uf_show(show) - ctrl.print_conversation(_('%s is now %s') % (name, uf_show), - 'status') - if status: - ctrl.print_conversation(' (', 'status', simple=True) - ctrl.print_conversation('%s' % (status), 'status', simple=True) - ctrl.print_conversation(')', 'status', simple=True) - - # unset custom status - if gajim.interface.status_sent_to_users.has_key(account) and \ - contact.jid in gajim.interface.status_sent_to_users[account]: - del gajim.interface.status_sent_to_users[account][contact.jid] - - if not contact.groups: - self.draw_group(_('General'), account) - else: - for group in contact.groups: - self.draw_group(group, account) - - self.draw_account(account) - - def on_info(self, widget, contact, account): - '''Call vcard_information_window class to display contact's information''' - if gajim.connections[account].is_zeroconf: - self.on_info_zeroconf(widget, contact, account) - return - - info = gajim.interface.instances[account]['infos'] - if info.has_key(contact.jid): - info[contact.jid].window.present() - else: - info[contact.jid] = vcard.VcardWindow(contact, account) - - def on_info_zeroconf(self, widget, contact, account): - info = gajim.interface.instances[account]['infos'] - if info.has_key(contact.jid): - info[contact.jid].window.present() - else: - contact = gajim.contacts.get_first_contact_from_jid(account, - contact.jid) - if contact.show in ('offline', 'error'): - # don't show info on offline contacts - return - info[contact.jid] = vcard.ZeroconfVcardWindow(contact, account) - - def show_tooltip(self, contact): - pointer = self.tree.get_pointer() - props = self.tree.get_path_at_pos(pointer[0], pointer[1]) - # check if the current pointer is at the same path - # as it was before setting the timeout - if props and self.tooltip.id == props[0]: - # bounding rectangle of coordinates for the cell within the treeview - rect = self.tree.get_cell_area(props[0], props[1]) - - # position of the treeview on the screen - position = self.tree.window.get_origin() - self.tooltip.show_tooltip(contact, rect.height, position[1] + rect.y) - else: - self.tooltip.hide_tooltip() - - def on_roster_treeview_leave_notify_event(self, widget, event): - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def on_roster_treeview_motion_notify_event(self, widget, event): - model = widget.get_model() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - [row, col, x, y] = props - iter = None - try: - iter = model.get_iter(row) - except: - self.tooltip.hide_tooltip() - return - if model[iter][C_TYPE] in ('contact', 'self_contact'): - # we're on a contact entry in the roster - account = model[iter][C_ACCOUNT].decode('utf-8') - jid = model[iter][C_JID].decode('utf-8') - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - contacts = gajim.contacts.get_contacts(account, jid) - connected_contacts = [] - for c in contacts: - if c.show not in ('offline', 'error'): - connected_contacts.append(c) - if not connected_contacts: - # no connected contacts, show the ofline one - connected_contacts = contacts - self.tooltip.account = account - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, connected_contacts) - elif model[iter][C_TYPE] == 'groupchat': - account = model[iter][C_ACCOUNT].decode('utf-8') - jid = model[iter][C_JID].decode('utf-8') - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - contact = gajim.contacts.get_contacts(account, jid) - self.tooltip.account = account - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, contact) - elif model[iter][C_TYPE] == 'account': - # we're on an account entry in the roster - account = model[iter][C_ACCOUNT].decode('utf-8') - if account == 'all': - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.account = None - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, []) - return - jid = gajim.get_jid_from_account(account) - contacts = [] - connection = gajim.connections[account] - # get our current contact info - - nbr_on, nbr_total = gajim.contacts.get_nb_online_total_contacts( - accounts = [account]) - account_name = account - if gajim.account_is_connected(account): - account_name += ' (%s/%s)' % (repr(nbr_on), repr(nbr_total)) - contact = gajim.contacts.create_contact(jid = jid, - name = account_name, show = connection.get_status(), sub = '', - status = connection.status, - resource = connection.server_resource, - priority = connection.priority, - mood = connection.mood, - tune = connection.tune, - activity = connection.activity) - if gajim.connections[account].gpg: - contact.keyID = gajim.config.get_per('accounts', connection.name, - 'keyid') - contacts.append(contact) - # if we're online ... - if connection.connection: - roster = connection.connection.getRoster() - # in threadless connection when no roster stanza is sent, - # 'roster' is None - if roster and roster.getItem(jid): - resources = roster.getResources(jid) - # ...get the contact info for our other online resources - for resource in resources: - # Check if we already have this resource - found = False - for contact_ in contacts: - if contact_.resource == resource: - found = True - break - if found: - continue - show = roster.getShow(jid+'/'+resource) - if not show: - show = 'online' - contact = gajim.contacts.create_contact(jid = jid, - name = account, show = show, - status = roster.getStatus(jid+'/'+resource), - resource = resource, - priority = roster.getPriority(jid+'/'+resource)) - contacts.append(contact) - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - self.tooltip.account = None - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, contacts) - - def on_agent_logging(self, widget, jid, state, account): - '''When an agent is requested to log in or off''' - gajim.connections[account].send_agent_status(jid, state) - - def on_edit_agent(self, widget, contact, account): - '''When we want to modify the agent registration''' - gajim.connections[account].request_register_agent_info(contact.jid) - - def on_remove_agent(self, widget, list_): - '''When an agent is requested to be removed. list_ is a list of - (contact, account) tuple''' - for (contact, account) in list_: - if gajim.config.get_per('accounts', account, 'hostname') == \ - contact.jid: - # We remove the server contact - # remove it from treeview - gajim.connections[account].unsubscribe(contact.jid) - self.remove_contact(contact, account) - gajim.contacts.remove_contact(account, contact) - return - - def remove(list_): - for (contact, account) in list_: - full_jid = contact.get_full_jid() - gajim.connections[account].unsubscribe_agent(full_jid) - # remove transport from treeview - self.remove_contact(contact, account) - gajim.contacts.remove_jid(account, contact.jid) - gajim.contacts.remove_contact(account, contact) - - # Check if there are unread events from some contacts - has_unread_events = False - for (contact, account) in list_: - for jid in gajim.events.get_events(account): - if jid.endswith(contact.jid): - has_unread_events = True - break - if has_unread_events: - dialogs.ErrorDialog(_('You have unread messages'), - _('You must read them before removing this transport.')) - return - if len(list_) == 1: - pritext = _('Transport "%s" will be removed') % contact.jid - sectext = _('You will no longer be able to send and receive messages ' - 'from contacts using this transport.') - else: - pritext = _('Transports will be removed') - jids = '' - for (contact, account) in list_: - jids += '\n ' + contact.get_shown_name() + ',' - jids = jids[:-1] + '.' - sectext = _('You will no longer be able to send and receive messages ' - 'to contacts from these transports: %s') % jids - dialogs.ConfirmationDialog(pritext, sectext, - on_response_ok = (remove, list_)) - - def on_block(self, widget, iter, group_list): - ''' When clicked on the 'block' button in context menu. ''' - model = self.tree.get_model() - accounts = [] - msg = self.get_status_message('offline') - if group_list == None: - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - accounts.append(account) - self.send_status(account, 'offline', msg, to = jid) - new_rule = {'order': u'1', 'type': u'jid', 'action': u'deny', - 'value' : jid, 'child': [u'message', u'iq', u'presence-out']} - gajim.connections[account].blocked_list.append(new_rule) - # needed for draw_contact: - gajim.connections[account].blocked_contacts.append(jid) - self.draw_contact(jid, account) - else: - if iter == None: - for (contact, account) in group_list: - if account not in accounts: - if not gajim.connections[account].privacy_rules_supported: - continue - accounts.append(account) - self.send_status(account, 'offline', msg, to=contact.jid) - new_rule = {'order': u'1', 'type': u'jid', - 'action': u'deny', 'value' : contact.jid, - 'child': [u'message', u'iq', u'presence-out']} - gajim.connections[account].blocked_list.append(new_rule) - # needed for draw_contact: - gajim.connections[account].blocked_contacts.append(contact.jid) - self.draw_contact(contact.jid, account) - else: - group = model[iter][C_JID].decode('utf-8') - for (contact, account) in group_list: - if account not in accounts: - if not gajim.connections[account].privacy_rules_supported: - continue - accounts.append(account) - # needed for draw_group: - gajim.connections[account].blocked_groups.append(group) - self.draw_group(group, account) - self.send_status(account, 'offline', msg, to=contact.jid) - self.draw_contact(contact.jid, account) - new_rule = {'order': u'1', 'type': u'group', 'action': u'deny', - 'value' : group, 'child': [u'message', u'iq', u'presence-out']} - gajim.connections[account].blocked_list.append(new_rule) - for account in accounts: - gajim.connections[account].set_privacy_list( - 'block', gajim.connections[account].blocked_list) - if len(gajim.connections[account].blocked_list) == 1: - gajim.connections[account].set_active_list('block') - gajim.connections[account].set_default_list('block') - gajim.connections[account].get_privacy_list('block') - - def on_unblock(self, widget, iter, group_list): - ''' When clicked on the 'unblock' button in context menu. ''' - model = self.tree.get_model() - accounts = [] - if group_list == None: - jid = model[iter][C_JID].decode('utf-8') - jid_account = model[iter][C_ACCOUNT].decode('utf-8') - accounts.append(jid_account) - gajim.connections[jid_account].new_blocked_list = [] - for rule in gajim.connections[jid_account].blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'jid' \ - or rule['value'] != jid: - gajim.connections[jid_account].new_blocked_list.append(rule) - # needed for draw_contact: - if jid in gajim.connections[jid_account].blocked_contacts: - gajim.connections[jid_account].blocked_contacts.remove(jid) - self.draw_contact(jid, jid_account) - else: - if iter == None: - for (contact, account) in group_list: - if account not in accounts: - if gajim.connections[account].privacy_rules_supported: - accounts.append(account) - gajim.connections[account].new_blocked_list = [] - gajim.connections[account].to_unblock = [] - gajim.connections[account].to_unblock.append(contact.jid) - else: - gajim.connections[account].to_unblock.append(contact.jid) - # needed for draw_contact: - if contact.jid in gajim.connections[account].blocked_contacts: - gajim.connections[account].blocked_contacts.remove( - contact.jid) - self.draw_contact(contact.jid, account) - for account in accounts: - for rule in gajim.connections[account].blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'jid' \ - or rule['value'] not in gajim.connections[account].to_unblock: - gajim.connections[account].new_blocked_list.append(rule) - else: - group = model[iter][C_JID].decode('utf-8') - for (contact, account) in group_list: - if account not in accounts: - if gajim.connections[account].privacy_rules_supported: - accounts.append(account) - # needed for draw_group: - if group in gajim.connections[account].blocked_groups: - gajim.connections[account].blocked_groups.remove(group) - self.draw_group(group, account) - gajim.connections[account].new_blocked_list = [] - for rule in gajim.connections[account].blocked_list: - if rule['action'] != 'deny' or rule['type'] != 'group' \ - or rule['value'] != group: - gajim.connections[account].new_blocked_list.append( - rule) - self.draw_contact(contact.jid, account) - for account in accounts: - gajim.connections[account].set_privacy_list( - 'block', gajim.connections[account].new_blocked_list) - gajim.connections[account].get_privacy_list('block') - if len(gajim.connections[account].new_blocked_list) == 0: - gajim.connections[account].blocked_list = [] - gajim.connections[account].blocked_contacts = [] - gajim.connections[account].blocked_groups = [] - gajim.connections[account].set_default_list('') - gajim.connections[account].set_active_list('') - gajim.connections[account].del_privacy_list('block') - if gajim.interface.instances[account].has_key('blocked_contacts'): - gajim.interface.instances[account]['blocked_contacts'].\ - privacy_list_received([]) - if group_list == None: - status = gajim.connections[jid_account].connected - msg = gajim.connections[jid_account].status - if not self.regroup: - show = gajim.SHOW_LIST[status] - else: # accounts merged - show = helpers.get_global_show() - self.send_status(jid_account, show, msg, to=jid) - else: - for (contact, account) in group_list: - if not self.regroup: - show = gajim.SHOW_LIST[gajim.connections[account].connected] - else: # accounts merged - show = helpers.get_global_show() - if account not in accounts: - if gajim.connections[account].privacy_rules_supported: - accounts.append(account) - self.send_status(account, show, - gajim.connections[account].status, to=contact.jid) - else: - self.send_status(account, show, - gajim.connections[account].status, to=contact.jid) - - def on_rename(self, widget, iter, path): - # this function is called either by F2 or by Rename menuitem - if gajim.interface.instances.has_key('rename'): - gajim.interface.instances['rename'].dialog.present() - return - model = self.tree.get_model() - - row_type = model[iter][C_TYPE] - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - # account is offline, don't allow to rename - if gajim.connections[account].connected < 2: - return - if row_type in ('contact', 'agent'): - # it's jid - title = _('Rename Contact') - message = _('Enter a new nickname for contact %s') % jid - old_text = gajim.contacts.get_contact_with_highest_priority(account, - jid).name - elif row_type == 'group': - if jid in helpers.special_groups + (_('General'),): - return - old_text = model[iter][C_JID].decode('utf-8') - title = _('Rename Group') - message = _('Enter a new name for group %s') % old_text - - def on_renamed(new_text, account, row_type, jid, old_text): - if gajim.interface.instances.has_key('rename'): - del gajim.interface.instances['rename'] - if row_type in ('contact', 'agent'): - if old_text == new_text: - return - for u in gajim.contacts.get_contacts(account, jid): - u.name = new_text - gajim.connections[account].update_contact(jid, new_text, u.groups) - self.draw_contact(jid, account) - # Update opened chat - ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) - if ctrl: - ctrl.update_ui() - win = gajim.interface.msg_win_mgr.get_window(jid, account) - win.redraw_tab(ctrl) - win.show_title() - elif row_type == 'group': - # in C_JID column, we hold the group name (which is not escaped) - if old_text == new_text: - return - # Groups may not change name from or to a special groups - for g in helpers.special_groups: - if g in (new_text, old_text): - return - # get all contacts in that group - for jid in gajim.contacts.get_jid_list(account): - contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) - if old_text in contact.groups: - # set them in the new one and remove it from the old - contact.groups.remove(old_text) - self.remove_contact(contact, account) - if new_text not in contact.groups: - contact.groups.append(new_text) - self.add_contact_to_roster(contact.jid, account) - gajim.connections[account].update_contact(contact.jid, - contact.name, contact.groups) - # If last removed iter was not visible, gajim.groups is not cleaned - if gajim.groups[account].has_key(old_text): - del gajim.groups[account][old_text] - self.draw_group(new_text, account) - - def on_canceled(): - if gajim.interface.instances.has_key('rename'): - del gajim.interface.instances['rename'] - - gajim.interface.instances['rename'] = dialogs.InputDialog(title, message, - old_text, False, (on_renamed, account, row_type, jid, old_text), - on_canceled) - - def readd_if_needed(self, contact, account): - need_readd = False - if len(gajim.events.get_events(account, contact.jid)): - need_readd = True - elif gajim.interface.msg_win_mgr.has_window(contact.jid, account): - if _('Not in Roster') in contact.groups: - # Close chat window - msg_win = gajim.interface.msg_win_mgr.get_window(contact.jid, - account) - ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) - msg_win.remove_tab(ctrl, msg_win.CLOSE_CLOSE_BUTTON) - else: - need_readd = True - if need_readd: - c = gajim.contacts.create_contact(jid = contact.jid, - name = '', groups = [_('Not in Roster')], - show = 'not in roster', status = '', ask = 'none', - keyID = contact.keyID) - gajim.contacts.add_contact(account, c) - self.add_contact_to_roster(contact.jid, account) - - def on_remove_group_item_activated(self, widget, group, account): - dlg = dialogs.ConfirmationDialogCheck(_('Remove Group'), - _('Do you want to remove group %s from the roster?' % group), - _('Remove also all contacts in this group from your roster')) - dlg.set_default_response(gtk.BUTTONS_OK_CANCEL) - response = dlg.run() - if response == gtk.RESPONSE_OK: - for contact in gajim.contacts.get_contacts_from_group(account, group): - if not dlg.is_checked(): - self.remove_contact_from_group(account, contact, group) - gajim.connections[account].update_contact(contact.jid, - contact.name, contact.groups) - self.add_contact_to_roster(contact.jid, account) - else: - gajim.connections[account].unsubscribe(contact.jid) - for c in gajim.contacts.get_contacts(account, contact.jid): - self.remove_contact(c, account) - gajim.contacts.remove_jid(account, c.jid) - self.readd_if_needed(contact, account) - self.draw_account(account) - - def on_assign_pgp_key(self, widget, contact, account): - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - keys = {} - #GPG Key - keyID = _('None') - for i in xrange(len(attached_keys)/2): - keys[attached_keys[2*i]] = attached_keys[2*i+1] - if attached_keys[2*i] == contact.jid: - keyID = attached_keys[2*i+1] - public_keys = gajim.connections[account].ask_gpg_keys() - #GPG Key - public_keys[_('None')] = _('None') - instance = dialogs.ChooseGPGKeyDialog(_('Assign OpenPGP Key'), - _('Select a key to apply to the contact'), public_keys, keyID) - keyID = instance.run() - if keyID is None: - return - #GPG Key - if keyID[0] == _('None'): - if contact.jid in keys: - del keys[contact.jid] - keyID = '' - else: - keyID = keyID[0] - keys[contact.jid] = keyID - - if gajim.interface.msg_win_mgr.has_window(contact.jid, account): - ctrl = gajim.interface.msg_win_mgr.get_control(contact.jid, account) - ctrl.update_ui() - keys_str = '' - for jid in keys: - keys_str += jid + ' ' + keys[jid] + ' ' - gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) - for u in gajim.contacts.get_contacts(account, contact.jid): - u.keyID = helpers.prepare_and_validate_gpg_keyID(account, - contact.jid, keyID) - - def update_avatar_in_gui(self, jid, account): - # Update roster - self.draw_avatar(jid, account) - # Update chat window - if gajim.interface.msg_win_mgr.has_window(jid, account): - win = gajim.interface.msg_win_mgr.get_window(jid, account) - ctrl = win.get_control(jid, account) - if win and ctrl.type_id != message_control.TYPE_GC: - ctrl.show_avatar() - - def on_set_custom_avatar_activate(self, widget, contact, account): - def on_ok(widget, path_to_file): - filesize = os.path.getsize(path_to_file) # in bytes - invalid_file = False - msg = '' - if os.path.isfile(path_to_file): - stat = os.stat(path_to_file) - if stat[6] == 0: - invalid_file = True - msg = _('File is empty') - else: - invalid_file = True - msg = _('File does not exist') - if invalid_file: - dialogs.ErrorDialog(_('Could not load image'), msg) - return - try: - pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) - if filesize > 16384: # 16 kb - # get the image at 'tooltip size' - # and hope that user did not specify in ACE crazy size - pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'tooltip') - except gobject.GError, msg: # unknown format - # msg should be string, not object instance - msg = str(msg) - dialogs.ErrorDialog(_('Could not load image'), msg) - return - gajim.interface.save_avatar_files(contact.jid, pixbuf, local = True) - dlg.destroy() - self.update_avatar_in_gui(contact.jid, account) - - def on_clear(widget): - dlg.destroy() - # Delete file: - gajim.interface.remove_avatar_files(contact.jid, local = True) - self.update_avatar_in_gui(contact.jid, account) - - dlg = dialogs.AvatarChooserDialog(on_response_ok = on_ok, - on_response_clear = on_clear) - - def on_edit_groups(self, widget, list_): - dlg = dialogs.EditGroupsDialog(list_) - dlg.run() - - def on_history(self, widget, contact, account): - '''When history menuitem is activated: call log window''' - if gajim.interface.instances.has_key('logs'): - gajim.interface.instances['logs'].window.present() - gajim.interface.instances['logs'].open_history(contact.jid, account) - else: - gajim.interface.instances['logs'] = history_window.\ - HistoryWindow(contact.jid, account) - - def on_disconnect(self, widget, jid, account): - '''When disconnect menuitem is activated: disconect from room''' - ctrl = gajim.interface.minimized_controls[account][jid] - del gajim.interface.minimized_controls[account][jid] - ctrl.shutdown() - - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - return - if contact.groups == [_('Groupchats')]: - self.remove_contact(contact, account) - gajim.contacts.remove_contact(account, contact) - self.draw_group(_('Groupchats'), account) - self.draw_account(account) - - def on_send_single_message_menuitem_activate(self, widget, account, - contact = None): - if contact is None: - dialogs.SingleMessageWindow(account, action = 'send') - elif type(contact) == type([]): - dialogs.SingleMessageWindow(account, contact, 'send') - else: - jid = contact.jid - if contact.jid == gajim.get_jid_from_account(account): - jid += '/' + contact.resource - dialogs.SingleMessageWindow(account, jid, 'send') - - def on_send_file_menuitem_activate(self, widget, contact, account, - resource=None): - gajim.interface.instances['file_transfers'].show_file_send_request( - account, contact) - - def on_add_special_notification_menuitem_activate(self, widget, jid): - dialogs.AddSpecialNotificationDialog(jid) - - def build_resources_submenu(self, contacts, account, action, room_jid=None, - room_account=None): - ''' Build a submenu with contact's resources. - room_jid and room_account are for action self.on_invite_to_room ''' - sub_menu = gtk.Menu() - - iconset = gajim.config.get('iconset') - if not iconset: - iconset = gajim.config.DEFAULT_ICONSET - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for c in contacts: - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority))) - icon_name = helpers.get_icon_name_to_show(c, account) - icon = state_images[icon_name] - item.set_image(icon) - sub_menu.append(item) - if action == self.on_invite_to_room: - item.connect('activate', action, [(c, account)], - room_jid, room_account, c.resource) - elif action == self.on_invite_to_new_room: - item.connect('activate', action, [(c, account)], c.resource) - else: # start_chat, execute_command, send_file - item.connect('activate', action, c, account, c.resource) - return sub_menu - - def build_invite_submenu(self, invite_menuitem, list_): - '''list_ in a list of (contact, account)''' - # used if we invite only one contact with several resources - contact_list = [] - if len(list_) == 1: - contact, account = list_[0] - contact_list = gajim.contacts.get_contacts(account, contact.jid) - contacts_transport = -1 - connected_accounts = [] - # -1 is at start, False when not from the same, None when jabber - for (contact, account) in list_: - if not account in connected_accounts: - connected_accounts.append(account) - transport = gajim.get_transport_name_from_jid(contact.jid) - if contacts_transport == -1: - contacts_transport = transport - elif contacts_transport != transport: - contacts_transport = False - - if contacts_transport == False: - # they are not all from the same transport - invite_menuitem.set_sensitive(False) - return - invite_to_submenu = gtk.Menu() - invite_menuitem.set_submenu(invite_to_submenu) - invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - invite_to_new_room_menuitem.set_image(icon) - if len(contact_list) > 1: # several resources - invite_to_new_room_menuitem.set_submenu(self.build_resources_submenu( - contact_list, account, self.on_invite_to_new_room)) - else: - invite_to_new_room_menuitem.connect('activate', - self.on_invite_to_new_room, list_) - # transform None in 'jabber' - c_t = contacts_transport or 'jabber' - muc_jid = {} - for account in connected_accounts: - for t in gajim.connections[account].muc_jid: - muc_jid[t] = gajim.connections[account].muc_jid[t] - if not muc_jid.has_key(c_t): - invite_to_new_room_menuitem.set_sensitive(False) - rooms = [] # a list of (room_jid, account) tuple - invite_to_submenu.append(invite_to_new_room_menuitem) - rooms = [] # a list of (room_jid, account) tuple - minimized_controls = [] - for account in connected_accounts: - minimized_controls += \ - gajim.interface.minimized_controls[account].values() - for gc_control in gajim.interface.msg_win_mgr.get_controls( - message_control.TYPE_GC) + minimized_controls: - acct = gc_control.account - room_jid = gc_control.room_jid - if gajim.gc_connected[acct].has_key(room_jid) and \ - gajim.gc_connected[acct][room_jid] and \ - contacts_transport == gajim.get_transport_name_from_jid(room_jid): - rooms.append((room_jid, acct)) - if len(rooms): - item = gtk.SeparatorMenuItem() # separator - invite_to_submenu.append(item) - for (room_jid, account) in rooms: - menuitem = gtk.MenuItem(room_jid.split('@')[0]) - if len(contact_list) > 1: # several resources - menuitem.set_submenu(self.build_resources_submenu( - contact_list, account, self.on_invite_to_room, room_jid, - account)) - else: - menuitem.connect('activate', self.on_invite_to_room, list_, - room_jid, account) - invite_to_submenu.append(menuitem) - - def make_contact_menu(self, event, iter): - '''Make contact\'s popup menu''' - model = self.tree.get_model() - jid = model[iter][C_JID].decode('utf-8') - tree_path = model.get_path(iter) - account = model[iter][C_ACCOUNT].decode('utf-8') - our_jid = jid == gajim.get_jid_from_account(account) - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - return - - # Zeroconf Account - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - xml = gtkgui_helpers.get_glade('zeroconf_contact_context_menu.glade') - zeroconf_contact_context_menu = xml.get_widget( - 'zeroconf_contact_context_menu') - - start_chat_menuitem = xml.get_widget('start_chat_menuitem') - rename_menuitem = xml.get_widget('rename_menuitem') - edit_groups_menuitem = xml.get_widget('edit_groups_menuitem') - send_file_menuitem = xml.get_widget('send_file_menuitem') - assign_openpgp_key_menuitem = xml.get_widget( - 'assign_openpgp_key_menuitem') - add_special_notification_menuitem = xml.get_widget( - 'add_special_notification_menuitem') - - if not our_jid: - # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - rename_menuitem.set_image(img) - - above_information_separator = xml.get_widget( - 'above_information_separator') - - information_menuitem = xml.get_widget('information_menuitem') - history_menuitem = xml.get_widget('history_menuitem') - - contacts = gajim.contacts.get_contacts(account, jid) - if len(contacts) > 1: # several resources - sub_menu = gtk.Menu() - start_chat_menuitem.set_submenu(sub_menu) - - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for c in contacts: - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - item = gtk.ImageMenuItem('%s (%s)' % (c.resource, - str(c.priority))) - icon_name = helpers.get_icon_name_to_show(c, account) - icon = state_images[icon_name] - item.set_image(icon) - sub_menu.append(item) - item.connect('activate', self.on_open_chat_window, c, account, - c.resource) - - else: # one resource - start_chat_menuitem.connect('activate', - self.on_roster_treeview_row_activated, tree_path) - - if contact.resource: - send_file_menuitem.connect('activate', - self.on_send_file_menuitem_activate, contact, account) - else: # if we do no have resource we cannot do much - send_file_menuitem.set_sensitive(False) - - rename_menuitem.connect('activate', self.on_rename, iter, tree_path) - if contact.show in ('offline', 'error'): - information_menuitem.set_sensitive(False) - send_file_menuitem.set_sensitive(False) - else: - information_menuitem.connect('activate', self.on_info_zeroconf, - contact, account) - history_menuitem.connect('activate', self.on_history, contact, - account) - - if _('Not in Roster') not in contact.groups: - # contact is in normal group - edit_groups_menuitem.set_no_show_all(False) - assign_openpgp_key_menuitem.set_no_show_all(False) - edit_groups_menuitem.connect('activate', self.on_edit_groups, [( - contact,account)]) - - if gajim.connections[account].gpg: - assign_openpgp_key_menuitem.connect('activate', - self.on_assign_pgp_key, contact, account) - else: - assign_openpgp_key_menuitem.set_sensitive(False) - - else: # contact is in group 'Not in Roster' - edit_groups_menuitem.set_sensitive(False) - edit_groups_menuitem.set_no_show_all(True) - assign_openpgp_key_menuitem.set_sensitive(False) - - # Remove many items when it's self contact row - if our_jid: - for menuitem in (rename_menuitem, edit_groups_menuitem, - above_information_separator): - menuitem.set_no_show_all(True) - menuitem.hide() - - # Unsensitive many items when account is offline - if gajim.connections[account].connected < 2: - for widget in [start_chat_menuitem, rename_menuitem, - edit_groups_menuitem, send_file_menuitem]: - widget.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - zeroconf_contact_context_menu.attach_to_widget(self.tree, None) - zeroconf_contact_context_menu.connect('selection-done', - gtkgui_helpers.destroy_widget) - zeroconf_contact_context_menu.show_all() - zeroconf_contact_context_menu.popup(None, None, None, event_button, - event.time) - return - - - # normal account - xml = gtkgui_helpers.get_glade('roster_contact_context_menu.glade') - roster_contact_context_menu = xml.get_widget( - 'roster_contact_context_menu') - - start_chat_menuitem = xml.get_widget('start_chat_menuitem') - send_custom_status_menuitem = xml.get_widget( - 'send_custom_status_menuitem') - send_single_message_menuitem = xml.get_widget( - 'send_single_message_menuitem') - invite_menuitem = xml.get_widget('invite_menuitem') - block_menuitem = xml.get_widget('block_menuitem') - unblock_menuitem = xml.get_widget('unblock_menuitem') - rename_menuitem = xml.get_widget('rename_menuitem') - edit_groups_menuitem = xml.get_widget('edit_groups_menuitem') - send_file_menuitem = xml.get_widget('send_file_menuitem') - assign_openpgp_key_menuitem = xml.get_widget( - 'assign_openpgp_key_menuitem') - set_custom_avatar_menuitem = xml.get_widget('set_custom_avatar_menuitem') - add_special_notification_menuitem = xml.get_widget( - 'add_special_notification_menuitem') - execute_command_menuitem = xml.get_widget( - 'execute_command_menuitem') - - # send custom status icon - blocked = False - if jid in gajim.connections[account].blocked_contacts: - blocked = True - else: - groups = contact.groups - if contact.is_observer(): - groups = [_('Observers')] - elif not groups: - groups = [_('General')] - for group in groups: - if group in gajim.connections[account].blocked_groups: - blocked = True - break - if blocked: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) - send_custom_status_menuitem.set_sensitive(False) - elif gajim.interface.status_sent_to_users.has_key(account) and \ - jid in gajim.interface.status_sent_to_users[account]: - send_custom_status_menuitem.set_image( - gtkgui_helpers.load_icon(gajim.interface.status_sent_to_users[ - account][jid])) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) - - if not our_jid: - # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - rename_menuitem.set_image(img) - - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_menuitem.set_image(muc_icon) - - self.build_invite_submenu(invite_menuitem, [(contact, account)]) - - # Subscription submenu - subscription_menuitem = xml.get_widget('subscription_menuitem') - send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem =\ - subscription_menuitem.get_submenu().get_children() - add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem') - remove_from_roster_menuitem = xml.get_widget( - 'remove_from_roster_menuitem') - - information_menuitem = xml.get_widget('information_menuitem') - history_menuitem = xml.get_widget('history_menuitem') - - contacts = gajim.contacts.get_contacts(account, jid) - - # One or several resource, we do the same for send_custom_status - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ['online', 'chat', 'away', 'xa', 'dnd', 'offline']: - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', self.on_send_custom_status, - [(contact, account)], s) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) - if len(contacts) > 1: # several resources - start_chat_menuitem.set_submenu(self.build_resources_submenu(contacts, - account, self.on_open_chat_window)) - send_file_menuitem.set_submenu(self.build_resources_submenu(contacts, - account, self.on_send_file_menuitem_activate)) - execute_command_menuitem.set_submenu(self.build_resources_submenu( - contacts, account, self.on_execute_command)) - - else: # one resource - start_chat_menuitem.connect('activate', - self.on_open_chat_window, contact, account) - execute_command_menuitem.connect('activate', self.on_execute_command, - contact, account, contact.resource) - - our_jid_other_resource = None - if our_jid: - # It's another resource of us, be sure to send invite to her - our_jid_other_resource = contact.resource - # Else this var is useless but harmless in next connect calls - - if contact.resource: - send_file_menuitem.connect('activate', - self.on_send_file_menuitem_activate, contact, account) - else: # if we do not have resource we cannot send file - send_file_menuitem.set_sensitive(False) - - send_single_message_menuitem.connect('activate', - self.on_send_single_message_menuitem_activate, account, contact) - - rename_menuitem.connect('activate', self.on_rename, iter, tree_path) - remove_from_roster_menuitem.connect('activate', self.on_req_usub, - [(contact, account)]) - information_menuitem.connect('activate', self.on_info, contact, - account) - history_menuitem.connect('activate', self.on_history, contact, - account) - - if _('Not in Roster') not in contact.groups: - # contact is in normal group - add_to_roster_menuitem.hide() - add_to_roster_menuitem.set_no_show_all(True) - edit_groups_menuitem.connect('activate', self.on_edit_groups, [( - contact,account)]) - - if gajim.connections[account].gpg: - assign_openpgp_key_menuitem.connect('activate', - self.on_assign_pgp_key, contact, account) - else: - assign_openpgp_key_menuitem.set_sensitive(False) - - if contact.sub in ('from', 'both'): - send_auth_menuitem.set_sensitive(False) - else: - send_auth_menuitem.connect('activate', self.authorize, jid, account) - if contact.sub in ('to', 'both'): - ask_auth_menuitem.set_sensitive(False) - add_special_notification_menuitem.connect('activate', - self.on_add_special_notification_menuitem_activate, jid) - else: - ask_auth_menuitem.connect('activate', self.req_sub, jid, - _('I would like to add you to my roster'), account, - contact.groups, contact.name) - if contact.sub in ('to', 'none'): - revoke_auth_menuitem.set_sensitive(False) - else: - revoke_auth_menuitem.connect('activate', self.revoke_auth, jid, - account) - - else: # contact is in group 'Not in Roster' - add_to_roster_menuitem.set_no_show_all(False) - edit_groups_menuitem.set_sensitive(False) - assign_openpgp_key_menuitem.set_sensitive(False) - subscription_menuitem.set_sensitive(False) - - add_to_roster_menuitem.connect('activate', - self.on_add_to_roster, contact, account) - - set_custom_avatar_menuitem.connect('activate', - self.on_set_custom_avatar_activate, contact, account) - # Hide items when it's self contact row - if our_jid: - menuitem = xml.get_widget('manage_contact') - menuitem.set_sensitive(False) - - # Unsensitive many items when account is offline - if gajim.connections[account].connected < 2: - for widget in [start_chat_menuitem, send_single_message_menuitem, - rename_menuitem, edit_groups_menuitem, send_file_menuitem, - subscription_menuitem, add_to_roster_menuitem, - remove_from_roster_menuitem, execute_command_menuitem, - send_custom_status_menuitem]: - widget.set_sensitive(False) - - if gajim.connections[account] and gajim.connections[account].\ - privacy_rules_supported: - if jid in gajim.connections[account].blocked_contacts: - block_menuitem.set_no_show_all(True) - unblock_menuitem.connect('activate', self.on_unblock, iter, None) - block_menuitem.hide() - else: - unblock_menuitem.set_no_show_all(True) - block_menuitem.connect('activate', self.on_block, iter, None) - unblock_menuitem.hide() - else: - unblock_menuitem.set_no_show_all(True) - block_menuitem.set_sensitive(False) - unblock_menuitem.hide() - - event_button = gtkgui_helpers.get_possible_button_event(event) - - roster_contact_context_menu.attach_to_widget(self.tree, None) - roster_contact_context_menu.connect('selection-done', - gtkgui_helpers.destroy_widget) - roster_contact_context_menu.show_all() - roster_contact_context_menu.popup(None, None, None, event_button, - event.time) - - def on_invite_to_new_room(self, widget, list_, resource = None): - ''' resource parameter MUST NOT be used if more than one contact in - list ''' - account_list = [] - jid_list = [] - for (contact, account) in list_: - if contact.jid not in jid_list: - if resource: # we MUST have one contact only in list_ - fjid = contact.jid + '/' + resource - jid_list.append(fjid) - else: - jid_list.append(contact.jid) - if account not in account_list: - account_list.append(account) - # transform None in 'jabber' - type_ = gajim.get_transport_name_from_jid(jid_list[0]) or 'jabber' - for account in account_list: - if gajim.connections[account].muc_jid[type_]: - # create the room on this muc server - if gajim.interface.instances[account].has_key('join_gc'): - gajim.interface.instances[account]['join_gc'].window.destroy() - try: - gajim.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account, - gajim.connections[account].muc_jid[type_], - automatic = {'invities': jid_list}) - except GajimGeneralException: - continue - break - - def on_invite_to_room(self, widget, list_, room_jid, room_account, - resource = None): - ''' resource parameter MUST NOT be used if more than one contact in - list ''' - for (contact, acct) in list_: - contact_jid = contact.jid - if resource: # we MUST have one contact only in list_ - contact_jid += '/' + resource - gajim.connections[room_account].send_invite(room_jid, contact_jid) - - - def make_multiple_contact_menu(self, event, iters): - '''Make group's popup menu''' - model = self.tree.get_model() - list_ = [] # list of (jid, account) tuples - one_account_offline = False - is_blocked = True - for iter in iters: - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - if gajim.connections[account].connected < 2: - one_account_offline = True - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if jid not in gajim.connections[account].blocked_contacts: - is_blocked = False - list_.append((contact, account)) - - menu = gtk.Menu() - account = None - for (contact, current_account) in list_: - # check that we use the same account for every sender - if account is not None and account != current_account: - account = None - break - account = current_account - if account is not None: - send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - send_group_message_item.set_image(icon) - menu.append(send_group_message_item) - send_group_message_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) - - # Invite to Groupchat - invite_item = gtk.ImageMenuItem(_('In_vite to')) - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_item.set_image(muc_icon) - - self.build_invite_submenu(invite_item, list_) - menu.append(invite_item) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Manage Transport submenu - item = gtk.ImageMenuItem(_('_Manage Contacts')) - icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_contacts_submenu = gtk.Menu() - item.set_submenu(manage_contacts_submenu) - menu.append(item) - - # Edit Groups - edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups')) - icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU) - edit_groups_item.set_image(icon) - manage_contacts_submenu.append(edit_groups_item) - edit_groups_item.connect('activate', self.on_edit_groups, list_) - - item = gtk.SeparatorMenuItem() # separator - manage_contacts_submenu.append(item) - - # Block - if is_blocked and gajim.connections[account].privacy_rules_supported: - unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - unblock_menuitem.set_image(icon) - unblock_menuitem.connect('activate', self.on_unblock, None, list_) - manage_contacts_submenu.append(unblock_menuitem) - else: - block_menuitem = gtk.ImageMenuItem(_('_Block')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - block_menuitem.set_image(icon) - block_menuitem.connect('activate', self.on_block, None, list_) - manage_contacts_submenu.append(block_menuitem) - - if not gajim.connections[account].privacy_rules_supported: - block_menuitem.set_sensitive(False) - - # Remove - remove_item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - remove_item.set_image(icon) - manage_contacts_submenu.append(remove_item) - remove_item.connect('activate', self.on_req_usub, list_) - # unsensitive remove if one account is not connected - if one_account_offline: - remove_item.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_groupchat_menu(self, event, iter): - model = self.tree.get_model() - - path = model.get_path(iter) - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - menu = gtk.Menu() - - if jid in gajim.interface.minimized_controls[account]: - maximize_menuitem = gtk.ImageMenuItem(_('_Maximize')) - icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) - maximize_menuitem.set_image(icon) - maximize_menuitem.connect('activate', self.on_groupchat_maximized, \ - jid, account) - menu.append(maximize_menuitem) - - disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect')) - disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \ - gtk.ICON_SIZE_MENU) - disconnect_menuitem.set_image(disconnect_icon) - disconnect_menuitem .connect('activate', self.on_disconnect, jid, account) - menu.append(disconnect_menuitem) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - history_menuitem = gtk.ImageMenuItem(_('_History')) - history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \ - gtk.ICON_SIZE_MENU) - history_menuitem.set_image(history_icon) - history_menuitem .connect('activate', self.on_history, \ - contact, account) - menu.append(history_menuitem) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def on_all_groupchat_maximized(self, widget, group_list): - for (contact, account) in group_list: - self.on_groupchat_maximized(widget, contact.jid, account) - - - def on_groupchat_maximized(self, widget, jid, account): - '''When a groupchat is maximised''' - if not gajim.interface.minimized_controls[account].has_key(jid): - return - - - ctrl = gajim.interface.minimized_controls[account][jid] - mw = gajim.interface.msg_win_mgr.get_window(ctrl.contact.jid, ctrl.account) - if not mw: - mw = gajim.interface.msg_win_mgr.create_window(ctrl.contact, - ctrl.account, ctrl.type_id) - ctrl.parent_win = mw - mw.new_tab(ctrl) - mw.set_active_tab(jid, account) - mw.window.present() - del gajim.interface.minimized_controls[account][jid] - - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - if not contact: - return - if contact.groups == [_('Groupchats')]: - self.remove_contact(contact, account) - gajim.contacts.remove_contact(account, contact) - self.draw_group(_('Groupchats'), account) - self.draw_account(account) - - def make_group_menu(self, event, iter): - '''Make group's popup menu''' - model = self.tree.get_model() - path = model.get_path(iter) - group = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - - list_ = [] # list of (jid, account) tuples - list_online = [] # list of (jid, account) tuples - - group = model[iter][C_JID] - for jid in gajim.contacts.get_jid_list(account): - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - if group in contact.groups or (contact.groups == [] and group == \ - _('General')): - if contact.show not in ('offline', 'error'): - list_online.append((contact, account)) - list_.append((contact, account)) - menu = gtk.Menu() - - # Make special context menu if group is Groupchats - if group == _('Groupchats'): - maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All')) - icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) - maximize_menuitem.set_image(icon) - maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\ - list_) - menu.append(maximize_menuitem) - else: - # Send Group Message - send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - send_group_message_item.set_image(icon) - - send_group_message_submenu = gtk.Menu() - send_group_message_item.set_submenu(send_group_message_submenu) - menu.append(send_group_message_item) - - group_message_to_all_item = gtk.MenuItem(_('To all users')) - send_group_message_submenu.append(group_message_to_all_item) - - group_message_to_all_online_item = gtk.MenuItem(_('To all online users')) - send_group_message_submenu.append(group_message_to_all_online_item) - - group_message_to_all_online_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_online) - group_message_to_all_item.connect('activate', - self.on_send_single_message_menuitem_activate, account, list_) - - # Invite to - invite_menuitem = gtk.ImageMenuItem(_('In_vite to')) - muc_icon = gtkgui_helpers.load_icon('muc_active') - if muc_icon: - invite_menuitem.set_image(muc_icon) - - self.build_invite_submenu(invite_menuitem, list_online) - menu.append(invite_menuitem) - - # Send Custom Status - send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) - # add a special img for this menuitem - if group in gajim.connections[account].blocked_groups: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) - send_custom_status_menuitem.set_sensitive(False) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, - gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ['online', 'chat', 'away', 'xa', 'dnd', 'offline']: - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', self.on_send_custom_status, list_, - s, group) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) - menu.append(send_custom_status_menuitem) - - # there is no singlemessage and custom status for zeroconf - if gajim.config.get_per('accounts', account, 'is_zeroconf'): - send_custom_status_menuitem.set_sensitive(False) - send_group_message_item.set_sensitive(False) - - if not group in helpers.special_groups: - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Rename - rename_item = gtk.ImageMenuItem(_('Re_name')) - # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - rename_item.set_image(img) - menu.append(rename_item) - rename_item.connect('activate', self.on_rename, iter, path) - - # Block group - is_blocked = False - if self.regroup: - for g_account in gajim.connections: - if group in gajim.connections[g_account].blocked_groups: - is_blocked = True - else: - if group in gajim.connections[account].blocked_groups: - is_blocked = True - - if is_blocked and gajim.connections[account].privacy_rules_supported: - unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - unblock_menuitem.set_image(icon) - unblock_menuitem.connect('activate', self.on_unblock, iter, list_) - menu.append(unblock_menuitem) - else: - block_menuitem = gtk.ImageMenuItem(_('_Block')) - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - block_menuitem.set_image(icon) - block_menuitem.connect('activate', self.on_block, iter, list_) - menu.append(block_menuitem) - if not gajim.connections[account].privacy_rules_supported: - block_menuitem.set_sensitive(False) - - # Remove group - remove_item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - remove_item.set_image(icon) - menu.append(remove_item) - remove_item.connect('activate', self.on_remove_group_item_activated, - group, account) - - # unsensitive if account is not connected - if gajim.connections[account].connected < 2: - rename_item.set_sensitive(False) - - # General group cannot be changed - if group == _('General'): - rename_item.set_sensitive(False) - block_menuitem.set_sensitive(False) - remove_item.set_sensitive(False) - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def make_transport_menu(self, event, iter): - '''Make transport\'s popup menu''' - model = self.tree.get_model() - jid = model[iter][C_JID].decode('utf-8') - path = model.get_path(iter) - account = model[iter][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, jid) - menu = gtk.Menu() - - # Send single message - item = gtk.ImageMenuItem(_('Send Single Message')) - icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) - item.set_image(icon) - item.connect('activate', - self.on_send_single_message_menuitem_activate, account, contact) - menu.append(item) - - blocked = False - if jid in gajim.connections[account].blocked_contacts: - blocked = True - - # Send Custom Status - send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) - # add a special img for this menuitem - if blocked: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - 'offline')) - send_custom_status_menuitem.set_sensitive(False) - else: - if gajim.interface.status_sent_to_users.has_key(account) and \ - jid in gajim.interface.status_sent_to_users[account]: - send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( - gajim.interface.status_sent_to_users[account][jid])) - else: - icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, - gtk.ICON_SIZE_MENU) - send_custom_status_menuitem.set_image(icon) - status_menuitems = gtk.Menu() - send_custom_status_menuitem.set_submenu(status_menuitems) - iconset = gajim.config.get('iconset') - path = os.path.join(helpers.get_iconset_path(iconset), '16x16') - for s in ['online', 'chat', 'away', 'xa', 'dnd', 'offline']: - # icon MUST be different instance for every item - state_images = gtkgui_helpers.load_iconset(path) - status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) - status_menuitem.connect('activate', self.on_send_custom_status, - [(contact, account)], s) - icon = state_images[s] - status_menuitem.set_image(icon) - status_menuitems.append(status_menuitem) - menu.append(send_custom_status_menuitem) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Execute Command - item = gtk.ImageMenuItem(_('Execute Command...')) - icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) - item.set_image(icon) - menu.append(item) - item.connect('activate', self.on_execute_command, contact, account, - contact.resource) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Manage Transport submenu - item = gtk.ImageMenuItem(_('_Manage Transport')) - icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu = gtk.Menu() - item.set_submenu(manage_transport_submenu) - menu.append(item) - - # Modify Transport - item = gtk.ImageMenuItem(_('_Modify Transport')) - icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - item.connect('activate', self.on_edit_agent, contact, account) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Rename - item = gtk.ImageMenuItem(_('_Rename')) - # add a special img for rename menuitem - path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', - 'kbd_input.png') - img = gtk.Image() - img.set_from_file(path_to_kbd_input_img) - item.set_image(img) - manage_transport_submenu.append(item) - item.connect('activate', self.on_rename, iter, path) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - item = gtk.SeparatorMenuItem() # separator - manage_transport_submenu.append(item) - - # Block - if blocked: - item = gtk.ImageMenuItem(_('_Unblock')) - item.connect('activate', self.on_unblock, iter, None) - else: - item = gtk.ImageMenuItem(_('_Block')) - item.connect('activate', self.on_block, iter, None) - - icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - # Remove - item = gtk.ImageMenuItem(_('_Remove')) - icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) - item.set_image(icon) - manage_transport_submenu.append(item) - item.connect('activate', self.on_remove_agent, [(contact, account)]) - if gajim.account_is_disconnected(account): - item.set_sensitive(False) - - item = gtk.SeparatorMenuItem() # separator - menu.append(item) - - # Information - information_menuitem = gtk.ImageMenuItem(_('_Information')) - icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU) - information_menuitem.set_image(icon) - menu.append(information_menuitem) - information_menuitem.connect('activate', self.on_info, contact, account) - - - event_button = gtkgui_helpers.get_possible_button_event(event) - - menu.attach_to_widget(self.tree, None) - menu.connect('selection-done', gtkgui_helpers.destroy_widget) - menu.show_all() - menu.popup(None, None, None, event_button, event.time) - - def on_edit_account(self, widget, account): - if gajim.interface.instances.has_key('accounts'): - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - gajim.interface.instances['accounts'].select_account(account) - - def on_zeroconf_properties(self, widget, account): - if gajim.interface.instances.has_key('accounts'): - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - gajim.interface.instances['accounts'].select_account(account) - - def on_open_gmail_inbox(self, widget, account): - url = gajim.connections[account].gmail_url - if url: - helpers.launch_browser_mailer('url', url) - - def on_change_activity_activate(self, widget, account): - dlg = dialogs.ChangeActivityDialog(account) - - def on_change_mood_activate(self, widget, account): - dlg = dialogs.ChangeMoodDialog(account) - - def on_change_status_message_activate(self, widget, account): - show = gajim.SHOW_LIST[gajim.connections[account].connected] - dlg = dialogs.ChangeStatusMessageDialog(show) - message = dlg.run() - if message is not None: # None is if user pressed Cancel - self.send_status(account, show, message) - + self.actions_menu_needs_rebuild = False + def build_account_menu(self, account): # we have to create our own set of icons for the menu # using self.jabber_status_images is poopoo @@ -3185,93 +4729,951 @@ class RosterWindow: menu.connect('selection-done', gtkgui_helpers.destroy_widget) menu.show_all() menu.popup(None, None, None, event_button, event.time) + + def make_group_menu(self, event, iter): + '''Make group's popup menu''' + model = self.tree.get_model() + path = model.get_path(iter) + group = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') - def on_add_to_roster(self, widget, contact, account): - dialogs.AddNewContactWindow(account, contact.jid, contact.name) + list_ = [] # list of (jid, account) tuples + list_online = [] # list of (jid, account) tuples - def authorize(self, widget, jid, account): - '''Authorize a contact (by re-sending auth menuitem)''' - gajim.connections[account].send_authorization(jid) - dialogs.InformationDialog(_('Authorization has been sent'), - _('Now "%s" will know your status.') %jid) + group = model[iter][C_JID] + for jid in gajim.contacts.get_jid_list(account): + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if group in contact.groups or (contact.groups == [] and group == \ + _('General')): + if contact.show not in ('offline', 'error'): + list_online.append((contact, account)) + list_.append((contact, account)) + menu = gtk.Menu() - def req_sub(self, widget, jid, txt, account, groups = [], nickname = None, - auto_auth = False): - '''Request subscription to a contact''' - gajim.connections[account].request_subscription(jid, txt, nickname, - groups, auto_auth, gajim.nicks[account]) + # Make special context menu if group is Groupchats + if group == _('Groupchats'): + maximize_menuitem = gtk.ImageMenuItem(_('_Maximize All')) + icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) + maximize_menuitem.set_image(icon) + maximize_menuitem.connect('activate', self.on_all_groupchat_maximized,\ + list_) + menu.append(maximize_menuitem) + else: + # Send Group Message + send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + send_group_message_item.set_image(icon) + + send_group_message_submenu = gtk.Menu() + send_group_message_item.set_submenu(send_group_message_submenu) + menu.append(send_group_message_item) + + group_message_to_all_item = gtk.MenuItem(_('To all users')) + send_group_message_submenu.append(group_message_to_all_item) + + group_message_to_all_online_item = gtk.MenuItem(_('To all online users')) + send_group_message_submenu.append(group_message_to_all_online_item) + + group_message_to_all_online_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_online) + group_message_to_all_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_) + + # Invite to + invite_menuitem = gtk.ImageMenuItem(_('In_vite to')) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_menuitem.set_image(muc_icon) + + self.build_invite_submenu(invite_menuitem, list_online) + menu.append(invite_menuitem) + + # Send Custom Status + send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) + # add a special img for this menuitem + if group in gajim.connections[account].blocked_groups: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline')) + send_custom_status_menuitem.set_sensitive(False) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, + gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ['online', 'chat', 'away', 'xa', 'dnd', 'offline']: + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', self.on_send_custom_status, list_, + s, group) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) + menu.append(send_custom_status_menuitem) + + # there is no singlemessage and custom status for zeroconf + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + send_custom_status_menuitem.set_sensitive(False) + send_group_message_item.set_sensitive(False) + + if not group in helpers.special_groups: + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Rename + rename_item = gtk.ImageMenuItem(_('Re_name')) + # add a special img for rename menuitem + path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path_to_kbd_input_img) + rename_item.set_image(img) + menu.append(rename_item) + rename_item.connect('activate', self.on_rename, iter, path) + + # Block group + is_blocked = False + if self.regroup: + for g_account in gajim.connections: + if group in gajim.connections[g_account].blocked_groups: + is_blocked = True + else: + if group in gajim.connections[account].blocked_groups: + is_blocked = True + + if is_blocked and gajim.connections[account].privacy_rules_supported: + unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + unblock_menuitem.set_image(icon) + unblock_menuitem.connect('activate', self.on_unblock, iter, list_) + menu.append(unblock_menuitem) + else: + block_menuitem = gtk.ImageMenuItem(_('_Block')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + block_menuitem.set_image(icon) + block_menuitem.connect('activate', self.on_block, iter, list_) + menu.append(block_menuitem) + if not gajim.connections[account].privacy_rules_supported: + block_menuitem.set_sensitive(False) + + # Remove group + remove_item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + remove_item.set_image(icon) + menu.append(remove_item) + remove_item.connect('activate', self.on_remove_group_item_activated, + group, account) + + # unsensitive if account is not connected + if gajim.connections[account].connected < 2: + rename_item.set_sensitive(False) + + # General group cannot be changed + if group == _('General'): + rename_item.set_sensitive(False) + block_menuitem.set_sensitive(False) + remove_item.set_sensitive(False) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_contact_menu(self, event, iter): + '''Make contact\'s popup menu''' + model = self.tree.get_model() + jid = model[iter][C_JID].decode('utf-8') + tree_path = model.get_path(iter) + account = model[iter][C_ACCOUNT].decode('utf-8') + our_jid = jid == gajim.get_jid_from_account(account) contact = gajim.contacts.get_contact_with_highest_priority(account, jid) if not contact: - keyID = '' - attached_keys = gajim.config.get_per('accounts', account, - 'attached_gpg_keys').split() - if jid in attached_keys: - keyID = attached_keys[attached_keys.index(jid) + 1] - contact = gajim.contacts.create_contact(jid = jid, name = nickname, - groups = groups, show = 'requested', status = '', ask = 'none', - sub = 'subscribe', keyID = keyID) - gajim.contacts.add_contact(account, contact) + return + + # Zeroconf Account + if gajim.config.get_per('accounts', account, 'is_zeroconf'): + xml = gtkgui_helpers.get_glade('zeroconf_contact_context_menu.glade') + zeroconf_contact_context_menu = xml.get_widget( + 'zeroconf_contact_context_menu') + + start_chat_menuitem = xml.get_widget('start_chat_menuitem') + rename_menuitem = xml.get_widget('rename_menuitem') + edit_groups_menuitem = xml.get_widget('edit_groups_menuitem') + send_file_menuitem = xml.get_widget('send_file_menuitem') + assign_openpgp_key_menuitem = xml.get_widget( + 'assign_openpgp_key_menuitem') + add_special_notification_menuitem = xml.get_widget( + 'add_special_notification_menuitem') + + if not our_jid: + # add a special img for rename menuitem + path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path_to_kbd_input_img) + rename_menuitem.set_image(img) + + above_information_separator = xml.get_widget( + 'above_information_separator') + + information_menuitem = xml.get_widget('information_menuitem') + history_menuitem = xml.get_widget('history_menuitem') + + contacts = gajim.contacts.get_contacts(account, jid) + if len(contacts) > 1: # several resources + sub_menu = gtk.Menu() + start_chat_menuitem.set_submenu(sub_menu) + + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for c in contacts: + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + item = gtk.ImageMenuItem('%s (%s)' % (c.resource, + str(c.priority))) + icon_name = helpers.get_icon_name_to_show(c, account) + icon = state_images[icon_name] + item.set_image(icon) + sub_menu.append(item) + item.connect('activate', self.on_open_chat_window, c, account, + c.resource) + + else: # one resource + start_chat_menuitem.connect('activate', + self.on_roster_treeview_row_activated, tree_path) + + if contact.resource: + send_file_menuitem.connect('activate', + self.on_send_file_menuitem_activate, contact, account) + else: # if we do no have resource we cannot do much + send_file_menuitem.set_sensitive(False) + + rename_menuitem.connect('activate', self.on_rename, iter, tree_path) + if contact.show in ('offline', 'error'): + information_menuitem.set_sensitive(False) + send_file_menuitem.set_sensitive(False) + else: + information_menuitem.connect('activate', self.on_info_zeroconf, + contact, account) + history_menuitem.connect('activate', self.on_history, contact, + account) + + if _('Not in Roster') not in contact.groups: + # contact is in normal group + edit_groups_menuitem.set_no_show_all(False) + assign_openpgp_key_menuitem.set_no_show_all(False) + edit_groups_menuitem.connect('activate', self.on_edit_groups, [( + contact,account)]) + + if gajim.connections[account].gpg: + assign_openpgp_key_menuitem.connect('activate', + self.on_assign_pgp_key, contact, account) + else: + assign_openpgp_key_menuitem.set_sensitive(False) + + else: # contact is in group 'Not in Roster' + edit_groups_menuitem.set_sensitive(False) + edit_groups_menuitem.set_no_show_all(True) + assign_openpgp_key_menuitem.set_sensitive(False) + + # Remove many items when it's self contact row + if our_jid: + for menuitem in (rename_menuitem, edit_groups_menuitem, + above_information_separator): + menuitem.set_no_show_all(True) + menuitem.hide() + + # Unsensitive many items when account is offline + if gajim.connections[account].connected < 2: + for widget in [start_chat_menuitem, rename_menuitem, + edit_groups_menuitem, send_file_menuitem]: + widget.set_sensitive(False) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + zeroconf_contact_context_menu.attach_to_widget(self.tree, None) + zeroconf_contact_context_menu.connect('selection-done', + gtkgui_helpers.destroy_widget) + zeroconf_contact_context_menu.show_all() + zeroconf_contact_context_menu.popup(None, None, None, event_button, + event.time) + return + + # normal account + xml = gtkgui_helpers.get_glade('roster_contact_context_menu.glade') + roster_contact_context_menu = xml.get_widget( + 'roster_contact_context_menu') + + start_chat_menuitem = xml.get_widget('start_chat_menuitem') + send_custom_status_menuitem = xml.get_widget( + 'send_custom_status_menuitem') + send_single_message_menuitem = xml.get_widget( + 'send_single_message_menuitem') + invite_menuitem = xml.get_widget('invite_menuitem') + block_menuitem = xml.get_widget('block_menuitem') + unblock_menuitem = xml.get_widget('unblock_menuitem') + rename_menuitem = xml.get_widget('rename_menuitem') + edit_groups_menuitem = xml.get_widget('edit_groups_menuitem') + send_file_menuitem = xml.get_widget('send_file_menuitem') + assign_openpgp_key_menuitem = xml.get_widget( + 'assign_openpgp_key_menuitem') + set_custom_avatar_menuitem = xml.get_widget('set_custom_avatar_menuitem') + add_special_notification_menuitem = xml.get_widget( + 'add_special_notification_menuitem') + execute_command_menuitem = xml.get_widget( + 'execute_command_menuitem') + + # send custom status icon + blocked = False + if jid in gajim.connections[account].blocked_contacts: + blocked = True else: - if not _('Not in Roster') in contact.groups: - dialogs.InformationDialog(_('Subscription request has been sent'), - _('If "%s" accepts this request you will know his or her status.' - ) % jid) - return - contact.groups = groups - if nickname: - contact.name = nickname - self.remove_contact(contact, account) - self.add_contact_to_roster(jid, account) + groups = contact.groups + if contact.is_observer(): + groups = [_('Observers')] + elif not groups: + groups = [_('General')] + for group in groups: + if group in gajim.connections[account].blocked_groups: + blocked = True + break + if blocked: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline')) + send_custom_status_menuitem.set_sensitive(False) + elif gajim.interface.status_sent_to_users.has_key(account) and \ + jid in gajim.interface.status_sent_to_users[account]: + send_custom_status_menuitem.set_image( + gtkgui_helpers.load_icon(gajim.interface.status_sent_to_users[account][jid])) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) - def revoke_auth(self, widget, jid, account): - '''Revoke a contact's authorization''' - gajim.connections[account].refuse_authorization(jid) - dialogs.InformationDialog(_('Authorization has been removed'), - _('Now "%s" will always see you as offline.') %jid) + if not our_jid: + # add a special img for rename menuitem + path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path_to_kbd_input_img) + rename_menuitem.set_image(img) - def on_roster_treeview_scroll_event(self, widget, event): - self.tooltip.hide_tooltip() + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_menuitem.set_image(muc_icon) - def on_roster_treeview_key_press_event(self, widget, event): - '''when a key is pressed in the treeviews''' - self.tooltip.hide_tooltip() - if event.keyval == gtk.keysyms.Escape: - self.tree.get_selection().unselect_all() - elif event.keyval == gtk.keysyms.F2: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - type_ = model[path][C_TYPE] - if type_ in ('contact', 'group', 'agent'): - iter = model.get_iter(path) - self.on_rename(widget, iter, path) + self.build_invite_submenu(invite_menuitem, [(contact, account)]) - elif event.keyval == gtk.keysyms.Delete: - treeselection = self.tree.get_selection() - model, list_of_paths = treeselection.get_selected_rows() - if not len(list_of_paths): - return - type_ = model[list_of_paths[0]][C_TYPE] - account = model[list_of_paths[0]][C_ACCOUNT] - list_ = [] - for path in list_of_paths: - if model[path][C_TYPE] != type_: - return - jid = model[path][C_JID].decode('utf-8') - account = model[path][C_ACCOUNT].decode('utf-8') - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - list_.append((contact, account)) - if type_ in ('account', 'group', 'self_contact') or \ - account == gajim.ZEROCONF_ACC_NAME: - return - if type_ == 'contact': - self.on_req_usub(widget, list_) - elif type_ == 'agent': - self.on_remove_agent(widget, list_) + # Subscription submenu + subscription_menuitem = xml.get_widget('subscription_menuitem') + send_auth_menuitem, ask_auth_menuitem, revoke_auth_menuitem =\ + subscription_menuitem.get_submenu().get_children() + add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem') + remove_from_roster_menuitem = xml.get_widget( + 'remove_from_roster_menuitem') + information_menuitem = xml.get_widget('information_menuitem') + history_menuitem = xml.get_widget('history_menuitem') + + contacts = gajim.contacts.get_contacts(account, jid) + + # One or several resource, we do the same for send_custom_status + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ['online', 'chat', 'away', 'xa', 'dnd', 'offline']: + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', self.on_send_custom_status, + [(contact, account)], s) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) + if len(contacts) > 1: # several resources + start_chat_menuitem.set_submenu(self.build_resources_submenu(contacts, + account, self.on_open_chat_window)) + send_file_menuitem.set_submenu(self.build_resources_submenu(contacts, + account, self.on_send_file_menuitem_activate)) + execute_command_menuitem.set_submenu(self.build_resources_submenu( + contacts, account, self.on_execute_command)) + + else: # one resource + start_chat_menuitem.connect('activate', + self.on_open_chat_window, contact, account) + execute_command_menuitem.connect('activate', self.on_execute_command, + contact, account, contact.resource) + + our_jid_other_resource = None + if our_jid: + # It's another resource of us, be sure to send invite to her + our_jid_other_resource = contact.resource + # Else this var is useless but harmless in next connect calls + + if contact.resource: + send_file_menuitem.connect('activate', + self.on_send_file_menuitem_activate, contact, account) + else: # if we do not have resource we cannot send file + send_file_menuitem.set_sensitive(False) + + send_single_message_menuitem.connect('activate', + self.on_send_single_message_menuitem_activate, account, contact) + + rename_menuitem.connect('activate', self.on_rename, iter, tree_path) + remove_from_roster_menuitem.connect('activate', self.on_req_usub, + [(contact, account)]) + information_menuitem.connect('activate', self.on_info, contact, + account) + history_menuitem.connect('activate', self.on_history, contact, + account) + + if _('Not in Roster') not in contact.groups: + # contact is in normal group + add_to_roster_menuitem.hide() + add_to_roster_menuitem.set_no_show_all(True) + edit_groups_menuitem.connect('activate', self.on_edit_groups, [( + contact,account)]) + + if gajim.connections[account].gpg: + assign_openpgp_key_menuitem.connect('activate', + self.on_assign_pgp_key, contact, account) + else: + assign_openpgp_key_menuitem.set_sensitive(False) + + if contact.sub in ('from', 'both'): + send_auth_menuitem.set_sensitive(False) + else: + send_auth_menuitem.connect('activate', self.authorize, jid, account) + if contact.sub in ('to', 'both'): + ask_auth_menuitem.set_sensitive(False) + add_special_notification_menuitem.connect('activate', + self.on_add_special_notification_menuitem_activate, jid) + else: + ask_auth_menuitem.connect('activate', self.req_sub, jid, + _('I would like to add you to my roster'), account, + contact.groups, contact.name) + if contact.sub in ('to', 'none'): + revoke_auth_menuitem.set_sensitive(False) + else: + revoke_auth_menuitem.connect('activate', self.revoke_auth, jid, + account) + + else: # contact is in group 'Not in Roster' + add_to_roster_menuitem.set_no_show_all(False) + edit_groups_menuitem.set_sensitive(False) + assign_openpgp_key_menuitem.set_sensitive(False) + subscription_menuitem.set_sensitive(False) + + add_to_roster_menuitem.connect('activate', + self.on_add_to_roster, contact, account) + + set_custom_avatar_menuitem.connect('activate', + self.on_set_custom_avatar_activate, contact, account) + # Hide items when it's self contact row + if our_jid: + menuitem = xml.get_widget('manage_contact') + menuitem.set_sensitive(False) + + # Unsensitive many items when account is offline + if gajim.connections[account].connected < 2: + for widget in [start_chat_menuitem, send_single_message_menuitem, + rename_menuitem, edit_groups_menuitem, send_file_menuitem, + subscription_menuitem, add_to_roster_menuitem, + remove_from_roster_menuitem, execute_command_menuitem, + send_custom_status_menuitem]: + widget.set_sensitive(False) + + if gajim.connections[account] and gajim.connections[account].\ + privacy_rules_supported: + if jid in gajim.connections[account].blocked_contacts: + block_menuitem.set_no_show_all(True) + unblock_menuitem.connect('activate', self.on_unblock, iter, None) + block_menuitem.hide() + else: + unblock_menuitem.set_no_show_all(True) + block_menuitem.connect('activate', self.on_block, iter, None) + unblock_menuitem.hide() + else: + unblock_menuitem.set_no_show_all(True) + block_menuitem.set_sensitive(False) + unblock_menuitem.hide() + + event_button = gtkgui_helpers.get_possible_button_event(event) + + roster_contact_context_menu.attach_to_widget(self.tree, None) + roster_contact_context_menu.connect('selection-done', + gtkgui_helpers.destroy_widget) + roster_contact_context_menu.show_all() + roster_contact_context_menu.popup(None, None, None, event_button, + event.time) + + def make_multiple_contact_menu(self, event, iters): + '''Make group's popup menu''' + model = self.tree.get_model() + list_ = [] # list of (jid, account) tuples + one_account_offline = False + is_blocked = True + for iter in iters: + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + if gajim.connections[account].connected < 2: + one_account_offline = True + contact = gajim.contacts.get_contact_with_highest_priority(account, + jid) + if jid not in gajim.connections[account].blocked_contacts: + is_blocked = False + list_.append((contact, account)) + + menu = gtk.Menu() + account = None + for (contact, current_account) in list_: + # check that we use the same account for every sender + if account is not None and account != current_account: + account = None + break + account = current_account + if account is not None: + send_group_message_item = gtk.ImageMenuItem(_('Send Group M_essage')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + send_group_message_item.set_image(icon) + menu.append(send_group_message_item) + send_group_message_item.connect('activate', + self.on_send_single_message_menuitem_activate, account, list_) + + # Invite to Groupchat + invite_item = gtk.ImageMenuItem(_('In_vite to')) + muc_icon = gtkgui_helpers.load_icon('muc_active') + if muc_icon: + invite_item.set_image(muc_icon) + + self.build_invite_submenu(invite_item, list_) + menu.append(invite_item) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Manage Transport submenu + item = gtk.ImageMenuItem(_('_Manage Contacts')) + icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_contacts_submenu = gtk.Menu() + item.set_submenu(manage_contacts_submenu) + menu.append(item) + + # Edit Groups + edit_groups_item = gtk.ImageMenuItem(_('Edit _Groups')) + icon = gtk.image_new_from_stock(gtk.STOCK_EDIT, gtk.ICON_SIZE_MENU) + edit_groups_item.set_image(icon) + manage_contacts_submenu.append(edit_groups_item) + edit_groups_item.connect('activate', self.on_edit_groups, list_) + + item = gtk.SeparatorMenuItem() # separator + manage_contacts_submenu.append(item) + + # Block + if is_blocked and gajim.connections[account].privacy_rules_supported: + unblock_menuitem = gtk.ImageMenuItem(_('_Unblock')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + unblock_menuitem.set_image(icon) + unblock_menuitem.connect('activate', self.on_unblock, None, list_) + manage_contacts_submenu.append(unblock_menuitem) + else: + block_menuitem = gtk.ImageMenuItem(_('_Block')) + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + block_menuitem.set_image(icon) + block_menuitem.connect('activate', self.on_block, None, list_) + manage_contacts_submenu.append(block_menuitem) + + if not gajim.connections[account].privacy_rules_supported: + block_menuitem.set_sensitive(False) + + # Remove + remove_item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + remove_item.set_image(icon) + manage_contacts_submenu.append(remove_item) + remove_item.connect('activate', self.on_req_usub, list_) + # unsensitive remove if one account is not connected + if one_account_offline: + remove_item.set_sensitive(False) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_transport_menu(self, event, iter): + '''Make transport\'s popup menu''' + model = self.tree.get_model() + jid = model[iter][C_JID].decode('utf-8') + path = model.get_path(iter) + account = model[iter][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + menu = gtk.Menu() + + # Send single message + item = gtk.ImageMenuItem(_('Send Single Message')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', + self.on_send_single_message_menuitem_activate, account, contact) + menu.append(item) + + blocked = False + if jid in gajim.connections[account].blocked_contacts: + blocked = True + + # Send Custom Status + send_custom_status_menuitem = gtk.ImageMenuItem(_('Send Cus_tom Status')) + # add a special img for this menuitem + if blocked: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon('offline')) + send_custom_status_menuitem.set_sensitive(False) + else: + if gajim.interface.status_sent_to_users.has_key(account) and \ + jid in gajim.interface.status_sent_to_users[account]: + send_custom_status_menuitem.set_image(gtkgui_helpers.load_icon( + gajim.interface.status_sent_to_users[account][jid])) + else: + icon = gtk.image_new_from_stock(gtk.STOCK_NETWORK, + gtk.ICON_SIZE_MENU) + send_custom_status_menuitem.set_image(icon) + status_menuitems = gtk.Menu() + send_custom_status_menuitem.set_submenu(status_menuitems) + iconset = gajim.config.get('iconset') + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for s in ['online', 'chat', 'away', 'xa', 'dnd', 'offline']: + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + status_menuitem = gtk.ImageMenuItem(helpers.get_uf_show(s)) + status_menuitem.connect('activate', self.on_send_custom_status, + [(contact, account)], s) + icon = state_images[s] + status_menuitem.set_image(icon) + status_menuitems.append(status_menuitem) + menu.append(send_custom_status_menuitem) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Execute Command + item = gtk.ImageMenuItem(_('Execute Command...')) + icon = gtk.image_new_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_MENU) + item.set_image(icon) + menu.append(item) + item.connect('activate', self.on_execute_command, contact, account, + contact.resource) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Manage Transport submenu + item = gtk.ImageMenuItem(_('_Manage Transport')) + icon = gtk.image_new_from_stock(gtk.STOCK_PROPERTIES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu = gtk.Menu() + item.set_submenu(manage_transport_submenu) + menu.append(item) + + # Modify Transport + item = gtk.ImageMenuItem(_('_Modify Transport')) + icon = gtk.image_new_from_stock(gtk.STOCK_PREFERENCES, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + item.connect('activate', self.on_edit_agent, contact, account) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Rename + item = gtk.ImageMenuItem(_('_Rename')) + # add a special img for rename menuitem + path_to_kbd_input_img = os.path.join(gajim.DATA_DIR, 'pixmaps', + 'kbd_input.png') + img = gtk.Image() + img.set_from_file(path_to_kbd_input_img) + item.set_image(img) + manage_transport_submenu.append(item) + item.connect('activate', self.on_rename, iter, path) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + item = gtk.SeparatorMenuItem() # separator + manage_transport_submenu.append(item) + + # Block + if blocked: + item = gtk.ImageMenuItem(_('_Unblock')) + item.connect('activate', self.on_unblock, iter, None) + else: + item = gtk.ImageMenuItem(_('_Block')) + item.connect('activate', self.on_block, iter, None) + + icon = gtk.image_new_from_stock(gtk.STOCK_STOP, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + # Remove + item = gtk.ImageMenuItem(_('_Remove')) + icon = gtk.image_new_from_stock(gtk.STOCK_REMOVE, gtk.ICON_SIZE_MENU) + item.set_image(icon) + manage_transport_submenu.append(item) + item.connect('activate', self.on_remove_agent, [(contact, account)]) + if gajim.account_is_disconnected(account): + item.set_sensitive(False) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # Information + information_menuitem = gtk.ImageMenuItem(_('_Information')) + icon = gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU) + information_menuitem.set_image(icon) + menu.append(information_menuitem) + information_menuitem.connect('activate', self.on_info, contact, account) + + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def make_groupchat_menu(self, event, iter): + model = self.tree.get_model() + + path = model.get_path(iter) + jid = model[iter][C_JID].decode('utf-8') + account = model[iter][C_ACCOUNT].decode('utf-8') + contact = gajim.contacts.get_contact_with_highest_priority(account, jid) + menu = gtk.Menu() + + if jid in gajim.interface.minimized_controls[account]: + maximize_menuitem = gtk.ImageMenuItem(_('_Maximize')) + icon = gtk.image_new_from_stock(gtk.STOCK_GOTO_TOP, gtk.ICON_SIZE_MENU) + maximize_menuitem.set_image(icon) + maximize_menuitem.connect('activate', self.on_groupchat_maximized, \ + jid, account) + menu.append(maximize_menuitem) + + disconnect_menuitem = gtk.ImageMenuItem(_('_Disconnect')) + disconnect_icon = gtk.image_new_from_stock(gtk.STOCK_DISCONNECT, \ + gtk.ICON_SIZE_MENU) + disconnect_menuitem.set_image(disconnect_icon) + disconnect_menuitem .connect('activate', self.on_disconnect, jid, account) + menu.append(disconnect_menuitem) + + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + history_menuitem = gtk.ImageMenuItem(_('_History')) + history_icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, \ + gtk.ICON_SIZE_MENU) + history_menuitem.set_image(history_icon) + history_menuitem .connect('activate', self.on_history, \ + contact, account) + menu.append(history_menuitem) + + event_button = gtkgui_helpers.get_possible_button_event(event) + + menu.attach_to_widget(self.tree, None) + menu.connect('selection-done', gtkgui_helpers.destroy_widget) + menu.show_all() + menu.popup(None, None, None, event_button, event.time) + + def build_resources_submenu(self, contacts, account, action, room_jid=None, + room_account=None): + ''' Build a submenu with contact's resources. + room_jid and room_account are for action self.on_invite_to_room ''' + sub_menu = gtk.Menu() + + iconset = gajim.config.get('iconset') + if not iconset: + iconset = gajim.config.DEFAULT_ICONSET + path = os.path.join(helpers.get_iconset_path(iconset), '16x16') + for c in contacts: + # icon MUST be different instance for every item + state_images = gtkgui_helpers.load_iconset(path) + item = gtk.ImageMenuItem('%s (%s)' % (c.resource, str(c.priority))) + icon_name = helpers.get_icon_name_to_show(c, account) + icon = state_images[icon_name] + item.set_image(icon) + sub_menu.append(item) + if action == self.on_invite_to_room: + item.connect('activate', action, [(c, account)], + room_jid, room_account, c.resource) + elif action == self.on_invite_to_new_room: + item.connect('activate', action, [(c, account)], c.resource) + else: # start_chat, execute_command, send_file + item.connect('activate', action, c, account, c.resource) + return sub_menu + + def build_invite_submenu(self, invite_menuitem, list_): + '''list_ in a list of (contact, account)''' + # used if we invite only one contact with several resources + contact_list = [] + if len(list_) == 1: + contact, account = list_[0] + contact_list = gajim.contacts.get_contacts(account, contact.jid) + contacts_transport = -1 + connected_accounts = [] + # -1 is at start, False when not from the same, None when jabber + for (contact, account) in list_: + if not account in connected_accounts: + connected_accounts.append(account) + transport = gajim.get_transport_name_from_jid(contact.jid) + if contacts_transport == -1: + contacts_transport = transport + elif contacts_transport != transport: + contacts_transport = False + + if contacts_transport == False: + # they are not all from the same transport + invite_menuitem.set_sensitive(False) + return + invite_to_submenu = gtk.Menu() + invite_menuitem.set_submenu(invite_to_submenu) + invite_to_new_room_menuitem = gtk.ImageMenuItem(_('_New Group Chat')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + invite_to_new_room_menuitem.set_image(icon) + if len(contact_list) > 1: # several resources + invite_to_new_room_menuitem.set_submenu(self.build_resources_submenu( + contact_list, account, self.on_invite_to_new_room)) + else: + invite_to_new_room_menuitem.connect('activate', + self.on_invite_to_new_room, list_) + # transform None in 'jabber' + c_t = contacts_transport or 'jabber' + muc_jid = {} + for account in connected_accounts: + for t in gajim.connections[account].muc_jid: + muc_jid[t] = gajim.connections[account].muc_jid[t] + if not muc_jid.has_key(c_t): + invite_to_new_room_menuitem.set_sensitive(False) + rooms = [] # a list of (room_jid, account) tuple + invite_to_submenu.append(invite_to_new_room_menuitem) + rooms = [] # a list of (room_jid, account) tuple + minimized_controls = [] + for account in connected_accounts: + minimized_controls += \ + gajim.interface.minimized_controls[account].values() + for gc_control in gajim.interface.msg_win_mgr.get_controls( + message_control.TYPE_GC) + minimized_controls: + acct = gc_control.account + room_jid = gc_control.room_jid + if gajim.gc_connected[acct].has_key(room_jid) and \ + gajim.gc_connected[acct][room_jid] and \ + contacts_transport == gajim.get_transport_name_from_jid(room_jid): + rooms.append((room_jid, acct)) + if len(rooms): + item = gtk.SeparatorMenuItem() # separator + invite_to_submenu.append(item) + for (room_jid, account) in rooms: + menuitem = gtk.MenuItem(room_jid.split('@')[0]) + if len(contact_list) > 1: # several resources + menuitem.set_submenu(self.build_resources_submenu( + contact_list, account, self.on_invite_to_room, room_jid, + account)) + else: + menuitem.connect('activate', self.on_invite_to_room, list_, + room_jid, account) + invite_to_submenu.append(menuitem) + + def get_and_connect_advanced_menuitem_menu(self, account): + '''adds FOR ACCOUNT options''' + xml = gtkgui_helpers.get_glade('advanced_menuitem_menu.glade') + advanced_menuitem_menu = xml.get_widget('advanced_menuitem_menu') + + xml_console_menuitem = xml.get_widget('xml_console_menuitem') + privacy_lists_menuitem = xml.get_widget('privacy_lists_menuitem') + administrator_menuitem = xml.get_widget('administrator_menuitem') + send_server_message_menuitem = xml.get_widget( + 'send_server_message_menuitem') + set_motd_menuitem = xml.get_widget('set_motd_menuitem') + update_motd_menuitem = xml.get_widget('update_motd_menuitem') + delete_motd_menuitem = xml.get_widget('delete_motd_menuitem') + + xml_console_menuitem.connect('activate', + self.on_xml_console_menuitem_activate, account) + + if gajim.connections[account] and gajim.connections[account].\ + privacy_rules_supported: + privacy_lists_menuitem.connect('activate', + self.on_privacy_lists_menuitem_activate, account) + else: + privacy_lists_menuitem.set_sensitive(False) + + if gajim.connections[account].is_zeroconf: + administrator_menuitem.set_sensitive(False) + send_server_message_menuitem.set_sensitive(False) + set_motd_menuitem.set_sensitive(False) + update_motd_menuitem.set_sensitive(False) + delete_motd_menuitem.set_sensitive(False) + else: + send_server_message_menuitem.connect('activate', + self.on_send_server_message_menuitem_activate, account) + + set_motd_menuitem.connect('activate', + self.on_set_motd_menuitem_activate, account) + + update_motd_menuitem.connect('activate', + self.on_update_motd_menuitem_activate, account) + + delete_motd_menuitem.connect('activate', + self.on_delete_motd_menuitem_activate, account) + + advanced_menuitem_menu.show_all() + + return advanced_menuitem_menu + + def add_history_manager_menuitem(self, menu): + '''adds a seperator and History Manager menuitem BELOW for account + menuitems''' + item = gtk.SeparatorMenuItem() # separator + menu.append(item) + + # History manager + item = gtk.ImageMenuItem(_('History Manager')) + icon = gtk.image_new_from_stock(gtk.STOCK_JUSTIFY_FILL, + gtk.ICON_SIZE_MENU) + item.set_image(icon) + menu.append(item) + item.connect('activate', self.on_history_manager_menuitem_activate) + + def add_bookmarks_list(self, gc_sub_menu, account): + '''Show join new group chat item and bookmarks list for an account''' + item = gtk.ImageMenuItem(_('_Join New Group Chat')) + icon = gtk.image_new_from_stock(gtk.STOCK_NEW, gtk.ICON_SIZE_MENU) + item.set_image(icon) + item.connect('activate', self.on_join_gc_activate, account) + gc_sub_menu.append(item) + + # user has at least one bookmark + if len(gajim.connections[account].bookmarks) > 0: + item = gtk.SeparatorMenuItem() # separator + gc_sub_menu.append(item) + + for bookmark in gajim.connections[account].bookmarks: + item = gtk.MenuItem(bookmark['name'], False) # Do not use underline + item.connect('activate', self.on_bookmark_menuitem_activate, + account, bookmark) + gc_sub_menu.append(item) + + def set_actions_menu_needs_rebuild(self): + self.actions_menu_needs_rebuild = True + # Force the rebuild now since the on_activates on the menu itself does + # not work with the os/x top level menubar + if sys.platform == 'darwin': + self.make_menu(force = True) + return + def show_appropriate_context_menu(self, event, iters): # iters must be all of the same type model = self.tree.get_model() @@ -3311,1914 +5713,10 @@ class RosterWindow: self.show_appropriate_context_menu(event, iters) return True - - def on_roster_treeview_button_release_event(self, widget, event): - try: - path, column, x, y = self.tree.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - return False - - if event.button == 1: # Left click - if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ - not event.state & gtk.gdk.CONTROL_MASK: - # Check if button has been pressed on the same row - if self.clicked_path == path: - self.on_row_activated(widget, path) - self.clicked_path = None - - def on_roster_treeview_button_press_event(self, widget, event): - # hide tooltip, no matter the button is pressed - self.tooltip.hide_tooltip() - try: - path, column, x, y = self.tree.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - self.tree.get_selection().unselect_all() - return False - - if event.button == 3: # Right click - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - list_of_paths = [] - if path not in list_of_paths: - self.tree.get_selection().unselect_all() - self.tree.get_selection().select_path(path) - return self.show_treeview_menu(event) - - elif event.button == 2: # Middle click - try: - model, list_of_paths = self.tree.get_selection().get_selected_rows() - except TypeError: - list_of_paths = [] - if list_of_paths != [path]: - self.tree.get_selection().unselect_all() - self.tree.get_selection().select_path(path) - type_ = model[path][C_TYPE] - if type_ in ('agent', 'contact', 'self_contact', 'groupchat'): - self.on_row_activated(widget, path) - elif type_ == 'account': - account = model[path][C_ACCOUNT].decode('utf-8') - if account != 'all': - show = gajim.connections[account].connected - if show > 1: # We are connected - self.on_change_status_message_activate(widget, account) - return True - show = helpers.get_global_show() - if show == 'offline': - return True - dlg = dialogs.ChangeStatusMessageDialog(show) - message = dlg.run() - if not message: - return True - for acct in gajim.connections: - if not gajim.config.get_per('accounts', acct, - 'sync_with_global_status'): - continue - current_show = gajim.SHOW_LIST[gajim.connections[acct].connected] - self.send_status(acct, current_show, message) - return True - - elif event.button == 1: # Left click - model = self.tree.get_model() - type_ = model[path][C_TYPE] - # x_min is the x start position of status icon column - if gajim.config.get('avatar_position_in_roster') == 'left': - x_min = gajim.config.get('roster_avatar_width') - else: - x_min = 0 - if gajim.single_click and not event.state & gtk.gdk.SHIFT_MASK and \ - not event.state & gtk.gdk.CONTROL_MASK: - # Don't handle dubble click if we press icon of a metacontact - iter = model.get_iter(path) - if x > x_min and x < x_min + 27 and type_ == 'contact' and \ - model.iter_has_child(iter): - account = model[path][C_ACCOUNT].decode('utf-8') - jid = model[path][C_JID].decode('utf-8') - # first cell in 1st column (the arrow SINGLE clicked) - iters = self.get_contact_iter(jid, account) - for iter in iters: - path = model.get_path(iter) - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return - # We just save on which row we press button, and open chat window on - # button release to be able to do DND without opening chat window - self.clicked_path = path - return - else: - if type_ == 'group' and x < 27: - # first cell in 1st column (the arrow SINGLE clicked) - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - - elif type_ == 'contact' and x > x_min and x < x_min + 27: - account = model[path][C_ACCOUNT].decode('utf-8') - jid = model[path][C_JID].decode('utf-8') - # first cell in 1st column (the arrow SINGLE clicked) - iters = self.get_contact_iter(jid, account) - for iter in iters: - path = model.get_path(iter) - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - - def on_req_usub(self, widget, list_): - '''Remove a contact. list_ is a list of (contact, account) tuples''' - def on_ok(is_checked, list_): - remove_auth = True - if len(list_) == 1: - contact = list_[0][0] - if contact.sub != 'to' and is_checked: - remove_auth = False - for (contact, account) in list_: - gajim.connections[account].unsubscribe(contact.jid, remove_auth) - for c in gajim.contacts.get_contacts(account, contact.jid): - self.remove_contact(c, account) - gajim.contacts.remove_jid(account, contact.jid) - self.draw_account(account) - if not remove_auth and contact.sub == 'both': - contact.name = '' - contact.groups = [] - contact.sub = 'from' - gajim.contacts.add_contact(account, contact) - self.add_contact_to_roster(contact.jid, account) - else: - if _('Not in Roster') in contact.groups: - gajim.events.remove_events(account, contact.jid) - self.readd_if_needed(contact, account) - - def on_ok2(list_): - on_ok(False, list_) - - if len(list_) == 1: - contact = list_[0][0] - account = list_[0][1] - pritext = _('Contact "%s" will be removed from your roster') % \ - contact.get_shown_name() - if contact.sub == 'to': - dialogs.ConfirmationDialog(pritext, - _('By removing this contact you also remove authorization ' - 'resulting in him or her always seeing you as offline.'), - on_response_ok = (on_ok2, list_)) - else: - dialogs.ConfirmationDialogCheck(pritext, - _('By removing this contact you also by default remove ' - 'authorization resulting in him or her always seeing you as ' - 'offline.'), - _('I want this contact to know my status after removal'), - on_response_ok = (on_ok, list_)) - else: - # several contact to remove at the same time - pritext = _('Contacts will be removed from your roster') - jids = '' - for (contact, account) in list_: - jids += '\n ' + contact.get_shown_name() + ',' - sectext = _('By removing these contacts:%s\nyou also remove ' - 'authorization resulting in them always seeing you as offline.') % \ - jids - dialogs.ConfirmationDialog(pritext, sectext, - on_response_ok = (on_ok2, list_)) - - - def set_connecting_state(self, account): - model = self.tree.get_model() - accountIter = self.get_account_iter(account) - if accountIter: - model[accountIter][0] = gajim.interface.jabber_state_images['16'][ - 'connecting'] - if gajim.interface.systray_enabled: - gajim.interface.systray.change_status('connecting') - - def send_status(self, account, status, txt, auto = False, to = None): - model = self.tree.get_model() - accountIter = self.get_account_iter(account) - if status != 'offline': - if gajim.connections[account].connected < 2: - self.set_connecting_state(account) - - if not gajim.connections[account].password: - passphrase = '' - text = _('Enter your password for account %s') % account - if passwords.USER_HAS_GNOMEKEYRING and \ - not passwords.USER_USES_GNOMEKEYRING: - text += '\n' + _('Gnome Keyring is installed but not correctly started\ - (environment variable probably not correctly set)') - w = dialogs.PassphraseDialog(_('Password Required'), text, - _('Save password')) - passphrase, save = w.run() - if passphrase == -1: - if accountIter: - model[accountIter][0] = gajim.interface.\ - jabber_state_images['16']['offline'] - if gajim.interface.systray_enabled: - gajim.interface.systray.change_status('offline') - self.update_status_combobox() - return - gajim.connections[account].password = passphrase - if save: - gajim.config.set_per('accounts', account, 'savepass', True) - passwords.save_password(account, passphrase) - - keyid = gajim.config.get_per('accounts', account, 'keyid') - if keyid and not gajim.connections[account].gpg: - dialog = dialogs.WarningDialog(_('GPG is not usable'), - _('You will be connected to %s without OpenPGP.') % account) - - if gajim.account_is_connected(account): - if status == 'online' and gajim.interface.sleeper.getState() != \ - common.sleepy.STATE_UNKNOWN: - gajim.sleeper_state[account] = 'online' - elif gajim.sleeper_state[account] not in ('autoaway', 'autoxa'): - gajim.sleeper_state[account] = 'off' - if to: - gajim.connections[account].send_custom_status(status, txt, to) - else: - was_invisible = gajim.connections[account].connected == \ - gajim.SHOW_LIST.index('invisible') - gajim.connections[account].change_status(status, txt, auto) - - if gajim.interface.status_sent_to_users.has_key(account): - gajim.interface.status_sent_to_users[account] = {} - if gajim.interface.status_sent_to_groups.has_key(account): - gajim.interface.status_sent_to_groups[account] = {} - for gc_control in gajim.interface.msg_win_mgr.get_controls( - message_control.TYPE_GC) + \ - gajim.interface.minimized_controls[account].values(): - if gc_control.account == account: - if gajim.gc_connected[account][gc_control.room_jid]: - gajim.connections[account].send_gc_status(gc_control.nick, - gc_control.room_jid, status, txt) - else: - # for some reason, we are not connected to the room even if - # tab is opened, send initial join_gc() - gajim.connections[account].join_gc(gc_control.nick, - gc_control.room_jid, None) - if was_invisible and status != 'offline': - # We come back from invisible, join bookmarks - self.auto_join_bookmarks(account) - - def get_status_message(self, show): - if show in gajim.config.get_per('defaultstatusmsg'): - if gajim.config.get_per('defaultstatusmsg', show, 'enabled'): - return gajim.config.get_per('defaultstatusmsg', show, 'message') - if (show == 'online' and not gajim.config.get('ask_online_status')) or \ - (show in ('offline', 'invisible') - and not gajim.config.get('ask_offline_status')): - return '' - dlg = dialogs.ChangeStatusMessageDialog(show) - dlg.window.present() # show it on current workspace - message = dlg.run() - return message - - def auto_join_bookmarks(self, account): - '''autojoin bookmarks that have 'auto join' on for this account''' - for bm in gajim.connections[account].bookmarks: - if bm['autojoin'] in ('1', 'true'): - jid = bm['jid'] - if not gajim.gc_connected[account].has_key(jid) or \ - not gajim.gc_connected[account][jid]: - # we are not already connected - minimize = bm['minimize'] in ('1', 'true') - self.join_gc_room(account, jid, bm['nick'], - bm['password'], minimize = minimize) - - def connected_rooms(self, account): - if True in gajim.gc_connected[account].values(): - return True - return False - - def change_status(self, widget, account, status): - def change(account, status): - message = self.get_status_message(status) - if message is None: - # user pressed Cancel to change status message dialog - return - self.send_status(account, status, message) - - if status == 'invisible' and self.connected_rooms(account): - dialogs.ConfirmationDialog( - _('You are participating in one or more group chats'), - _('Changing your status to invisible will result in disconnection ' - 'from those group chats. Are you sure you want to go invisible?'), - on_response_ok = (change, account, status)) - else: - change(account, status) - - def on_send_custom_status(self, widget, contact_list, show, group=None): - '''send custom status''' - dlg = dialogs.ChangeStatusMessageDialog(show) - message = dlg.run() - if message is not None: # None if user pressed Cancel - for (contact, account) in contact_list: - our_jid = gajim.get_jid_from_account(account) - accounts = [] - if group and account not in accounts: - if not gajim.interface.status_sent_to_groups.has_key(account): - gajim.interface.status_sent_to_groups[account] = {} - gajim.interface.status_sent_to_groups[account][group] = show - accounts.append(group) - jid = contact.jid - if jid == our_jid: - jid += '/' + contact.resource - self.send_status(account, show, message, to=jid) - if not gajim.interface.status_sent_to_users.has_key(account): - gajim.interface.status_sent_to_users[account] = {} - gajim.interface.status_sent_to_users[account][contact.jid] = show - - def on_status_combobox_changed(self, widget): - '''When we change our status via the combobox''' - model = self.status_combobox.get_model() - active = self.status_combobox.get_active() - if active == -1: # no active item - return - if not self.combobox_callback_active: - self.previous_status_combobox_active = active - return - accounts = gajim.connections.keys() - if len(accounts) == 0: - dialogs.ErrorDialog(_('No account available'), - _('You must create an account before you can chat with other contacts.')) - self.update_status_combobox() - return - status = model[active][2].decode('utf-8') - statuses_unified = helpers.statuses_unified() # status "desync'ed" or not - if (active == 7 and statuses_unified) or (active == 9 and not statuses_unified): - # 'Change status message' selected: - # do not change show, just show change status dialog - status = model[self.previous_status_combobox_active][2].decode('utf-8') - dlg = dialogs.ChangeStatusMessageDialog(status) - message = dlg.run() - if message is not None: # None if user pressed Cancel - for account in accounts: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - current_show = gajim.SHOW_LIST[ - gajim.connections[account].connected] - self.send_status(account, current_show, message) - self.combobox_callback_active = False - self.status_combobox.set_active( - self.previous_status_combobox_active) - self.combobox_callback_active = True - return - # we are about to change show, so save this new show so in case - # after user chooses "Change status message" menuitem - # we can return to this show - self.previous_status_combobox_active = active - connected_accounts = gajim.get_number_of_connected_accounts() - if status == 'invisible': - bug_user = False - for account in accounts: - if connected_accounts < 1 or gajim.account_is_connected(account): - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - # We're going to change our status to invisible - if self.connected_rooms(account): - bug_user = True - break - if bug_user: - dialog = dialogs.ConfirmationDialog( - _('You are participating in one or more group chats'), - _('Changing your status to invisible will result in ' - 'disconnection from those group chats. Are you sure you want to ' - 'go invisible?')) - if dialog.get_response() != gtk.RESPONSE_OK: - self.update_status_combobox() - return - message = self.get_status_message(status) - if message is None: # user pressed Cancel to change status message dialog - self.update_status_combobox() - return - global_sync_accounts = [] - for acct in accounts: - if gajim.config.get_per('accounts', acct, 'sync_with_global_status'): - global_sync_accounts.append(acct) - global_sync_connected_accounts = gajim.get_number_of_connected_accounts( - global_sync_accounts) - for account in accounts: - if not gajim.config.get_per('accounts', account, - 'sync_with_global_status'): - continue - # we are connected (so we wanna change show and status) - # or no account is connected and we want to connect with new show and - # status - - if not global_sync_connected_accounts > 0 or \ - gajim.connections[account].connected > 0: - self.send_status(account, status, message) - self.update_status_combobox() - - ## enable setting status msg from currently playing music track - def enable_syncing_status_msg_from_current_music_track(self, enabled): - '''if enabled is True, we listen to events from music players about - currently played music track, and we update our - status message accordinly''' - if not dbus_support.supported: - # do nothing if user doesn't have D-Bus bindings - return - if enabled: - listener = MusicTrackListener.get() - if self._music_track_changed_signal is None: - self._music_track_changed_signal = listener.connect( - 'music-track-changed', self._music_track_changed) - track = listener.get_playing_track() - self._music_track_changed(listener, track) - else: - if self._music_track_changed_signal is not None: - listener = MusicTrackListener.get() - listener.disconnect(self._music_track_changed_signal) - self._music_track_changed_signal = None - self._music_track_changed(None, None) - - ## enable setting status msg from a Last.fm account - def enable_syncing_status_msg_from_lastfm(self, enabled): - '''if enabled is True, we start polling the Last.fm server, - and we update our status message accordinly''' - if enabled: - if self._music_track_changed_signal is None: - listener = LastFMTrackListener.get( - gajim.config.get('lastfm_username')) - self._music_track_changed_signal = listener.connect( - 'music-track-changed', self._music_track_changed) - track = listener.get_playing_track() - self._music_track_changed(listener, track) - else: - if self._music_track_changed_signal is not None: - listener = LastFMTrackListener.get( - gajim.config.get('lastfm_username')) - listener.disconnect(self._music_track_changed_signal) - self._music_track_changed_signal = None - self._music_track_changed(None, None) - - def _change_awn_icon_status(self, status): - if not dbus_support.supported: - # do nothing if user doesn't have D-Bus bindings - return - try: - bus = dbus.SessionBus() - if not 'com.google.code.Awn' in bus.list_names(): - # Awn is not installed - return - except: - return - iconset = gajim.config.get('iconset') - prefix = os.path.join(helpers.get_iconset_path(iconset), '32x32') - if status in ('chat', 'away', 'xa', 'dnd', 'invisible', 'offline'): - status = status + '.png' - elif status == 'online': - prefix = os.path.join(gajim.DATA_DIR, 'pixmaps') - status = 'gajim.png' - path = os.path.join(prefix, status) - try: - obj = bus.get_object('com.google.code.Awn', '/com/google/code/Awn') - awn = dbus.Interface(obj, 'com.google.code.Awn') - awn.SetTaskIconByName('Gajim', os.path.abspath(path)) - except Exception, e: - pass - - def _music_track_changed(self, unused_listener, music_track_info, - account=''): - from common import pep - if account == '': - accounts = gajim.connections.keys() - if music_track_info is None: - artist = '' - title = '' - source = '' - track = '' - length = '' - elif hasattr(music_track_info, 'paused') and music_track_info.paused == 0: - artist = '' - title = '' - source = '' - track = '' - length = '' - else: - artist = music_track_info.artist - title = music_track_info.title - source = music_track_info.album - if account == '': - for account in accounts: - if not gajim.account_is_connected(account): - continue - if not gajim.connections[account].pep_supported: - continue - pep.user_send_tune(account, artist, title, source) - elif gajim.connections[account].pep_supported: - pep.user_send_tune(account, artist, title, source) - - def update_status_combobox(self): - # table to change index in connection.connected to index in combobox - table = {'offline':9, 'connecting':9, 'online':0, 'chat':1, 'away':2, - 'xa':3, 'dnd':4, 'invisible':5} - - # we check if there are more options in the combobox that it should - # if yes, we remove the first ones - while len(self.status_combobox.get_model()) > len(table)+2: - self.status_combobox.remove_text(0) - - show = helpers.get_global_show() - # temporarily block signal in order not to send status that we show - # in the combobox - self.combobox_callback_active = False - if helpers.statuses_unified(): - self.status_combobox.set_active(table[show]) - else: - uf_show = helpers.get_uf_show(show) - liststore = self.status_combobox.get_model() - liststore.prepend(['SEPARATOR', None, '', True]) - liststore.prepend([uf_show + ' ' + "(desync'ed)", - gajim.interface.jabber_state_images['16'][show], show, False]) - self.status_combobox.set_active(0) - self._change_awn_icon_status(show) - self.combobox_callback_active = True - if gajim.interface.systray_enabled: - gajim.interface.systray.change_status(show) - - def set_account_status_icon(self, account): - status = gajim.connections[account].connected - model = self.tree.get_model() - accountIter = self.get_account_iter(account) - if not accountIter: - return - if not self.regroup: - show = gajim.SHOW_LIST[status] - else: # accounts merged - show = helpers.get_global_show() - model[accountIter][C_IMG] = gajim.interface.jabber_state_images['16'][ - show] - - def on_status_changed(self, account, status): - '''the core tells us that our status has changed''' - if account not in gajim.contacts.get_accounts(): - return - model = self.tree.get_model() - accountIter = self.get_account_iter(account) - self.set_account_status_icon(account) - if status == 'offline': - if self.quit_on_next_offline > -1: - # we want to quit, we are waiting for all accounts to be offline - self.quit_on_next_offline -= 1 - if self.quit_on_next_offline < 1: - # all accounts offline, quit - self.quit_gtkgui_interface() - else: - # No need to redraw contacts if we're quitting - if accountIter: - model[accountIter][C_AVATAR_PIXBUF] = None - if gajim.con_types.has_key(account): - gajim.con_types[account] = None - for jid in gajim.contacts.get_jid_list(account): - lcontact = gajim.contacts.get_contacts(account, jid) - for contact in [c for c in lcontact if (c.show != 'offline' or \ - c.is_transport())]: - self.chg_contact_status(contact, 'offline', '', account) - self.actions_menu_needs_rebuild = True - self.update_status_combobox() - # Force the rebuild now since the on_activates on the menu itself does - # not work with the os/x top level menubar - if sys.platform == 'darwin': - self.make_menu(force = True) - - def new_private_chat(self, gc_contact, account, session = None): - contact = gajim.contacts.contact_from_gc_contact(gc_contact) - type_ = message_control.TYPE_PM - fjid = gc_contact.room_jid + '/' + gc_contact.name - mw = gajim.interface.msg_win_mgr.get_window(fjid, account) - if not mw: - mw = gajim.interface.msg_win_mgr.create_window(contact, account, type_) - - chat_control = PrivateChatControl(mw, gc_contact, contact, account, session) - mw.new_tab(chat_control) - if len(gajim.events.get_events(account, fjid)): - # We call this here to avoid race conditions with widget validation - chat_control.read_queue() - - def new_chat(self, contact, account, resource = None, session = None): - # Get target window, create a control, and associate it with the window - type_ = message_control.TYPE_CHAT - - fjid = contact.jid - if resource: - fjid += '/' + resource - - mw = gajim.interface.msg_win_mgr.get_window(fjid, account) - if not mw: - mw = gajim.interface.msg_win_mgr.create_window(contact, account, type_) - - chat_control = ChatControl(mw, contact, account, session, resource) - - mw.new_tab(chat_control) - - if len(gajim.events.get_events(account, fjid)): - # We call this here to avoid race conditions with widget validation - chat_control.read_queue() - - def new_chat_from_jid(self, account, fjid): - jid, resource = gajim.get_room_and_nick_from_fjid(fjid) - if resource: - contact = gajim.contacts.get_contact(account, jid, resource) - else: - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - added_to_roster = False - if not contact: - added_to_roster = True - contact = self.add_to_not_in_the_roster(account, jid, - resource = resource) - - if not gajim.interface.msg_win_mgr.has_window(fjid, account): - self.new_chat(contact, account, resource = resource) - mw = gajim.interface.msg_win_mgr.get_window(fjid, account) - mw.set_active_tab(fjid, account) - mw.window.present() - # For JEP-0172 - if added_to_roster: - mc = mw.get_control(fjid, account) - mc.user_nick = gajim.nicks[account] - - def new_room(self, room_jid, nick, account, is_continued=False): - # Get target window, create a control, and associate it with the window - contact = gajim.contacts.create_contact(jid = room_jid, name = nick) - mw = gajim.interface.msg_win_mgr.get_window(contact.jid, account) - if not mw: - mw = gajim.interface.msg_win_mgr.create_window(contact, account, - GroupchatControl.TYPE_ID) - gc_control = GroupchatControl(mw, contact, account, - is_continued=is_continued) - mw.new_tab(gc_control) - - def on_message(self, jid, msg, tim, account, encrypted=False, msg_type='', - subject=None, resource='', msg_id=None, user_nick='', - advanced_notif_num=None, xhtml=None, session=None, form_node=None): - '''when we receive a message''' - contact = None - # if chat window will be for specific resource - resource_for_chat = resource - fjid = jid - # Try to catch the contact with correct resource - if resource: - fjid = jid + '/' + resource - contact = gajim.contacts.get_contact(account, jid, resource) - highest_contact = gajim.contacts.get_contact_with_highest_priority( - account, jid) - if not contact: - # If there is another resource, it may be a message from an invisible - # resource - lcontact = gajim.contacts.get_contacts(account, jid) - if (len(lcontact) > 1 or (lcontact and lcontact[0].resource and \ - lcontact[0].show != 'offline')) and jid.find('@') > 0: - contact = gajim.contacts.copy_contact(highest_contact) - contact.resource = resource - if resource: - fjid = jid + '/' + resource - contact.priority = 0 - contact.show = 'offline' - contact.status = '' - gajim.contacts.add_contact(account, contact) - - else: - # Default to highest prio - fjid = jid - resource_for_chat = None - contact = highest_contact - if not contact: - # contact is not in roster - contact = self.add_to_not_in_the_roster(account, jid, user_nick) - - path = self.get_path(jid, account) # Try to get line of contact in roster - - # Look for a chat control that has the given resource - ctrl = gajim.interface.msg_win_mgr.get_control(fjid, account) - if not ctrl: - # if not, if message comes from highest prio, get control or open one - # without resource - if highest_contact and contact.resource == highest_contact.resource \ - and not jid == gajim.get_jid_from_account(account): - ctrl = gajim.interface.msg_win_mgr.get_control(jid, account) - fjid = jid - resource_for_chat = None - - # Do we have a queue? - no_queue = len(gajim.events.get_events(account, fjid)) == 0 - - popup = helpers.allow_popup_window(account, advanced_notif_num) - - if msg_type == 'normal' and popup: # it's single message to be autopopuped - dialogs.SingleMessageWindow(account, contact.jid, action='receive', - from_whom=jid, subject=subject, message=msg, resource=resource, - session=session, form_node=form_node) - return - - # We print if window is opened and it's not a single message - if ctrl and msg_type != 'normal': - typ = '' - if msg_type == 'error': - typ = 'status' - if session: - ctrl.set_session(session) - ctrl.print_conversation(msg, typ, tim = tim, encrypted = encrypted, - subject = subject, xhtml = xhtml) - if msg_id: - gajim.logger.set_read_messages([msg_id]) - return - - # We save it in a queue - type_ = 'chat' - event_type = 'message_received' - if msg_type == 'normal': - type_ = 'normal' - event_type = 'single_message_received' - show_in_roster = notify.get_show_in_roster(event_type, account, contact) - show_in_systray = notify.get_show_in_systray(event_type, account, contact) - event = gajim.events.create_event(type_, (msg, subject, msg_type, tim, - encrypted, resource, msg_id, xhtml, session, form_node), - show_in_roster=show_in_roster, show_in_systray=show_in_systray) - gajim.events.add_event(account, fjid, event) - if popup: - if not ctrl: - self.new_chat(contact, account, resource=resource_for_chat) - if path and not self.dragging and gajim.config.get( - 'scroll_roster_to_last_message'): - # we curently see contact in our roster OR he - # is not in the roster at all. - # show and select his line in roster - # do not change selection while DND'ing - self.tree.expand_row(path[0:1], False) - self.tree.expand_row(path[0:2], False) - self.tree.scroll_to_cell(path) - self.tree.set_cursor(path) - else: - if no_queue: # We didn't have a queue: we change icons - self.draw_contact(jid, account) - self.show_title() # we show the * or [n] - # Show contact in roster (if he is invisible for example) and select - # line - self.show_and_select_path(path, jid, account) - - def on_preferences_menuitem_activate(self, widget): - if gajim.interface.instances.has_key('preferences'): - gajim.interface.instances['preferences'].window.present() - else: - gajim.interface.instances['preferences'] = config.PreferencesWindow() - - def on_pep_services_menuitem_activate(self, widget, account): - if gajim.interface.instances[account].has_key('pep_services'): - gajim.interface.instances[account]['pep_services'].window.present() - else: - gajim.interface.instances[account]['pep_services'] = \ - config.ManagePEPServicesWindow(account) - - def on_add_new_contact(self, widget, account): - dialogs.AddNewContactWindow(account) - - def on_join_gc_activate(self, widget, account): - '''when the join gc menuitem is clicked, show the join gc window''' - invisible_show = gajim.SHOW_LIST.index('invisible') - if gajim.connections[account].connected == invisible_show: - dialogs.ErrorDialog(_('You cannot join a group chat while you are ' - 'invisible')) - return - if gajim.interface.instances[account].has_key('join_gc'): - gajim.interface.instances[account]['join_gc'].window.present() - else: - # c http://nkour.blogspot.com/2005/05/pythons-init-return-none-doesnt-return.html - try: - gajim.interface.instances[account]['join_gc'] = \ - dialogs.JoinGroupchatWindow(account) - except GajimGeneralException: - pass - - def on_new_chat_menuitem_activate(self, widget, account): - dialogs.NewChatDialog(account) - - def on_contents_menuitem_activate(self, widget): - helpers.launch_browser_mailer('url', 'http://trac.gajim.org/wiki') - - def on_faq_menuitem_activate(self, widget): - helpers.launch_browser_mailer('url', - 'http://trac.gajim.org/wiki/GajimFaq') - - def on_features_menuitem_activate(self, widget): - features_window.FeaturesWindow() - - def on_about_menuitem_activate(self, widget): - dialogs.AboutDialog() - - def on_accounts_menuitem_activate(self, widget): - if gajim.interface.instances.has_key('accounts'): - gajim.interface.instances['accounts'].window.present() - else: - gajim.interface.instances['accounts'] = config.AccountsWindow() - - def on_file_transfers_menuitem_activate(self, widget): - if gajim.interface.instances['file_transfers'].window.get_property( - 'visible'): - gajim.interface.instances['file_transfers'].window.present() - else: - gajim.interface.instances['file_transfers'].window.show_all() - - def on_history_menuitem_activate(self, widget): - if gajim.interface.instances.has_key('logs'): - gajim.interface.instances['logs'].window.present() - else: - gajim.interface.instances['logs'] = history_window.\ - HistoryWindow() - - def on_show_transports_menuitem_activate(self, widget): - gajim.config.set('show_transports_group', widget.get_active()) - self.draw_roster() - - def on_manage_bookmarks_menuitem_activate(self, widget): - config.ManageBookmarksWindow() - - def on_profile_avatar_menuitem_activate(self, widget, account): - gajim.interface.edit_own_details(account) - - def close_all_from_dict(self, dic): - '''close all the windows in the given dictionary''' - for w in dic.values(): - if type(w) == type({}): - self.close_all_from_dict(w) - else: - w.window.destroy() - - def close_all(self, account, force = False): - '''close all the windows from an account - if force is True, do not ask confirmation before closing chat/gc windows - ''' - if account in gajim.interface.instances: - self.close_all_from_dict(gajim.interface.instances[account]) - for ctrl in gajim.interface.msg_win_mgr.get_controls(acct = account): - ctrl.parent_win.remove_tab(ctrl, ctrl.parent_win.CLOSE_CLOSE_BUTTON, - force = force) - - def on_roster_window_delete_event(self, widget, event): - '''Main window X button was clicked''' - if gajim.interface.systray_enabled and not gajim.config.get( - 'quit_on_roster_x_button'): - self.tooltip.hide_tooltip() - self.window.hide() - else: - self.on_quit_request() - return True # do NOT destroy the window - - def on_roster_window_focus_in_event(self, widget, event): - # roster received focus, so if we had urgency REMOVE IT - # NOTE: we do not have to read the message to remove urgency - # so this functions does that - gtkgui_helpers.set_unset_urgency_hint(widget, False) - - # if a contact row is selected, update colors (eg. for status msg) - # because gtk engines may differ in bg when window is selected - # or not - if len(self._last_selected_contact): - for (jid, account) in self._last_selected_contact: - self.draw_contact(jid, account, selected = True, - focus = True) - - def on_roster_window_focus_out_event(self, widget, event): - # if a contact row is selected, update colors (eg. for status msg) - # because gtk engines may differ in bg when window is selected - # or not - if len(self._last_selected_contact): - for (jid, account) in self._last_selected_contact: - self.draw_contact(jid, account, selected = True, - focus = False) - - def on_roster_window_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - if gajim.interface.msg_win_mgr.mode == \ - MessageWindowMgr.ONE_MSG_WINDOW_ALWAYS_WITH_ROSTER and \ - gajim.interface.msg_win_mgr.one_window_opened(): - # let message window close the tab - return - model, list_of_paths = self.tree.get_selection().get_selected_rows() - if not len(list_of_paths) and gajim.interface.systray_enabled and \ - not gajim.config.get('quit_on_roster_x_button'): - self.tooltip.hide_tooltip() - self.window.hide() - - def on_roster_window_popup_menu(self, widget): - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) - self.show_treeview_menu(event) - - def quit_gtkgui_interface(self): - '''When we quit the gtk interface : - tell that to the core and exit gtk''' - msgwin_width_adjust = 0 - - # in case show_roster_on_start is False and roster is never shown - # window.window is None - if self.window.window is not None: - x, y = self.window.window.get_root_origin() - gajim.config.set('roster_x-position', x) - gajim.config.set('roster_y-position', y) - width, height = self.window.get_size() - # For the width use the size of the vbox containing the tree and - # status combo, this will cancel out any hpaned width - width = self.xml.get_widget('roster_vbox2').allocation.width - gajim.config.set('roster_width', width) - gajim.config.set('roster_height', height) - if not self.xml.get_widget('roster_vbox2').get_property('visible'): - # The roster vbox is hidden, so the message window is larger - # then we want to save (i.e. the window will grow every startup) - # so adjust. - msgwin_width_adjust = -1 * width - - - gajim.config.set('show_roster_on_startup', - self.window.get_property('visible')) - gajim.interface.msg_win_mgr.shutdown(msgwin_width_adjust) - - gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows)) - gajim.interface.save_config() - for account in gajim.connections: - gajim.connections[account].quit(True) - self.close_all(account) - if gajim.interface.systray_enabled: - gajim.interface.hide_systray() - gtk.main_quit() - - def on_quit_request(self, widget = None): - ''' user want to quit. Check if he should be warned about messages - pending. Send offline to all connected account. We do NOT really quit - gajim here ''' - accounts = gajim.connections.keys() - get_msg = False - for acct in accounts: - if gajim.connections[acct].connected: - get_msg = True - break - if get_msg: - message = self.get_status_message('offline') - if message is None: - # user pressed Cancel to change status message dialog - return - - # check if we have unread messages - unread = gajim.events.get_nb_events() - if not gajim.config.get('notify_on_all_muc_messages'): - unread_not_to_notify = gajim.events.get_nb_events(['printed_gc_msg']) - unread -= unread_not_to_notify - - # check if we have recent messages - recent = False - for win in gajim.interface.msg_win_mgr.windows(): - for ctrl in win.controls(): - fjid = ctrl.get_full_jid() - if gajim.last_message_time[ctrl.account].has_key(fjid): - if time.time() - gajim.last_message_time[ctrl.account][fjid] < 2: - recent = True - break - if recent: - break - - if unread or recent: - dialog = dialogs.ConfirmationDialog(_('You have unread messages'), - _('Messages will only be available for reading them later if you' - ' have history enabled and contact is in your roster.')) - if dialog.get_response() != gtk.RESPONSE_OK: - return - - self.quit_on_next_offline = 0 - for acct in accounts: - if gajim.connections[acct].connected: - self.quit_on_next_offline += 1 - self.send_status(acct, 'offline', message) - - if not self.quit_on_next_offline: - self.quit_gtkgui_interface() - - def open_event(self, account, jid, event): - '''If an event was handled, return True, else return False''' - data = event.parameters - ft = gajim.interface.instances['file_transfers'] - if event.type_ == 'normal': - dialogs.SingleMessageWindow(account, jid, - action='receive', from_whom=jid, subject=data[1], message=data[0], - resource=data[5], session=data[8], form_node=data[9]) - gajim.interface.remove_first_event(account, jid, event.type_) - return True - elif event.type_ == 'file-request': - contact = gajim.contacts.get_contact_with_highest_priority(account, - jid) - gajim.interface.remove_first_event(account, jid, event.type_) - ft.show_file_request(account, contact, data) - return True - elif event.type_ in ('file-request-error', 'file-send-error'): - gajim.interface.remove_first_event(account, jid, event.type_) - ft.show_send_error(data) - return True - elif event.type_ in ('file-error', 'file-stopped'): - gajim.interface.remove_first_event(account, jid, event.type_) - ft.show_stopped(jid, data) - return True - elif event.type_ == 'file-completed': - gajim.interface.remove_first_event(account, jid, event.type_) - ft.show_completed(jid, data) - return True - elif event.type_ == 'gc-invitation': - dialogs.InvitationReceivedDialog(account, data[0], jid, data[2], - data[1]) - gajim.interface.remove_first_event(account, jid, event.type_) - return True - return False - - def on_execute_command(self, widget, contact, account, resource=None): - '''Execute command. Full JID needed; if it is other contact, - resource is necessary. Widget is unnecessary, only to be - able to make this a callback.''' - jid = contact.jid - if resource is not None: - jid = jid + u'/' + resource - adhoc_commands.CommandWindow(account, jid) - - def on_open_chat_window(self, widget, contact, account, resource = None, session = None): - # Get the window containing the chat - fjid = contact.jid - if resource: - fjid += '/' + resource - win = gajim.interface.msg_win_mgr.get_window(fjid, account) - if not win: - self.new_chat(contact, account, resource = resource, session = session) - win = gajim.interface.msg_win_mgr.get_window(fjid, account) - ctrl = win.get_control(fjid, account) - # last message is long time ago - gajim.last_message_time[account][ctrl.get_full_jid()] = 0 - win.set_active_tab(fjid, account) - if gajim.connections[account].is_zeroconf and \ - gajim.connections[account].status in ('offline', 'invisible'): - win.get_control(fjid, account).got_disconnected() - - win.window.present() - - def on_row_activated(self, widget, path): - '''When an iter is activated (dubblick or single click if gnome is set - this way''' - model = self.tree.get_model() - account = model[path][C_ACCOUNT].decode('utf-8') - type_ = model[path][C_TYPE] - jid = model[path][C_JID].decode('utf-8') - resource = None - contact = None - iter = model.get_iter(path) - if type_ in ('group', 'account'): - if self.tree.row_expanded(path): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - elif jid in gajim.interface.minimized_controls[account]: - self.on_groupchat_maximized(None, jid, account) - else: - first_ev = gajim.events.get_first_event(account, jid) - if not first_ev: - # look in other resources - for c in gajim.contacts.get_contacts(account, jid): - fjid = c.get_full_jid() - first_ev = gajim.events.get_first_event(account, fjid) - if first_ev: - resource = c.resource - break - if not first_ev and model.iter_has_child(iter): - child_iter = model.iter_children(iter) - while not first_ev and child_iter: - child_jid = model[child_iter][C_JID].decode('utf-8') - first_ev = gajim.events.get_first_event(account, child_jid) - if first_ev: - jid = child_jid - else: - child_iter = model.iter_next(child_iter) - session = None - if first_ev: - if first_ev.type_ in ('chat', 'normal'): - session = first_ev.parameters[8] - fjid = jid - if resource: - fjid += '/' + resource - if self.open_event(account, fjid, first_ev): - return - # else - contact = gajim.contacts.get_contact(account, jid, resource) - if not contact or isinstance(contact, list): - contact = \ - gajim.contacts.get_contact_with_highest_priority(account, jid) - if jid == gajim.get_jid_from_account(account): - resource = contact.resource - self.on_open_chat_window(widget, contact, account, \ - resource = resource, session = session) - - def on_roster_treeview_row_activated(self, widget, path, col = 0): - '''When an iter is double clicked: open the first event window''' - if not gajim.single_click: - self.on_row_activated(widget, path) - - def on_roster_treeview_row_expanded(self, widget, iter, path): - '''When a row is expanded change the icon of the arrow''' - model = self.tree.get_model() - if self.regroup: # merged accounts - accounts = gajim.connections.keys() - else: - accounts = [model[iter][C_ACCOUNT].decode('utf-8')] - type_ = model[iter][C_TYPE] - if type_ == 'group': - model.set_value(iter, 0, gajim.interface.jabber_state_images['16'][ - 'opened']) - jid = model[iter][C_JID].decode('utf-8') - for account in accounts: - if gajim.groups[account].has_key(jid): # This account has this group - gajim.groups[account][jid]['expand'] = True - if account + jid in self.collapsed_rows: - self.collapsed_rows.remove(account + jid) - elif type_ == 'account': - account = accounts[0] # There is only one cause we don't use merge - if account in self.collapsed_rows: - self.collapsed_rows.remove(account) - for g in gajim.groups[account]: - groupIter = self.get_group_iter(g, account) - if groupIter and gajim.groups[account][g]['expand']: - pathG = model.get_path(groupIter) - self.tree.expand_row(pathG, False) - self.draw_account(account) - elif type_ == 'contact': - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - self.draw_contact(jid, account) - - def on_roster_treeview_row_collapsed(self, widget, iter, path): - '''When a row is collapsed : - change the icon of the arrow''' - model = self.tree.get_model() - if self.regroup: # merged accounts - accounts = gajim.connections.keys() - else: - accounts = [model[iter][C_ACCOUNT].decode('utf-8')] - type_ = model[iter][C_TYPE] - if type_ == 'group': - model.set_value(iter, 0, gajim.interface.jabber_state_images['16'][ - 'closed']) - jid = model[iter][C_JID].decode('utf-8') - for account in accounts: - if gajim.groups[account].has_key(jid): # This account has this group - gajim.groups[account][jid]['expand'] = False - if not account + jid in self.collapsed_rows: - self.collapsed_rows.append(account + jid) - elif type_ == 'account': - account = accounts[0] # There is only one cause we don't use merge - if not account in self.collapsed_rows: - self.collapsed_rows.append(account) - self.draw_account(account) - elif type_ == 'contact': - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - self.draw_contact(jid, account) - - def on_service_disco_menuitem_activate(self, widget, account): - server_jid = gajim.config.get_per('accounts', account, 'hostname') - if gajim.interface.instances[account]['disco'].has_key(server_jid): - gajim.interface.instances[account]['disco'][server_jid].\ - window.present() - else: - try: - # Object will add itself to the window dict - disco.ServiceDiscoveryWindow(account, address_entry = True) - except GajimGeneralException: - pass - - def make_transport_state_images(self, transport): - '''initialise opened and closed 'transport' iconset dict''' - if gajim.config.get('use_transports_iconsets'): - folder = os.path.join(helpers.get_transport_path(transport), - '16x16') - pixo, pixc = gtkgui_helpers.load_icons_meta() - self.transports_state_images['opened'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixo, transport = True) - self.transports_state_images['closed'][transport] = \ - gtkgui_helpers.load_iconset(folder, pixc, transport = True) - folder = os.path.join(helpers.get_transport_path(transport), '32x32') - self.transports_state_images['32'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport = True) - folder = os.path.join(helpers.get_transport_path(transport), '16x16') - self.transports_state_images['16'][transport] = \ - gtkgui_helpers.load_iconset(folder, transport = True) - - def update_jabber_state_images(self): - # Update the roster - self.draw_roster() - # Update the status combobox - model = self.status_combobox.get_model() - iter = model.get_iter_root() - while iter: - if model[iter][2] != '': - # If it's not change status message iter - # eg. if it has show parameter not '' - model[iter][1] = gajim.interface.jabber_state_images['16'][model[ - iter][2]] - iter = model.iter_next(iter) - # Update the systray - if gajim.interface.systray_enabled: - gajim.interface.systray.set_img() - - for win in gajim.interface.msg_win_mgr.windows(): - for ctrl in win.controls(): - ctrl.update_ui() - win.redraw_tab(ctrl) - - self.update_status_combobox() - - def repaint_themed_widgets(self): - '''Notify windows that contain themed widgets to repaint them''' - for win in gajim.interface.msg_win_mgr.windows(): - win.repaint_themed_widgets() - for account in gajim.connections: - for addr in gajim.interface.instances[account]['disco']: - gajim.interface.instances[account]['disco'][addr].paint_banner() - for ctrl in gajim.interface.minimized_controls[account].values(): - ctrl.repaint_themed_widgets() - - def on_show_offline_contacts_menuitem_activate(self, widget): - '''when show offline option is changed: - redraw the treeview''' - gajim.config.set('showoffline', not gajim.config.get('showoffline')) - self.draw_roster() - - def on_view_menu_activate(self, widget): - # Hide the show roster menu if we are not in the right windowing mode. - if self.hpaned.get_child2() is not None: - self.xml.get_widget('show_roster_menuitem').show() - else: - self.xml.get_widget('show_roster_menuitem').hide() - - def on_show_roster_menuitem_toggled(self, widget): - # when num controls is 0 this menuitem is hidden, but still need to - # disable keybinding - if self.hpaned.get_child2() is not None: - self.show_roster_vbox(widget.get_active()) - - def show_roster_vbox(self, active): - if active: - self.xml.get_widget('roster_vbox2').show() - else: - self.xml.get_widget('roster_vbox2').hide() - - def set_renderer_color(self, renderer, style, set_background = True): - '''set style for treeview cell, using PRELIGHT system color''' - if set_background: - bgcolor = self.tree.style.bg[style] - renderer.set_property('cell-background-gdk', bgcolor) - else: - fgcolor = self.tree.style.fg[style] - renderer.set_property('foreground-gdk', fgcolor) - - def iconCellDataFunc(self, column, renderer, model, iter, data = None): - '''When a row is added, set properties for icon renderer''' - theme = gajim.config.get('roster_theme') - type_ = model[iter][C_TYPE] - if type_ == 'account': - color = gajim.config.get_per('themes', theme, 'accountbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE) - renderer.set_property('xalign', 0) - elif type_ == 'group': - color = gajim.config.get_per('themes', theme, 'groupbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) - renderer.set_property('xalign', 0.2) - elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[iter][C_JID] or not model[iter][C_ACCOUNT]: - # This can append when at the moment we add the row - return - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - if jid in gajim.newly_added[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_connected_bg_color')) - elif jid in gajim.to_be_removed[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) - else: - color = gajim.config.get_per('themes', theme, 'contactbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - renderer.set_property('cell-background', None) - parent_iter = model.iter_parent(iter) - if model[parent_iter][C_TYPE] == 'contact': - renderer.set_property('xalign', 1) - else: - renderer.set_property('xalign', 0.4) - renderer.set_property('width', 26) - - def nameCellDataFunc(self, column, renderer, model, iter, data = None): - '''When a row is added, set properties for name renderer''' - theme = gajim.config.get('roster_theme') - type_ = model[iter][C_TYPE] - if type_ == 'account': - color = gajim.config.get_per('themes', theme, 'accounttextcolor') - if color: - renderer.set_property('foreground', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE, False) - color = gajim.config.get_per('themes', theme, 'accountbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'accountfont')) - renderer.set_property('xpad', 0) - renderer.set_property('width', 3) - elif type_ == 'group': - color = gajim.config.get_per('themes', theme, 'grouptextcolor') - if color: - renderer.set_property('foreground', color) - else: - self.set_renderer_color(renderer, gtk.STATE_PRELIGHT, False) - color = gajim.config.get_per('themes', theme, 'groupbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_PRELIGHT) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) - renderer.set_property('xpad', 4) - elif type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[iter][C_JID] or not model[iter][C_ACCOUNT]: - # This can append when at the moment we add the row - return - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - color = gajim.config.get_per('themes', theme, 'contacttextcolor') - if color: - renderer.set_property('foreground', color) - else: - renderer.set_property('foreground', None) - if jid in gajim.newly_added[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_connected_bg_color')) - elif jid in gajim.to_be_removed[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) - else: - color = gajim.config.get_per('themes', theme, 'contactbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - renderer.set_property('cell-background', None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) - parent_iter = model.iter_parent(iter) - if model[parent_iter][C_TYPE] == 'contact': - renderer.set_property('xpad', 16) - else: - renderer.set_property('xpad', 8) - - def fill_avatar_pixbuf_rederer(self, column, renderer, model, iter, - data = None): - '''When a row is added, set properties for avatar renderer''' - theme = gajim.config.get('roster_theme') - type_ = model[iter][C_TYPE] - if type_ in ('group', 'account'): - renderer.set_property('visible', False) - return - - # allocate space for the icon only if needed - if model[iter][C_AVATAR_PIXBUF] or \ - gajim.config.get('avatar_position_in_roster') == 'left': - renderer.set_property('visible', True) - else: - renderer.set_property('visible', False) - if type_: # prevent type_ = None, see http://trac.gajim.org/ticket/2534 - if not model[iter][C_JID] or not model[iter][C_ACCOUNT]: - # This can append at the moment we add the row - return - jid = model[iter][C_JID].decode('utf-8') - account = model[iter][C_ACCOUNT].decode('utf-8') - if jid in gajim.newly_added[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_connected_bg_color')) - elif jid in gajim.to_be_removed[account]: - renderer.set_property('cell-background', gajim.config.get( - 'just_disconnected_bg_color')) - else: - color = gajim.config.get_per('themes', theme, 'contactbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - renderer.set_property('cell-background', None) - if gajim.config.get('avatar_position_in_roster') == 'left': - renderer.set_property('width', gajim.config.get('roster_avatar_width')) - renderer.set_property('xalign', 0.5) - else: - renderer.set_property('xalign', 1) # align pixbuf to the right - - def fill_padlock_pixbuf_rederer(self, column, renderer, model, iter, - data = None): - '''When a row is added, set properties for padlock renderer''' - theme = gajim.config.get('roster_theme') - type_ = model[iter][C_TYPE] - # allocate space for the icon only if needed - if type_ == 'account' and model[iter][C_PADLOCK_PIXBUF]: - renderer.set_property('visible', True) - color = gajim.config.get_per('themes', theme, 'accountbgcolor') - if color: - renderer.set_property('cell-background', color) - else: - self.set_renderer_color(renderer, gtk.STATE_ACTIVE) - renderer.set_property('xalign', 1) # align pixbuf to the right - else: - renderer.set_property('visible', False) - - def get_show(self, lcontact): - prio = lcontact[0].priority - show = lcontact[0].show - for u in lcontact: - if u.priority > prio: - prio = u.priority - show = u.show - return show - - def compareIters(self, model, iter1, iter2, data = None): - '''Compare two iters to sort them''' - name1 = model[iter1][C_NAME] - name2 = model[iter2][C_NAME] - if not name1 or not name2: - return 0 - name1 = name1.decode('utf-8') - name2 = name2.decode('utf-8') - type1 = model[iter1][C_TYPE] - type2 = model[iter2][C_TYPE] - if type1 == 'self_contact': - return -1 - if type2 == 'self_contact': - return 1 - if type1 == 'group': - name1 = model[iter1][C_JID] - name2 = model[iter2][C_JID] - if name1 == _('Transports'): - return 1 - if name2 == _('Transports'): - return -1 - if name1 == _('Not in Roster'): - return 1 - if name2 == _('Not in Roster'): - return -1 - if name1 == _('Groupchats'): - return 1 - if name2 == _('Groupchats'): - return -1 - account1 = model[iter1][C_ACCOUNT] - account2 = model[iter2][C_ACCOUNT] - if not account1 or not account2: - return 0 - account1 = account1.decode('utf-8') - account2 = account2.decode('utf-8') - if type1 == 'account': - if account1 < account2: - return -1 - return 1 - jid1 = model[iter1][C_JID].decode('utf-8') - jid2 = model[iter2][C_JID].decode('utf-8') - if type1 == 'contact': - lcontact1 = gajim.contacts.get_contacts(account1, jid1) - contact1 = gajim.contacts.get_first_contact_from_jid(account1, jid1) - if not contact1: - return 0 - name1 = contact1.get_shown_name() - if type2 == 'contact': - lcontact2 = gajim.contacts.get_contacts(account2, jid2) - contact2 = gajim.contacts.get_first_contact_from_jid(account2, jid2) - if not contact2: - return 0 - name2 = contact2.get_shown_name() - # We first compare by show if sort_by_show is True or if it's a child - # contact - if type1 == 'contact' and type2 == 'contact' and \ - gajim.config.get('sort_by_show'): - cshow = {'online':0, 'chat': 1, 'away': 2, 'xa': 3, 'dnd': 4, - 'invisible': 5, 'offline': 6, 'not in roster': 7, 'error': 8} - s = self.get_show(lcontact1) - if s in cshow: - show1 = cshow[s] - else: - show1 = 9 - s = self.get_show(lcontact2) - if s in cshow: - show2 = cshow[s] - else: - show2 = 9 - removing1 = False - removing2 = False - if show1 == 6 and jid1 in gajim.to_be_removed[account1]: - removing1 = True - if show2 == 6 and jid2 in gajim.to_be_removed[account2]: - removing2 = True - if removing1 and not removing2: - return -1 - if removing2 and not removing1: - return 1 - if show1 < show2: - return -1 - elif show1 > show2: - return 1 - # We compare names - if name1.lower() < name2.lower(): - return -1 - if name2.lower() < name1.lower(): - return 1 - if type1 == 'contact' and type2 == 'contact': - # We compare account names - if account1.lower() < account2.lower(): - return -1 - if account2.lower() < account1.lower(): - return 1 - # We compare jids - if jid1.lower() < jid2.lower(): - return -1 - if jid2.lower() < jid1.lower(): - return 1 - return 0 - - def drag_data_get_data(self, treeview, context, selection, target_id, etime): - model, list_of_paths = self.tree.get_selection().get_selected_rows() - if len(list_of_paths) != 1: - return - path = list_of_paths[0] - data = '' - if len(path) >= 3: - data = model[path][C_JID] - selection.set(selection.target, 8, data) - - def drag_begin(self, treeview, context): - self.dragging = True - - def drag_end(self, treeview, context): - self.dragging = False - - def on_drop_in_contact(self, widget, account_source, c_source, account_dest, - c_dest, was_big_brother, context, etime): - if not gajim.connections[account_source].private_storage_supported or not\ - gajim.connections[account_dest].private_storage_supported: - dialogs.WarningDialog(_('Metacontacts storage not supported by your ' - 'server'), - _('Your server does not support storing metacontacts information. ' - 'So those information will not be saved on next reconnection.')) - def merge_contacts(is_checked=None): - if is_checked != None: # dialog has been shown - if is_checked: # user does not want to be asked again - gajim.config.set('confirm_metacontacts', 'no') - else: - gajim.config.set('confirm_metacontacts', 'yes') - # children must take the new tag too, so remember old tag - old_tag = gajim.contacts.get_metacontacts_tag(account_source, - c_source.jid) - # remove the source row - self.remove_contact(c_source, account_source) - # brother inherite big brother groups - old_groups = c_source.groups - c_source.groups = [] - for g in c_dest.groups: - c_source.groups.append(g) - gajim.connections[account_source].update_contact(c_source.jid, - c_source.name, c_source.groups) - gajim.contacts.add_metacontact(account_dest, c_dest.jid, - account_source, c_source.jid) - if was_big_brother: - # add brothers too - all_jid = gajim.contacts.get_metacontacts_jids(old_tag) - for _account in all_jid: - for _jid in all_jid[_account]: - gajim.contacts.add_metacontact(account_dest, c_dest.jid, - _account, _jid) - _c = gajim.contacts.get_first_contact_from_jid(_account, _jid) - self.remove_contact(_c, _account) - self.add_contact_to_roster(_jid, _account) - self.draw_contact(_jid, _account) - self.add_contact_to_roster(c_source.jid, account_source) - self.draw_contact(c_dest.jid, account_dest) - # FIXME: Why do groups have to be redrawn by hand? - for g in old_groups: - self.draw_group(g, account_source) - self.draw_account(account_source) - context.finish(True, True, etime) - - confirm_metacontacts = gajim.config.get('confirm_metacontacts') - if confirm_metacontacts == 'no': - merge_contacts() - return - pritext = _('You are about to create a metacontact. Are you sure you want' - ' to continue?') - sectext = _('Metacontacts are a way to regroup several contacts in one ' - 'line. Generally it is used when the same person has several Jabber ' - 'accounts or transport accounts.') - dlg = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again'), on_response_ok = merge_contacts) - if not confirm_metacontacts: # First time we see this window - dlg.checkbutton.set_active(True) - - def on_drop_in_group(self, widget, account, c_source, grp_dest, is_big_brother, - context, etime, grp_source = None): - if grp_source: - self.remove_contact_from_group(account, c_source, grp_source) - if not is_big_brother: - # remove tag before readding - gajim.contacts.remove_metacontact(account, c_source.jid) - self.add_contact_to_group(account, c_source, grp_dest) - if is_big_brother: - # add whole metacontact to new group - tag = gajim.contacts.get_metacontacts_tag(account, c_source.jid) - all_jid = gajim.contacts.get_metacontacts_jids(tag) - for _account in all_jid: - for _jid in all_jid[_account]: - _c = gajim.contacts.get_first_contact_from_jid(_account, _jid) - if grp_source: - self.remove_contact_from_group(_account, _c, grp_source) - self.add_contact_to_group(_account, _c, grp_dest) - if context.action in (gtk.gdk.ACTION_MOVE, gtk.gdk.ACTION_COPY): - context.finish(True, True, etime) - - def add_contact_to_group(self, account, contact, group): - model = self.tree.get_model() - if not group in contact.groups: - contact.groups.append(group) - # Remove all rows because add_contact_to_roster doesn't add it if one - # is already in roster - for i in self.get_contact_iter(contact.jid, account): - model.remove(i) - self.add_contact_to_roster(contact.jid, account) - gajim.connections[account].update_contact(contact.jid, contact.name, - contact.groups) - - def remove_contact_from_group(self, account, contact, group): - # Make sure contact was in the group - if group in contact.groups: - contact.groups.remove(group) - self.remove_contact(contact, account) - - def drag_data_received_data(self, treeview, context, x, y, selection, info, - etime): - drop_info = treeview.get_dest_row_at_pos(x, y) - if not drop_info: - return - if not selection.data: - return # prevents tb when several entrys are dragged - model = treeview.get_model() - data = selection.data - path_dest, position = drop_info - - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 \ - and path_dest[1] == 0: # dropped before the first group - return - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: - # dropped before a group: we drop it in the previous group every time - path_dest = (path_dest[0], path_dest[1]-1) - # destination: the row something got dropped on - iter_dest = model.get_iter(path_dest) - type_dest = model[iter_dest][C_TYPE].decode('utf-8') - jid_dest = model[iter_dest][C_JID].decode('utf-8') - account_dest = model[iter_dest][C_ACCOUNT].decode('utf-8') - - # drop on account row in merged mode, we cannot know the desired account - if account_dest == 'all': - return - # nothing can be done, if destination account is offline - if gajim.connections[account_dest].connected < 2: - return - - # A file got dropped on the roster - if info == self.TARGET_TYPE_URI_LIST: - if len(path_dest) < 3: - return - if type_dest != 'contact': - return - c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, - jid_dest) - uri = data.strip() - uri_splitted = uri.split() # we may have more than one file dropped - nb_uri = len(uri_splitted) - # Check the URIs - bad_uris = [] - for a_uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(a_uri) - if not os.path.isfile(path): - bad_uris.append(a_uri) - if len(bad_uris): - dialogs.ErrorDialog(_('Invalid file URI:'), '\n'.join(bad_uris)) - return - def _on_send_files(account, jid, uris): - c = gajim.contacts.get_contact_with_highest_priority(account, jid) - for uri in uris: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - if os.path.isfile(path): # is it file? - gajim.interface.instances['file_transfers'].send_file( - account, c, path) - # Popup dialog to confirm sending - prim_text = 'Send file?' - sec_text = i18n.ngettext('Do you want to send this file to %s:', - 'Do you want to send those files to %s:', nb_uri) %\ - c_dest.get_shown_name() - for uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - sec_text += '\n' + os.path.basename(path) - dialog = dialogs.NonModalConfirmationDialog(prim_text, sec_text, - on_response_ok = (_on_send_files, account_dest, jid_dest, - uri_splitted)) - dialog.popup() - return - - # a roster entry was dragged and dropped somewhere in the roster - - # source: the row that was dragged - path_source = treeview.get_selection().get_selected_rows()[1][0] - iter_source = model.get_iter(path_source) - 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 - - # 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 - while model[it][C_TYPE] == 'contact': - it = model.iter_parent(it) - grp_source = model[it][C_JID].decode('utf-8') - if grp_source in helpers.special_groups and \ - grp_source not in ('Not in Roster', 'Observers'): - # a transport or a minimized groupchat was dragged - # we can add it to other accounts but not move it to another group, see below - return - jid_source = data.decode('utf-8') - c_source = gajim.contacts.get_contact_with_highest_priority( - account_source, jid_source) - - # Get destination group - grp_dest = None - if type_dest == 'group': - grp_dest = model[iter_dest][C_JID].decode('utf-8') - elif type_dest in ('contact', 'agent'): - it = iter_dest - while model[it][C_TYPE] != 'group': - it = model.iter_parent(it) - grp_dest = model[it][C_JID].decode('utf-8') - if grp_dest in helpers.special_groups: - return - - if jid_source == jid_dest: - if grp_source == grp_dest and account_source == account_dest: - # Drop on self - return - - # contact drop somewhere in or on a foreign account - if (type_dest == 'account' or not self.regroup) and \ - account_source != account_dest: - # add to account in specified group - dialogs.AddNewContactWindow(account = account_dest, jid = jid_source, - user_nick = c_source.name, group = grp_dest) - return - - # we may not add contacts from special_groups - if grp_source in helpers.special_groups : - return - - # Is the contact we drag a meta contact? - is_big_brother = gajim.contacts.is_big_brother(account_source, jid_source) - - # Contact drop on group row or between two contacts - if type_dest == 'group' or position == gtk.TREE_VIEW_DROP_BEFORE or \ - position == gtk.TREE_VIEW_DROP_AFTER: - self.on_drop_in_group(None, account_source, c_source, grp_dest, - is_big_brother, context, etime, grp_source) - return - - # Contact drop on another contact, make meta contacts - if position == gtk.TREE_VIEW_DROP_INTO_OR_AFTER or \ - position == gtk.TREE_VIEW_DROP_INTO_OR_BEFORE: - c_dest = gajim.contacts.get_contact_with_highest_priority(account_dest, - jid_dest) - if not c_dest: - # c_dest is None if jid_dest doesn't belong to account - return - self.on_drop_in_contact(treeview, account_source, c_source, - account_dest, c_dest, is_big_brother, context, etime) - return - - def show_title(self): - change_title_allowed = gajim.config.get('change_roster_title') - if not change_title_allowed: - return - - if gajim.config.get('one_message_window') == 'always_with_roster': - # always_with_roster mode defers to the MessageWindow - if not gajim.interface.msg_win_mgr.one_window_opened(): - # No MessageWindow to defer to - self.window.set_title('Gajim') - return - - nb_unread = 0 - start = '' - for account in gajim.connections: - # Count events in roster title only if we don't auto open them - if not helpers.allow_popup_window(account): - nb_unread += gajim.events.get_nb_events(['chat', 'normal', - 'file-request', 'file-error', 'file-completed', - 'file-request-error', 'file-send-error', 'file-stopped', - 'printed_chat'], account) - if nb_unread > 1: - start = '[' + str(nb_unread) + '] ' - elif nb_unread == 1: - start = '* ' - self.window.set_title(start + 'Gajim') - - gtkgui_helpers.set_unset_urgency_hint(self.window, nb_unread) - - def iter_is_separator(self, model, iter): - if model[iter][0] == 'SEPARATOR': - return True - return False - - def iter_contact_rows(self): - '''iterate over all contact rows in the tree model''' - model = self.tree.get_model() - account_iter = model.get_iter_root() - while account_iter: - group_iter = model.iter_children(account_iter) - while group_iter: - contact_iter = model.iter_children(group_iter) - while contact_iter: - yield model[contact_iter] - contact_iter = model.iter_next(contact_iter) - group_iter = model.iter_next(group_iter) - account_iter = model.iter_next(account_iter) - - def on_roster_treeview_style_set(self, treeview, style): - '''When style (theme) changes, redraw all contacts''' - for contact in self.iter_contact_rows(): - self.draw_contact(contact[C_JID].decode('utf-8'), - contact[C_ACCOUNT].decode('utf-8')) - - def _on_treeview_selection_changed(self, selection): - model, list_of_paths = selection.get_selected_rows() - if len(self._last_selected_contact): - # update unselected rows - for (jid, account) in self._last_selected_contact: - try: - self.draw_contact(jid, account) - except: - # This can fail when last selected row was on an account we just - # removed. So we don't care if that fail - pass - self._last_selected_contact = [] - if len(list_of_paths) == 0: - return - for path in list_of_paths: - row = model[path] - if row[C_TYPE] != 'contact': - self._last_selected_contact = [] - return - jid = row[C_JID].decode('utf-8') - account = row[C_ACCOUNT].decode('utf-8') - self._last_selected_contact.append((jid, account)) - self.draw_contact(jid, account, selected = True) - - def search_roster_func(self, model, column, key, iter): - if model[iter][C_NAME].decode('utf-8').lower().startswith( - gobject.markup_escape_text(key.lower())): - return False - return True - + def setup_for_osx(self): - # Massage the GTK menu so it will match up to the OS/X nib style menu - # when passed to sync-menu and merged + '''Massage the GTK menu so it will match up to the OS/X nib style menu + when passed to sync-menu and merged''' main_menu = self.xml.get_widget('menubar') app_item = gtk.MenuItem('Gajim') main_menu.insert(app_item, 0) @@ -5261,29 +5759,28 @@ class RosterWindow: # Hide the GTK menubar itself and let the OS/X menubar do its thing #self.xml.get_widget('menubar').hide() return - - def _on_message_window_delete(self, win_mgr, msg_win): - if gajim.config.get('one_message_window') == 'always_with_roster': - self.show_roster_vbox(True) - gtkgui_helpers.resize_window(self.window, - gajim.config.get('roster_width'), - gajim.config.get('roster_height')) - + +################################################################################ +### +################################################################################ + def __init__(self): + self.filtering = False self.xml = gtkgui_helpers.get_glade('roster_window.glade') self.window = self.xml.get_widget('roster_window') self.hpaned = self.xml.get_widget('roster_hpaned') self._music_track_changed_signal = None gajim.interface.msg_win_mgr = MessageWindowMgr(self.window, self.hpaned) - gajim.interface.msg_win_mgr.connect('window-delete', self._on_message_window_delete) + gajim.interface.msg_win_mgr.connect('window-delete', self.on_message_window_delete) self.advanced_menus = [] # We keep them to destroy them if gajim.config.get('roster_window_skip_taskbar'): self.window.set_property('skip-taskbar-hint', True) self.tree = self.xml.get_widget('roster_treeview') sel = self.tree.get_selection() sel.set_mode(gtk.SELECTION_MULTIPLE) - sel.connect('changed', - self._on_treeview_selection_changed) + #FIXME: talk to asterix, why is this needed? + #sel.connect('changed', + # self.on_treeview_selection_changed) self._last_selected_contact = [] # holds a list of (jid, account) tupples self.transports_state_images = {'16': {}, '32': {}, 'opened': {}, @@ -5317,12 +5814,20 @@ class RosterWindow: self.popup_notification_windows = [] #(icon, name, type, jid, account, editable, avatar_pixbuf, padlock_pixbuf) - model = gtk.TreeStore(gtk.Image, str, str, str, str, gtk.gdk.Pixbuf, + self.model = gtk.TreeStore(gtk.Image, str, str, str, str, gtk.gdk.Pixbuf, gtk.gdk.Pixbuf) - model.set_sort_func(1, self.compareIters) - model.set_sort_column_id(1, gtk.SORT_ASCENDING) - self.tree.set_model(model) + self.model.set_sort_func(1, self._compareIters) + self.model.set_sort_column_id(1, gtk.SORT_ASCENDING) + self.modelfilter = self.model.filter_new() + self.modelfilter.set_visible_func(self._visible_func) + + + self.modelfilter.connect('row-has-child-toggled', self.on_model_row_has_child_toggled) + # Workaroung: For strange reasons signal is behaving like row-changed + self._toggeling_row = False + + self.tree.set_model(self.modelfilter) # when this value become 0 we quit main application. If it's more than 0 # it means we are waiting for this number of accounts to disconnect before @@ -5351,13 +5856,13 @@ class RosterWindow: # if it will be sensitive or not it is in the fourth column self.status_combobox.add_attribute(cell, 'sensitive', 3) - self.status_combobox.set_row_separator_func(self.iter_is_separator) + self.status_combobox.set_row_separator_func(self._iter_is_separator) for show in ('online', 'chat', 'away', 'xa', 'dnd', 'invisible'): uf_show = helpers.get_uf_show(show) - liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ - show], show, True]) - # Add a Separator (self.iter_is_separator() checks on string SEPARATOR) + liststore.append([uf_show, gajim.interface.jabber_state_images['16'][show], show, + True]) + # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) liststore.append(['SEPARATOR', None, '', True]) path = os.path.join(gajim.DATA_DIR, 'pixmaps', 'kbd_input.png') @@ -5366,12 +5871,12 @@ class RosterWindow: # sensitivity to False because by default we're offline self.status_message_menuitem_iter = liststore.append( [_('Change Status Message...'), img, '', False]) - # Add a Separator (self.iter_is_separator() checks on string SEPARATOR) + # Add a Separator (self._iter_is_separator() checks on string SEPARATOR) liststore.append(['SEPARATOR', None, '', True]) uf_show = helpers.get_uf_show('offline') - liststore.append([uf_show, gajim.interface.jabber_state_images['16'][ - 'offline'], 'offline', True]) + liststore.append([uf_show, gajim.interface.jabber_state_images['16']['offline'], + 'offline', True]) status_combobox_items = ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', 'separator1', 'change_status_msg', 'separator2', @@ -5408,7 +5913,7 @@ class RosterWindow: col.pack_start(render_pixbuf, expand = False) col.add_attribute(render_pixbuf, 'pixbuf', C_AVATAR_PIXBUF) col.set_cell_data_func(render_pixbuf, - self.fill_avatar_pixbuf_rederer, None) + self._fill_avatar_pixbuf_rederer, None) if gajim.config.get('avatar_position_in_roster') == 'left': add_avatar_renderer() @@ -5417,13 +5922,13 @@ class RosterWindow: # show img or +- col.pack_start(render_image, expand = False) col.add_attribute(render_image, 'image', C_IMG) - col.set_cell_data_func(render_image, self.iconCellDataFunc, None) + col.set_cell_data_func(render_image, self._iconCellDataFunc, None) render_text = gtk.CellRendererText() # contact or group or account name render_text.set_property('ellipsize', pango.ELLIPSIZE_END) col.pack_start(render_text, expand = True) col.add_attribute(render_text, 'markup', C_NAME) # where we hold the name - col.set_cell_data_func(render_text, self.nameCellDataFunc, None) + col.set_cell_data_func(render_text, self._nameCellDataFunc, None) if gajim.config.get('avatar_position_in_roster') == 'right': add_avatar_renderer() @@ -5432,7 +5937,7 @@ class RosterWindow: col.pack_start(render_pixbuf, expand = False) col.add_attribute(render_pixbuf, 'pixbuf', C_PADLOCK_PIXBUF) col.set_cell_data_func(render_pixbuf, - self.fill_padlock_pixbuf_rederer, None) + self._fill_padlock_pixbuf_rederer, None) self.tree.append_column(col) # do not show gtk arrows workaround @@ -5444,7 +5949,7 @@ class RosterWindow: self.tree.set_expander_column(col) # set search function - self.tree.set_search_equal_func(self.search_roster_func) + self.tree.set_search_equal_func(self._search_roster_func) # signals self.TARGET_TYPE_URI_LIST = 80 @@ -5457,6 +5962,7 @@ class RosterWindow: self.tree.enable_model_drag_dest(TARGETS2, gtk.gdk.ACTION_DEFAULT) self.tree.connect('drag_begin', self.drag_begin) self.tree.connect('drag_end', self.drag_end) + self.tree.connect('drag_drop', self.drag_drop) self.tree.connect('drag_data_get', self.drag_data_get_data) self.tree.connect('drag_data_received', self.drag_data_received_data) self.dragging = False