From 13571fb8916bae3affd89efd93ad6d2c8073b015 Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Thu, 9 Jun 2005 17:37:18 +0000 Subject: [PATCH] restore a removed file ! --- src/roster_window.py | 1647 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1647 insertions(+) create mode 100644 src/roster_window.py diff --git a/src/roster_window.py b/src/roster_window.py new file mode 100644 index 000000000..b003c9715 --- /dev/null +++ b/src/roster_window.py @@ -0,0 +1,1647 @@ +## roster_window.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - Nikos Kouremenos +## +## Copyright (C) 2003-2005 Gajim Team +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## + +import gtk +import gtk.glade +import pango +import gobject +import os +import time +import common.sleepy + +import tabbed_chat_window +import groupchat_window +import history_window +import dialogs +import config +import cell_renderer_image + +from gajim import User +from common import gajim +from common import helpers +from common import i18n + +_ = i18n._ +APP = i18n.APP +gtk.glade.bindtextdomain(APP, i18n.DIR) +gtk.glade.textdomain(APP) + +GTKGUI_GLADE = 'gtkgui.glade' + +class Roster_window: + '''Class for main window of gtkgui plugin''' + + def get_account_iter(self, name): + if self.regroup: + return None + model = self.tree.get_model() + account = model.get_iter_root() + while account: + account_name = model.get_value(account, 3) + if name == account_name: + break + account = model.iter_next(account) + return account + + def get_group_iter(self, name, account): + model = self.tree.get_model() + root = self.get_account_iter(account) + group = model.iter_children(root) + while group: + group_name = model.get_value(group, 3) + if name == group_name: + break + group = model.iter_next(group) + return group + + def get_user_iter(self, jid, account): + model = self.tree.get_model() + acct = self.get_account_iter(account) + found = [] + fin = False + group = model.iter_children(acct) + while group: + user = model.iter_children(group) + while user: + if jid == model.get_value(user, 3): + found.append(user) + user = model.iter_next(user) + group = model.iter_next(group) + return found + + def add_account_to_roster(self, account): + if self.regroup: + return + model = self.tree.get_model() + if self.get_account_iter(account): + return + statuss = ['offline', 'connecting', 'online', 'chat', + 'away', 'xa', 'dnd', 'invisible'] + status = statuss[gajim.connections[account].connected] + model.append(None, (self.jabber_state_images[status], account, + 'account', account, account, False)) + + def remove_newly_added(self, jid, account): + if jid in self.newly_added[account]: + self.newly_added[account].remove(jid) + self.draw_contact(jid, account) + + def add_user_to_roster(self, jid, account): + '''Add a user to the roster and add groups if they aren't in roster''' + showOffline = gajim.config.get('showoffline') + if not self.contacts[account].has_key(jid): + return + users = self.contacts[account][jid] + user = users[0] + if user.jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent + user.groups = ['Transports'] + elif user.groups == []: + user.groups.append('General') + + if (user.show == 'offline' or user.show == 'error') and \ + not showOffline and (not 'Transports' in user.groups or \ + gajim.connections[account].connected < 2) and \ + not self.plugin.queues[account].has_key(user.jid): + return + + model = self.tree.get_model() + for g in user.groups: + iterG = self.get_group_iter(g, account) + if not iterG: + IterAcct = self.get_account_iter(account) + iterG = model.append(IterAcct, + (self.jabber_state_images['closed'], g, 'group', g, account, False)) + if not self.groups[account].has_key(g): #It can probably never append + if account + g in self.collapsed_rows: + ishidden = False + else: + ishidden = True + self.groups[account][g] = { 'expand': ishidden } + if not account in self.collapsed_rows and \ + not gajim.config.get('mergeaccounts'): + self.tree.expand_row((model.get_path(iterG)[0]), False) + + typestr = 'user' + if g == 'Transports': + typestr = 'agent' + + model.append(iterG, (self.jabber_state_images[user.show], user.name, + typestr, user.jid, account, False)) + + if self.groups[account][g]['expand']: + self.tree.expand_row(model.get_path(iterG), + False) + self.draw_contact(jid, account) + + def really_remove_user(self, user, account): + if user.jid in self.newly_added[account]: + return + if user.jid.find('@') < 1 and gajim.connections[account].connected > 1: # It's an agent + return + if user.jid in self.to_be_removed[account]: + self.to_be_removed[account].remove(user.jid) + if gajim.config.get('showoffline'): + self.draw_contact(user.jid, account) + return + self.remove_user(user, account) + + def remove_user(self, user, account): + '''Remove a user from the roster''' + if user.jid in self.to_be_removed[account]: + return + model = self.tree.get_model() + for i in self.get_user_iter(user.jid, account): + parent_i = model.iter_parent(i) + group = model.get_value(parent_i, 3) + model.remove(i) + if model.iter_n_children(parent_i) == 0: + model.remove(parent_i) + # We need to check all contacts, even offline contacts + group_empty = True + for jid in self.contacts[account]: + if group in self.contacts[account][jid][0].groups: + group_empty = False + break + if group_empty: + del self.groups[account][group] + + def get_appropriate_state_images(self, jid): + '''check jid and return the appropriate state images dict''' + if not jid or not gajim.config.get('use_transports_iconsets'): + return self.jabber_state_images + + host = jid.split('@')[-1] + + if host.startswith('aim'): + state_images = self.transports_state_images['aim'] + elif host.startswith('gadugadu'): + state_images = self.transports_state_images['gadugadu'] + elif host.startswith('irc'): + state_images = self.transports_state_images['irc'] + elif host.startswith('icq'): # abc@icqsucks.org will match as ICQ, but what to do.. + state_images = self.transports_state_images['icq'] + elif host.startswith('msn'): + state_images = self.transports_state_images['msn'] + elif host.startswith('sms'): + state_images = self.transports_state_images['sms'] + elif host.startswith('yahoo'): + state_images = self.transports_state_images['yahoo'] + else: + state_images = self.jabber_state_images + + return state_images + + def draw_contact(self, jid, account): + '''draw the correct state image and name''' + model = self.tree.get_model() + iters = self.get_user_iter(jid, account) + if len(iters) == 0: + return + users = self.contacts[account][jid] + name = users[0].name + if len(users) > 1: + name += ' (' + str(len(users)) + ')' + prio = 0 + user = users[0] + for u in users: + if u.priority > prio: + prio = u.priority + user = u + + state_images = self.get_appropriate_state_images(jid) + if self.plugin.queues[account].has_key(jid): + img = state_images['message'] + elif jid.find('@') <= 0: # if not '@' or '@' starts the jid ==> agent + img = state_images[user.show] + else: + if user.sub == 'both': + img = state_images[user.show] + else: + if user.ask == 'subscribe': + img = state_images['requested'] + else: + img = state_images['not in the roster'] + for iter in iters: + model.set_value(iter, 0, img) + model.set_value(iter, 1, name) + + def make_menu(self): + '''create the main_window's menus''' + new_message_menuitem = self.xml.get_widget('new_message_menuitem') + join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') + add_new_contact_menuitem = self.xml.get_widget('add_new_contact_menuitem') + service_disco_menuitem = self.xml.get_widget('service_disco_menuitem') + if self.add_new_contact_handler_id: + add_new_contact_menuitem.handler_disconnect(self.add_new_contact_handler_id) + self.add_new_contact_handler_id = None + if self.service_disco_handler_id: + service_disco_menuitem.handler_disconnect( + self.service_disco_handler_id) + self.service_disco_handler_id = None + if self.join_gc_handler_id: + join_gc_menuitem.handler_disconnect(self.join_gc_handler_id) + self.join_gc_handler_id = None + if self.new_message_menuitem_handler_id: + new_message_menuitem.handler_disconnect( + self.new_message_menuitem_handler_id) + self.new_message_menuitem_handler_id = None + #remove the existing submenus + if add_new_contact_menuitem.get_submenu(): + add_new_contact_menuitem.remove_submenu() + if service_disco_menuitem.get_submenu(): + service_disco_menuitem.remove_submenu() + if join_gc_menuitem.get_submenu(): + join_gc_menuitem.remove_submenu() + if new_message_menuitem.get_submenu(): + new_message_menuitem.remove_submenu() + if len(gajim.connections) > 0: + new_message_menuitem.set_sensitive(True) + join_gc_menuitem.set_sensitive(True) + add_new_contact_menuitem.set_sensitive(True) + service_disco_menuitem.set_sensitive(True) + else: + new_message_menuitem.set_sensitive(False) + join_gc_menuitem.set_sensitive(False) + add_new_contact_menuitem.set_sensitive(False) + service_disco_menuitem.set_sensitive(False) + if len(gajim.connections) >= 2: # 2 or more accounts? make submenus + #add + sub_menu = gtk.Menu() + add_new_contact_menuitem.set_submenu(sub_menu) + for account in gajim.connections: + item = gtk.MenuItem(_('to ') + account + _(' account')) + sub_menu.append(item) + item.connect('activate', self.on_add_new_contact, account) + sub_menu.show_all() + #disco + sub_menu = gtk.Menu() + service_disco_menuitem.set_submenu(sub_menu) + for account in gajim.connections: + our_jid = gajim.config.get_per('accounts', account, 'name') + '@' +\ + gajim.config.get_per('accounts', account, 'hostname') + item = gtk.MenuItem(_('using ') + account + _(' account')) + sub_menu.append(item) + item.connect('activate', self.on_service_disco_menuitem_activate, account) + sub_menu.show_all() + #join gc + sub_menu = gtk.Menu() + join_gc_menuitem.set_submenu(sub_menu) + for account in gajim.connections: + our_jid = gajim.config.get_per('accounts', account, 'name') + '@' +\ + gajim.config.get_per('accounts', account, 'hostname') + lbl = gtk.Label() + lbl.set_markup('abc') + item = gtk.MenuItem() + item.add(lbl) + #item = gtk.MenuItem(_('as ') + our_jid) + sub_menu.append(item) + item.connect('activate', self.on_join_gc_activate, account) + sub_menu.show_all() + #new message + sub_menu = gtk.Menu() + new_message_menuitem.set_submenu(sub_menu) + for account in gajim.connections: + our_jid = gajim.config.get_per('accounts', account, 'name') + '@' +\ + gajim.config.get_per('accounts', account, 'hostname') + item = gtk.MenuItem(_('as ') + our_jid) + sub_menu.append(item) + item.connect('activate', self.on_new_message_menuitem_activate, + account) + sub_menu.show_all() + elif len(gajim.connections) == 1: # one account + #add + if not self.add_new_contact_handler_id: + self.add_new_contact_handler_id = add_new_contact_menuitem.connect( + 'activate', self.on_add_new_contact, gajim.connections.keys()[0]) + #disco + if not self.service_disco_handler_id: + self.service_disco_handler_id = service_disco_menuitem.connect( + 'activate', self.on_service_disco_menuitem_activate, + gajim.connections.keys()[0]) + #join_gc + if not self.join_gc_handler_id: + self.join_gc_handler_id = join_gc_menuitem.connect( + 'activate', self.on_join_gc_activate, + gajim.connections.keys()[0]) + if not self.new_message_menuitem_handler_id: + self.new_message_menuitem_handler_id = new_message_menuitem.\ + connect('activate', self.on_new_message_menuitem_activate, + gajim.connections.keys()[0]) + + def draw_roster(self): + '''Clear and draw roster''' + self.make_menu() + self.tree.get_model().clear() + for acct in gajim.connections: + self.add_account_to_roster(acct) + for jid in self.contacts[acct].keys(): + self.add_user_to_roster(jid, acct) + + def mklists(self, array, account): + '''fill self.contacts and self.groups''' + if not self.contacts.has_key(account): + self.contacts[account] = {} + if not self.groups.has_key(account): + self.groups[account] = {} + for jid in array.keys(): + 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: + if ji.find('@') <= 0: + name = ji + else: + name = jid.split('@')[0] + 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] + user1 = User(ji, name, array[jid]['groups'], show, status,\ + array[jid]['subscription'], array[jid]['ask'], resource, 0, + keyID) + + # when we draw the roster, we avoid having the same contact + # more than once (eg. we avoid showing it twice with 2 resources) + self.contacts[account][ji] = [user1] + for g in array[jid]['groups'] : + if g in self.groups[account].keys(): + continue + + if account + g in self.collapsed_rows: + ishidden = False + else: + ishidden = True + self.groups[account][g] = { 'expand': ishidden } + + def chg_user_status(self, user, show, status, account): + '''When a user change his status''' + showOffline = gajim.config.get('showoffline') + model = self.tree.get_model() + luser = self.contacts[account][user.jid] + user.show = show + user.status = status + if (show == 'offline' or show == 'error') and \ + not self.plugin.queues[account].has_key(user.jid): + if len(luser) > 1: + luser.remove(user) + self.draw_contact(user.jid, account) + elif not showOffline: + self.remove_user(user, account) + else: + self.draw_contact(user.jid, account) + else: + if not self.get_user_iter(user.jid, account): + self.add_user_to_roster(user.jid, account) + self.draw_contact(user.jid, account) + #print status in chat window and update status/GPG image + if self.plugin.windows[account]['chats'].has_key(user.jid): + jid = user.jid + self.plugin.windows[account]['chats'][jid].set_state_image(jid) + name = user.name + if user.resource != '': + name += '/' + user.resource + uf_show = helpers.get_uf_show(show) + self.plugin.windows[account]['chats'][jid].print_conversation( + _('%s is now %s (%s)') % (name, uf_show, status), jid, 'status') + + def on_info(self, widget, user, account): + '''Call vcard_information_window class to display user's information''' + info = self.plugin.windows[account]['infos'] + if info.has_key(user.jid): + info[user.jid].window.present() + else: + info[user.jid] = dialogs.Vcard_window(user, self.plugin, + account) + + 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, user, account): + '''When we want to modify the agent registration''' + gajim.connections[account].request_register_agent_info(user.jid) + + def on_remove_agent(self, widget, user, account): + '''When an agent is requested to log in or off''' + window = dialogs.Confirmation_dialog(_('Transport "%s" will be removed') % user.jid, _('You will no longer be able to send and receive messages to contacts from %s.' % user.jid)) + if window.get_response() == gtk.RESPONSE_YES: + gajim.connections[account].unsubscribe_agent(user.jid + '/' \ + + user.resource) + # remove transport from treeview + self.remove_user(user, account) + # remove transport's contacts from treeview + for jid, contacts in self.contacts[account].items(): + contact = contacts[0] + if jid.endswith('@' + user.jid): + gajim.log.debug('REMOVE user %s due to unregistered transport %s' % (contact.jid, user.name)) + self.remove_user(contact, account) + del self.contacts[account][user.jid] + + def on_rename(self, widget, iter, path): + model = self.tree.get_model() + model.set_value(iter, 5, True) + self.tree.set_cursor(path, self.tree.get_column(0), True) + + def on_assign_pgp_key(self, widget, user, account): + attached_keys = gajim.config.get_per('accounts', account, + 'attached_gpg_keys').split() + keys = {} + keyID = 'None' + for i in range(0, len(attached_keys)/2): + keys[attached_keys[2*i]] = attached_keys[2*i+1] + if attached_keys[2*i] == user.jid: + keyID = attached_keys[2*i+1] + public_keys = gajim.connections[account].ask_gpg_keys() + public_keys['None'] = 'None' + w = dialogs.ChooseGPGKeyDialog(_('Assign PGP Key'), _('Select a key to apply to the contact'), + public_keys, keyID) + keyID = w.run() + if keyID == -1: + return + if keyID[0] == 'None' and user.jid in keys: + del keys[user.jid] + else: + keys[user.jid] = keyID[0] + for u in self.contacts[account][user.jid]: + u.keyID = keyID[0] + if self.plugin.windows[account]['chats'].has_key(user.jid): + self.plugin.windows[account]['chats'][user.jid].draw_widgets(user) + keys_str = '' + for jid in keys: + keys_str += jid + ' ' + keys[jid] + gajim.config.set_per('accounts', account, 'attached_gpg_keys', keys_str) + + def on_edit_groups(self, widget, user, account): + dlg = dialogs.Edit_groups_dialog(user, account, self.plugin) + dlg.run() + + def on_history(self, widget, user): + '''When history menuitem is activated: call log window''' + if self.plugin.windows['logs'].has_key(user.jid): + self.plugin.windows['logs'][user.jid].window.present() + else: + self.plugin.windows['logs'][user.jid] = history_window.\ + History_window(self.plugin, user.jid) + + def mk_menu_user(self, event, iter): + '''Make user's popup menu''' + model = self.tree.get_model() + jid = model.get_value(iter, 3) + path = model.get_path(iter) + account = model.get_value(iter, 4) + user = self.contacts[account][jid][0] + + menu = gtk.Menu() + item = gtk.MenuItem(_('Start chat')) + menu.append(item) + item.connect('activate', self.on_roster_treeview_row_activated, path) + item = gtk.MenuItem(_('Rename')) + menu.append(item) + item.connect('activate', self.on_rename, iter, path) + if not 'not in the roster' in user.groups: + item = gtk.MenuItem(_('Edit Groups')) + menu.append(item) + item.connect('activate', self.on_edit_groups, user, account) + item = gtk.MenuItem() + menu.append(item) + if gajim.config.get('usegpg'): + item = gtk.MenuItem(_('Assign OpenPGP key')) + menu.append(item) + item.connect('activate', self.on_assign_pgp_key, user, account) + item = gtk.MenuItem() + menu.append(item) + item = gtk.MenuItem(_('Subscription')) + menu.append(item) + + sub_menu = gtk.Menu() + item.set_submenu(sub_menu) + item = gtk.MenuItem(_('Resend Authorization to')) + sub_menu.append(item) + item.connect('activate', self.authorize, jid, account) + item = gtk.MenuItem(_('Rerequest Authorization from')) + sub_menu.append(item) + item.connect('activate', self.req_sub, jid, + _('I would like to add you to my roster'), account) + else: + item = gtk.MenuItem() + menu.append(item) + item = gtk.MenuItem(_('Add to Roster')) + menu.append(item) + item.connect('activate', self.on_add_to_roster, user, account) + + item = gtk.MenuItem(_('Remove')) + menu.append(item) + item.connect('activate', self.on_req_usub, user, account) + + item = gtk.MenuItem() + menu.append(item) + item = gtk.MenuItem(_('Information')) + menu.append(item) + item.connect('activate', self.on_info, user, account) + item = gtk.MenuItem(_('History')) + menu.append(item) + item.connect('activate', self.on_history, user) + + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def mk_menu_g(self, event, iter): + '''Make group's popup menu''' + model = self.tree.get_model() + path = model.get_path(iter) + + menu = gtk.Menu() + item = gtk.MenuItem(_('Rename')) + menu.append(item) + item.connect('activate', self.on_rename, iter, path) + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def mk_menu_agent(self, event, iter): + '''Make agent's popup menu''' + model = self.tree.get_model() + jid = model.get_value(iter, 3) + path = model.get_path(iter) + account = model.get_value(iter, 4) + user = self.contacts[account][jid][0] + menu = gtk.Menu() + item = gtk.MenuItem(_('_Log on')) + show = self.contacts[account][jid][0].show + if show != 'offline' and show != 'error': + item.set_sensitive(False) + menu.append(item) + item.connect('activate', self.on_agent_logging, jid, None, account) + + item = gtk.MenuItem(_('Log _off')) + if show == 'offline' or show == 'error': + item.set_sensitive(False) + menu.append(item) + item.connect('activate', self.on_agent_logging, jid, 'unavailable', + account) + + item = gtk.MenuItem() + menu.append(item) + + item = gtk.MenuItem(_('_Edit')) + menu.append(item) + item.connect('activate', self.on_edit_agent, user, account) + + item = gtk.MenuItem(_('_Remove')) + menu.append(item) + item.connect('activate', self.on_remove_agent, user, account) + + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def on_edit_account(self, widget, account): + if self.plugin.windows[account].has_key('account_modification'): + self.plugin.windows[account]['account_modification'].window.present() + else: + self.plugin.windows[account]['account_modification'] = \ + config.Account_modification_window(self.plugin, account) + + def mk_menu_account(self, event, iter): + '''Make account's popup menu''' + model = self.tree.get_model() + account = model.get_value(iter, 3) + + menu = gtk.Menu() + item = gtk.MenuItem(_('Status')) + menu.append(item) + + sub_menu = gtk.Menu() + item.set_submenu(sub_menu) + item = gtk.MenuItem(_('Online')) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'online') + item = gtk.MenuItem(_('Free for chat')) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'chat') + item = gtk.MenuItem(_('Away')) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'away') + item = gtk.MenuItem(_('Not Available')) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'xa') + item = gtk.MenuItem(_('Busy')) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'dnd') + item = gtk.MenuItem(_('Invisible')) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'invisible') + item = gtk.MenuItem() + sub_menu.append(item) + item = gtk.MenuItem(_('Offline')) + sub_menu.append(item) + item.connect('activate', self.change_status, account, 'offline') + + item = gtk.MenuItem() + menu.append(item) + + item = gtk.MenuItem(_('_Edit Account')) + menu.append(item) + item.connect('activate', self.on_edit_account, account) + item = gtk.MenuItem(_('_Service Discovery')) + menu.append(item) + item.connect('activate', self.on_service_disco_menuitem_activate, account) + item = gtk.MenuItem(_('_Add Contact')) + menu.append(item) + item.connect('activate', self.on_add_new_contact, account) + item = gtk.MenuItem(_('Join _Groupchat')) + menu.append(item) + item.connect('activate', self.on_join_gc_activate, account) + item = gtk.MenuItem(_('_New Message')) + menu.append(item) + item.connect('activate', self.on_new_message_menuitem_activate, account) + + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def on_add_to_roster(self, widget, user, account): + dialogs.Add_new_contact_window(self.plugin, account, user.jid) + + def authorize(self, widget, jid, account): + '''Authorize a user''' + gajim.connections[account].send_authorization(jid) + + def req_sub(self, widget, jid, txt, account, group = None, pseudo = None): + '''Request subscription to a user''' + if not pseudo: + pseudo = jid + gajim.connections[account].request_subscription(jid, txt) + if not group: + group = 'General' + if not self.contacts[account].has_key(jid): + 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] + user1 = User(jid, pseudo, [group], 'requested', 'requested', + 'none', 'subscribe', '', 0, keyID) + self.contacts[account][jid] = [user1] + else: + user1 = self.contacts[account][jid][0] + if not 'not in the roster' in user1.groups: + return + user1.groups = [group] + user1.name = pseudo + self.remove_user(user1, account) + self.add_user_to_roster(jid, account) + + def on_roster_treeview_key_press_event(self, widget, event): + '''when a key is pressed in the treeviews''' + if event.keyval == gtk.keysyms.Escape: + self.tree.get_selection().unselect_all() + if event.keyval == gtk.keysyms.F2: + treeselection = self.tree.get_selection() + model, iter = treeselection.get_selected() + if not iter: + return + type = model.get_value(iter, 2) + if type == 'user' or type == 'group': + path = model.get_path(iter) + model.set_value(iter, 5, True) + self.tree.set_cursor(path, self.tree.get_column(0), True) + if event.keyval == gtk.keysyms.Delete: + treeselection = self.tree.get_selection() + model, iter = treeselection.get_selected() + if not iter: + return + jid = model.get_value(iter, 3) + account = model.get_value(iter, 4) + type = model.get_value(iter, 2) + if type == 'user': + user = self.contacts[account][jid][0] + self.on_req_usub(widget, user, account) + elif type == 'agent': + self.on_remove_agent(widget, jid, account) + return False + + def on_roster_treeview_button_press_event(self, widget, event): + '''popup contact's, group's or agent's menu''' + if event.type == gtk.gdk.BUTTON_PRESS: + if event.button == 3: # Right click + 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 + self.tree.get_selection().select_path(path) + model = self.tree.get_model() + iter = model.get_iter(path) + type = model.get_value(iter, 2) + if type == 'group': + self.mk_menu_g(event, iter) + elif type == 'agent': + self.mk_menu_agent(event, iter) + elif type == 'user': + self.mk_menu_user(event, iter) + elif type == 'account': + self.mk_menu_account(event, iter) + return True + if event.button == 1: # Left click + 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 + model = self.tree.get_model() + iter = model.get_iter(path) + type = model.get_value(iter, 2) + if (type == 'group'): + if x < 20: # 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) + + def on_req_usub(self, widget, user, account): + '''Remove a user''' + window = dialogs.Confirmation_dialog(\ + _('Contact "%s" will be removed from your roster') % (user.name), + _('By removing this contact you also remove authorization. Contact "%s" will always see you as offline.') % user.name) + if window.get_response() == gtk.RESPONSE_OK: + gajim.connections[account].unsubscribe(user.jid) + for u in self.contacts[account][user.jid]: + self.remove_user(u, account) + del self.contacts[account][u.jid] + + def forget_gpg_passphrase(self, keyid): + if self.gpg_passphrase.has_key(keyid): + del self.gpg_passphrase[keyid] + return False + + def send_status(self, account, status, txt): + if status != 'offline': + if gajim.connections[account].connected < 2: + model = self.tree.get_model() + accountIter = self.get_account_iter(account) + if accountIter: + model.set_value(accountIter, 0, self.jabber_state_images['connecting']) + if self.plugin.systray_enabled: + self.plugin.systray.set_status('connecting') + + save_pass = gajim.config.get_per('accounts', account, 'savepass') + if not save_pass and gajim.connections[account].connected < 2: + passphrase = '' + w = dialogs.Passphrase_dialog( + _('Password Required'), + _('Enter your password for account %s') % account, + _('Save password')) + passphrase, save = w.run() + if passphrase == -1: + if accountIter: + model.set_value(accountIter, 0, self.jabber_state_images['offline']) +# gajim.connections[account].connected = 0 + if self.plugin.systray_enabled: + self.plugin.systray.set_status('offline') + self.update_status_comboxbox() + return + gajim.connections[account].password = passphrase + if save: + gajim.config.set_per('accounts', account, 'savepass', True) + gajim.config.set_per('accounts', account, 'password', passphrase) + + keyid = None + save_gpg_pass = 0 + save_gpg_pass = gajim.config.get_per('accounts', account, + 'savegpgpass') + keyid = gajim.config.get_per('accounts', account, 'keyid') + if keyid and gajim.connections[account].connected < 2 and \ + gajim.config.get('usegpg'): + if save_gpg_pass: + passphrase = gajim.config.get_per('accounts', account, + 'gpgpassword') + else: + if self.gpg_passphrase.has_key(keyid): + passphrase = self.gpg_passphrase[keyid] + save = False + else: + w = dialogs.Passphrase_dialog( + _('Passphrase Required'), + _('Enter GPG key passphrase for account %s') % account, + _('Save passphrase')) + passphrase, save = w.run() + if passphrase == -1: + passphrase = '' + else: + self.gpg_passphrase[keyid] = passphrase + gobject.timeout_add(30000, self.forget_gpg_passphrase, keyid) + if save: + gajim.config.set_per('accounts', account, 'savegpgpass', True) + gajim.config.set_per('accounts', account, 'gpgpassword', + passphrase) + gajim.connections[account].gpg_passphrase(passphrase) + gajim.connections[account].change_status(status, txt) + for room_jid in self.plugin.windows[account]['gc']: + if room_jid != 'tabbed': + nick = self.plugin.windows[account]['gc'][room_jid].nicks[room_jid] + gajim.connections[account].send_gc_status(nick, room_jid, status, + txt) + if status == 'online' and self.plugin.sleeper.getState() != \ + common.sleepy.STATE_UNKNOWN: + self.plugin.sleeper_state[account] = 1 + else: + self.plugin.sleeper_state[account] = 0 + + def get_status_message(self, show): + if (show == 'online' and not gajim.config.get('ask_online_status')) or \ + (show == 'offline' and not gajim.config.get('ask_offline_status')): + lowered_uf_status_msg = helpers.get_uf_show(show).lower() + return _("I'm %s") % lowered_uf_status_msg + dlg = dialogs.Change_status_message_dialog(self.plugin, show) + message = dlg.run() + return message + + def change_status(self, widget, account, status): + message = self.get_status_message(status) + if message == -1: + return + self.send_status(account, status, message) + + def on_status_combobox_changed(self, widget): + '''When we change our status''' + model = self.status_combobox.get_model() + active = self.status_combobox.get_active() + if active < 0: + return + accounts = gajim.connections.keys() + if len(accounts) == 0: + dialogs.Error_dialog(_('No accounts created'), + _('You must create Jabber account before connecting the server.')).get_response() + self.update_status_comboxbox() + return + status = model[active][2] + message = self.get_status_message(status) + if message == -1: + self.update_status_comboxbox() + return + for acct in accounts: + if not gajim.config.get_per('accounts', acct, + 'sync_with_global_status'): + continue + self.send_status(acct, status, message) + + def update_status_comboxbox(self): + #table to change index in plugin.connected to index in combobox + table = {0:6, 1:6, 2:0, 3:1, 4:2, 5:3, 6:4, 7:5} + maxi = 0 + for account in gajim.connections: + if gajim.connections[account].connected > maxi: + maxi = gajim.connections[account].connected + #temporarily block signal in order not to send status that we show + #in the combobox + self.status_combobox.handler_block(self.id_signal_cb) + self.status_combobox.set_active(table[maxi]) + self.status_combobox.handler_unblock(self.id_signal_cb) + statuss = ['offline', 'connecting', 'online', 'chat', 'away', + 'xa', 'dnd', 'invisible'] + if self.plugin.systray_enabled: + self.plugin.systray.set_status(statuss[maxi]) + + def on_status_changed(self, account, status): + '''the core tells us that our status has changed''' + if not self.contacts.has_key(account): + return + model = self.tree.get_model() + accountIter = self.get_account_iter(account) + if accountIter: + model.set_value(accountIter, 0, self.jabber_state_images[status]) + if status == 'offline': + for jid in self.contacts[account]: + luser = self.contacts[account][jid] + luser_copy = [] + for user in luser: + luser_copy.append(user) + for user in luser_copy: + self.chg_user_status(user, 'offline', 'Disconnected', account) + self.update_status_comboxbox() + + def new_chat(self, user, account): + if gajim.config.get('usetabbedchat'): + if not self.plugin.windows[account]['chats'].has_key('tabbed'): + self.plugin.windows[account]['chats']['tabbed'] = \ + tabbed_chat_window.Tabbed_chat_window(user, self.plugin, account) + else: + self.plugin.windows[account]['chats']['tabbed'].new_user(user) + + self.plugin.windows[account]['chats'][user.jid] = \ + self.plugin.windows[account]['chats']['tabbed'] + else: + self.plugin.windows[account]['chats'][user.jid] = \ + tabbed_chat_window.Tabbed_chat_window(user, self.plugin, account) + + def new_room(self, jid, nick, account): + if gajim.config.get('usetabbedchat'): + if not self.plugin.windows[account]['gc'].has_key('tabbed'): + self.plugin.windows[account]['gc']['tabbed'] = \ + groupchat_window.Groupchat_window(jid, nick, self.plugin, + account) + else: + self.plugin.windows[account]['gc']['tabbed'].new_room(jid, nick) + self.plugin.windows[account]['gc'][jid] = \ + self.plugin.windows[account]['gc']['tabbed'] + else: + self.plugin.windows[account]['gc'][jid] = \ + groupchat_window.Groupchat_window(jid, nick, self.plugin, account) + + def on_message(self, jid, msg, tim, account, encrypted = False): + '''when we receive a message''' + if not self.contacts[account].has_key(jid): + 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] + user1 = User(jid, jid, ['not in the roster'], 'not in the roster', + 'not in the roster', 'none', None, '', 0, keyID) + self.contacts[account][jid] = [user1] + self.add_user_to_roster(jid, account) + iters = self.get_user_iter(jid, account) + if iters: + path = self.tree.get_model().get_path(iters[0]) + else: + path = None + autopopup = gajim.config.get('autopopup') + autopopupaway = gajim.config.get('autopopupaway') + # Do we have a queue ? + no_queue = True + if self.plugin.queues[account].has_key(jid): + no_queue = False + if self.plugin.windows[account]['chats'].has_key(jid): + self.plugin.windows[account]['chats'][jid].print_conversation(msg, + jid, tim = tim, encrypted = encrypted) + return + #We save it in a queue + if not self.plugin.queues[account].has_key(jid): + self.plugin.queues[account][jid] = [] + self.plugin.queues[account][jid].append((msg, tim, encrypted)) + self.nb_unread += 1 + if (not autopopup or ( not autopopupaway and \ + gajim.connections[account].connected > 2)) and not \ + self.plugin.windows[account]['chats'].has_key(jid): + if no_queue: #We didn't have a queue: we change icons + model = self.tree.get_model() + self.draw_contact(jid, account) + if self.plugin.systray_enabled: + self.plugin.systray.add_jid(jid, account) + self.show_title() # we show the * or [n] + if not path: + self.add_user_to_roster(jid, account) + iters = self.get_user_iter(jid, account) + path = self.tree.get_model().get_path(iters[0]) + 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 not self.plugin.windows[account]['chats'].has_key(jid): + self.new_chat(self.contacts[account][jid][0], account) + if path: + 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 on_preferences_menuitem_activate(self, widget): + if self.plugin.windows['preferences'].window.get_property('visible'): + self.plugin.windows['preferences'].window.present() + else: + self.plugin.windows['preferences'].window.show_all() + + def on_add_new_contact(self, widget, account): + dialogs.Add_new_contact_window(self.plugin, account) + + def on_join_gc_activate(self, widget, account): + if self.plugin.windows[account].has_key('join_gc'): + self.plugin.windows[account]['join_gc'].window.present() + else: + try: + self.plugin.windows[account]['join_gc'] = dialogs.Join_groupchat_window(self.plugin, account) + except RuntimeError: + pass + + + def on_new_message_menuitem_activate(self, widget, account): + dialogs.New_message_dialog(self.plugin, account) + + def on_about_menuitem_activate(self, widget): + dialogs.About_dialog() + + def on_accounts_menuitem_activate(self, widget): + if self.plugin.windows.has_key('accounts'): + self.plugin.windows['accounts'].window.present() + else: + self.plugin.windows['accounts'] = config.Accounts_window(self.plugin) + + def close_all(self, dic): + '''close all the windows in the given dictionary''' + for w in dic.values(): + if type(w) == type({}): + self.close_all(w) + else: + w.window.destroy() + + def on_roster_window_delete_event(self, widget, event): + '''When we want to close the window''' + if self.plugin.systray_enabled: + self.window.hide() + else: + 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 == -1: + message = '' + for acct in accounts: + if gajim.connections[acct].connected: + self.send_status(acct, 'offline', message) + self.quit_gtkgui_plugin() + return True # do NOT destory the window + + def quit_gtkgui_plugin(self): + '''When we quit the gtk plugin : + tell that to the core and exit gtk''' + if gajim.config.get('saveposition'): + x, y = self.window.get_position() + gajim.config.set('x-position', x) + gajim.config.set('y-position', y) + width, height = self.window.get_size() + gajim.config.set('width', width) + gajim.config.set('height', height) + + gajim.config.set('collapsed_rows', '\t'.join(self.collapsed_rows)) + self.plugin.save_config() + for account in gajim.connections: + gajim.connections[account].quit(True) + self.close_all(self.plugin.windows) + if self.plugin.systray_enabled: + self.plugin.hide_systray() + gtk.main_quit() + + def on_quit_menuitem_activate(self, widget): + 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 == -1: + return + # check if we have unread or recent mesages + unread = False + recent = False + if self.nb_unread > 0: + unread = True + for account in accounts: + if self.plugin.windows[account]['chats'].has_key('tabbed'): + wins = [self.plugin.windows[account]['chats']['tabbed']] + else: + wins = self.plugin.windows[account]['chats'].values() + for win in wins: + unrd = 0 + for jid in win.nb_unread: + unrd += win.nb_unread[jid] + if unrd: + unread = True + break + for jid in win.users: + if time.time() - win.last_message_time[jid] < 2: + recent = True + break + if unread: + dialog = dialogs.Confirmation_dialog(_('You have unread messages'), _('If you exit Gajim these messages will be lost.')) + if dialog.get_response() != gtk.RESPONSE_OK: + return + + if recent: + dialog = dialogs.Confirmation_dialog(_('You have unread messages'), _('If you exit Gajim these messages will be lost.')) + if dialog.get_response() != gtk.RESPONSE_OK: + return + for acct in accounts: + if gajim.connections[acct].connected: + self.send_status(acct, 'offline', message) + self.quit_gtkgui_plugin() + + def on_roster_treeview_row_activated(self, widget, path, col = 0): + '''When an iter is double clicked: open the chat window''' + model = self.tree.get_model() + iter = model.get_iter(path) + account = model.get_value(iter, 4) + type = model.get_value(iter, 2) + jid = model.get_value(iter, 3) + if (type == 'group') or (type == 'account'): + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + else: + if self.plugin.windows[account]['chats'].has_key(jid): + self.plugin.windows[account]['chats'][jid].set_active_tab(jid) + elif self.contacts[account].has_key(jid): + self.new_chat(self.contacts[account][jid][0], account) + self.plugin.windows[account]['chats'][jid].set_active_tab(jid) + self.plugin.windows[account]['chats'][jid].window.present() + + 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() + account = model.get_value(iter, 4) + type = model.get_value(iter, 2) + if type == 'group': + model.set_value(iter, 0, self.jabber_state_images['opened']) + jid = model.get_value(iter, 3) + self.groups[account][jid]['expand'] = True + if account + jid in self.collapsed_rows: + self.collapsed_rows.remove(account + jid) + elif type == 'account': + if account in self.collapsed_rows: + self.collapsed_rows.remove(account) + for g in self.groups[account]: + groupIter = self.get_group_iter(g, account) + if groupIter and self.groups[account][g]['expand']: + pathG = model.get_path(groupIter) + self.tree.expand_row(pathG, False) + + + 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() + account = model.get_value(iter, 4) + type = model.get_value(iter, 2) + if type == 'group': + model.set_value(iter, 0, self.jabber_state_images['closed']) + jid = model.get_value(iter, 3) + self.groups[account][jid]['expand'] = False + if not account + jid in self.collapsed_rows: + self.collapsed_rows.append(account + jid) + elif type == 'account': + if not account in self.collapsed_rows: + self.collapsed_rows.append(account) + + def on_editing_canceled (self, cell): + '''editing has been canceled''' + #TODO: get iter + #model.set_value(iter, 5, False) + pass + + def on_cell_edited(self, cell, row, new_text): + '''When an iter is editer : + if text has changed, rename the user''' + model = self.tree.get_model() + iter = model.get_iter_from_string(row) + path = model.get_path(iter) + account = model.get_value(iter, 4) + jid = model.get_value(iter, 3) + type = model.get_value(iter, 2) + if type == 'user': + old_text = self.contacts[account][jid][0].name + if old_text != new_text: + for u in self.contacts[account][jid]: + u.name = new_text + gajim.connections[account].update_user(jid, new_text, u.groups) + self.draw_contact(jid, account) + elif type == 'group': + old_name = model.get_value(iter, 1) + #get all users in that group + for jid in self.contacts[account]: + user = self.contacts[account][jid][0] + if old_name in user.groups: + #set them in the new one and remove it from the old + self.remove_user(user, account) + user.groups.remove(old_name) + user.groups.append(new_text) + self.add_user_to_roster(user.jid, account) + gajim.connections[account].update_user(user.jid, user.name, + user.groups) + model.set_value(iter, 5, False) + + def on_service_disco_menuitem_activate(self, widget, account): + if self.plugin.windows[account].has_key('disco'): + self.plugin.windows[account]['disco'].window.present() + else: + try: + self.plugin.windows[account]['disco'] = \ + config.Service_discovery_window(self.plugin, account) + except RuntimeError: + pass + + + def load_iconset(self, path): + imgs = {} + for state in ('connecting', 'online', 'chat', 'away', 'xa', + 'dnd', 'invisible', 'offline', 'error', 'requested', + 'message', 'opened', 'closed', 'not in the roster'): + + # try to open a pixfile with the correct method + state_file = state.replace(' ', '_') + files = [] + files.append(path + state_file + '.gif') + files.append(path + state_file + '.png') + files.append(path + state_file + '.xpm') + image = gtk.Image() + image.show() + imgs[state] = image + for file in files: + if os.path.exists(file): + image.set_from_file(file) + break + return imgs + + def make_jabber_state_images(self): + '''initialise jabber_state_images dict''' + iconset = gajim.config.get('iconset') + if not iconset: + iconset = 'sun' + self.path = os.path.join(gajim.DATA_DIR, 'iconsets/' + iconset + '/') + self.jabber_state_images = self.load_iconset(self.path) + + def reload_jabber_state_images(self): + self.make_jabber_state_images() + # Update the roster + self.draw_roster() + # Update the status combobox + model = self.status_combobox.get_model() + iter = model.get_iter_root() + while iter: + model.set_value(iter, 1, self.jabber_state_images[model.get_value(iter, 2)]) + iter = model.iter_next(iter) + # Update the systray + if self.plugin.systray_enabled: + self.plugin.systray.set_img() + for account in gajim.connections: + # Update opened chat windows + for jid in self.plugin.windows[account]['chats']: + if jid != 'tabbed': + self.plugin.windows[account]['chats'][jid].set_state_image(jid) + # Update opened groupchat windows + for jid in self.plugin.windows[account]['gc']: + if jid != 'tabbed': + self.plugin.windows[account]['gc'][jid].update_state_images() + self.update_status_comboxbox() + + def repaint_themed_widgets(self): + """Notify windows that contain themed widgets to repaint them""" + for account in gajim.connections: + # Update opened chat windows/tabs + for jid in self.plugin.windows[account]['chats']: + self.plugin.windows[account]['chats'][jid].repaint_colored_widgets() + + def on_show_offline_contacts_menuitem_activate(self, widget): + '''when show offline option is changed: + redraw the treeview''' + gajim.config.set('showoffline', 1 - gajim.config.get('showoffline')) + self.draw_roster() + + def iconCellDataFunc(self, column, renderer, model, iter, data = None): + '''When a row is added, set properties for icon renderer''' + if model.get_value(iter, 2) == 'account': + renderer.set_property('cell-background', + gajim.config.get('accountbgcolor')) + renderer.set_property('xalign', 0) + elif model.get_value(iter, 2) == 'group': + renderer.set_property('cell-background', + gajim.config.get('groupbgcolor')) + renderer.set_property('xalign', 0.5) + else: + jid = model.get_value(iter, 3) + account = model.get_value(iter, 4) + if jid in self.newly_added[account]: + renderer.set_property('cell-background', '#adc3c6') + elif jid in self.to_be_removed[account]: + renderer.set_property('cell-background', '#ab6161') + else: + renderer.set_property('cell-background', + gajim.config.get('userbgcolor')) + renderer.set_property('xalign', 1) + renderer.set_property('width', 20) + + def nameCellDataFunc(self, column, renderer, model, iter, data = None): + '''When a row is added, set properties for name renderer''' + if model.get_value(iter, 2) == 'account': + renderer.set_property('foreground', + gajim.config.get('accounttextcolor')) + renderer.set_property('cell-background', + gajim.config.get('accountbgcolor')) + renderer.set_property('font', gajim.config.get('accountfont')) + renderer.set_property('xpad', 0) + elif model.get_value(iter, 2) == 'group': + renderer.set_property('foreground', + gajim.config.get('grouptextcolor')) + renderer.set_property('cell-background', + gajim.config.get('groupbgcolor')) + renderer.set_property('font', gajim.config.get('groupfont')) + renderer.set_property('xpad', 4) + else: + jid = model.get_value(iter, 3) + account = model.get_value(iter, 4) + renderer.set_property('foreground', + gajim.config.get('usertextcolor')) + if jid in self.newly_added[account]: + renderer.set_property('cell-background', '#adc3c6') + elif jid in self.to_be_removed[account]: + renderer.set_property('cell-background', '#ab6161') + else: + renderer.set_property('cell-background', + gajim.config.get('userbgcolor')) + renderer.set_property('font', gajim.config.get('userfont')) + renderer.set_property('xpad', 8) + + def get_show(self, luser): + prio = luser[0].priority + show = luser[0].show + for u in luser: + 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.get_value(iter1, 1) + name2 = model.get_value(iter2, 1) + if not name1 or not name2: + return 0 + type1 = model.get_value(iter1, 2) + type2 = model.get_value(iter2, 2) + if type1 == 'group': + if name1 == 'Transports': + return 1 + if name2 == 'Transports': + return -1 + if name1 == 'not in the roster': + return 1 + if name2 == 'not in the roster': + return -1 + if type1 == 'user' and type2 == 'user' and \ + gajim.config.get('sort_by_show'): + account = model.get_value(iter1, 4) + if account and model.get_value(iter2, 4) == account: + jid1 = model.get_value(iter1, 3) + jid2 = model.get_value(iter2, 3) + luser1 = self.contacts[account][jid1] + luser2 = self.contacts[account][jid2] + cshow = {'online':0, 'chat': 1, 'away':2, 'xa':3, 'dnd':4, + 'invisible':5, 'offline':6, 'not in the roster':7, 'error':8} + s = self.get_show(luser1) + if s in cshow: + show1 = cshow[s] + else: + show1 = 9 + s = self.get_show(luser2) + if s in cshow: + show2 = cshow[s] + else: + show2 = 9 + if show1 < show2: + return -1 + elif show1 > show2: + return 1 + if name1.lower() < name2.lower(): + return -1 + if name2.lower < name1.lower(): + return 1 + return 0 + + def drag_data_get_data(self, treeview, context, selection, target_id, etime): + treeselection = treeview.get_selection() + model, iter = treeselection.get_selected() + path = model.get_path(iter) + data = '' + if len(path) == 3: + data = model.get_value(iter, 3) + selection.set(selection.target, 8, data) + + def drag_data_received_data(self, treeview, context, x, y, selection, info, + etime): + merge = 0 + if gajim.config.get('mergeaccounts'): + merge = 1 + model = treeview.get_model() + data = selection.data + if not data: + return + drop_info = treeview.get_dest_row_at_pos(x, y) + if not drop_info: + return + path_dest, position = drop_info + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 - merge\ + and path_dest[1 - merge] == 0: #droped before the first group + return + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2 - merge: + #droped before a group : we drop it in the previous group + path_dest = (path_dest[1 - merge], path_dest[1 - merge]-1) + iter_dest = model.get_iter(path_dest) + iter_source = treeview.get_selection().get_selected()[1] + path_source = model.get_path(iter_source) + if len(path_dest) == 1 and not merge: #droped on an account + return + if path_dest[0] != path_source[0] and not merge: + #droped in another account + return + iter_group_source = model.iter_parent(iter_source) + grp_source = model.get_value(iter_group_source, 3) + if grp_source == 'Transports' or grp_source == 'not in the roster': + return + account = model.get_value(iter_dest, 4) + type_dest = model.get_value(iter_dest, 2) + if type_dest == 'group': + grp_dest = model.get_value(iter_dest, 3) + else: + grp_dest = model.get_value(model.iter_parent(iter_dest), 3) + if grp_source == grp_dest: + return + # We upgrade only the first user because user2.groups is a pointer to + # user1.groups + u = self.contacts[account][data][0] + u.groups.remove(grp_source) + if model.iter_n_children(iter_group_source) == 1: #this was the only child + model.remove(iter_group_source) + #delete the group if it is empty (need to look for offline users too) + group_empty = True + for jid in self.contacts[account]: + if grp_source in self.contacts[account][jid][0].groups: + group_empty = False + break + if group_empty: + del self.groups[account][grp_source] + if not grp_dest in u.groups: + u.groups.append(grp_dest) + self.add_user_to_roster(data, account) + gajim.connections[account].update_user(u.jid, u.name, u.groups) + if context.action == gtk.gdk.ACTION_MOVE: + context.finish(True, True, etime) + return + + def show_title(self): + change_title_allowed = gajim.config.get('change_roster_title') + if change_title_allowed: + start = '' + if self.nb_unread > 1: + start = '[' + str(self.nb_unread) + '] ' + elif self.nb_unread == 1: + start = '* ' + self.window.set_title(start + 'Gajim') + + def __init__(self, plugin): + self.xml = gtk.glade.XML(GTKGUI_GLADE, 'roster_window', APP) + self.window = self.xml.get_widget('roster_window') + self.tree = self.xml.get_widget('roster_treeview') + self.plugin = plugin + self.nb_unread = 0 + self.add_new_contact_handler_id = False + self.service_disco_handler_id = False + self.join_gc_handler_id = False + self.new_message_menuitem_handler_id = False + self.regroup = 0 + self.regroup = gajim.config.get('mergeaccounts') + if gajim.config.get('saveposition'): + self.window.move(gajim.config.get('x-position'), \ + gajim.config.get('y-position')) + self.window.resize(gajim.config.get('width'), \ + gajim.config.get('height')) + + if gajim.config.get('show_roster_on_startup'): + self.window.show_all() + else: + if not gajim.config.get('trayicon'): + # cannot happen via GUI, but I put this incase user touches config + self.window.show_all() # without trayicon, he should see the roster! + gajim.config.set('show_roster_on_startup', True) + + self.groups = {} + self.contacts = {} + self.newly_added = {} + self.to_be_removed = {} + self.popups_notification_height = 0 + self.popup_notification_windows = [] + self.gpg_passphrase = {} + for a in gajim.connections: + self.contacts[a] = {} + self.groups[a] = {} + self.newly_added[a] = [] + self.to_be_removed[a] = [] + #(icon, name, type, jid, account, editable) + model = gtk.TreeStore(gtk.Image, str, str, str, str, gobject.TYPE_BOOLEAN) + model.set_sort_func(1, self.compareIters) + model.set_sort_column_id(1, gtk.SORT_ASCENDING) + self.tree.set_model(model) + self.make_jabber_state_images() + self.transports_state_images = { 'aim': {}, 'gadugadu': {}, 'irc': {}, 'icq': {}, 'msn': {}, 'sms': {}, 'yahoo': {} } + + path = os.path.join(gajim.DATA_DIR, 'iconsets/transports') + folders = os.listdir(path) + for transport in folders: + if transport == '.svn': + continue + folder = os.path.join(path, transport) + self.transports_state_images[transport] = self.load_iconset(folder + \ + '/') + + liststore = gtk.ListStore(gobject.TYPE_STRING, gtk.Image, + gobject.TYPE_STRING) + self.status_combobox = gtk.ComboBox() + roster_vbox = self.xml.get_widget('roster_vbox') + roster_vbox.pack_end(self.status_combobox, False) + cell = cell_renderer_image.CellRendererImage() + self.status_combobox.pack_start(cell, False) + self.status_combobox.add_attribute(cell, 'image', 1) + cell = gtk.CellRendererText() + cell.set_property('xpad', 5) # padding for status text + self.status_combobox.pack_start(cell, True) + self.status_combobox.add_attribute(cell, 'text', 0) + + for show in ['online', 'chat', 'away', 'xa', 'dnd', 'invisible', + 'offline']: + uf_show = helpers.get_uf_show(show) + iter = liststore.append([uf_show, self.jabber_state_images[show], + show]) + self.status_combobox.show_all() + self.status_combobox.set_model(liststore) + self.status_combobox.set_active(6) # default to offline + + showOffline = gajim.config.get('showoffline') + self.xml.get_widget('show_offline_contacts_menuitem').set_active(showOffline) + + #columns + + #this col has two cells: first one img, second one text + col = gtk.TreeViewColumn() + render_pixbuf = cell_renderer_image.CellRendererImage() + col.pack_start(render_pixbuf, expand = False) + col.add_attribute(render_pixbuf, 'image', 0) + col.set_cell_data_func(render_pixbuf, self.iconCellDataFunc, None) + + render_text = gtk.CellRendererText() + render_text.connect('edited', self.on_cell_edited) + render_text.connect('editing-canceled', self.on_editing_canceled) + col.pack_start(render_text, expand = True) + col.add_attribute(render_text, 'text', 1) + col.add_attribute(render_text, 'editable', 5) + col.set_cell_data_func(render_text, self.nameCellDataFunc, None) + self.tree.append_column(col) + + #do not show gtk arrows workaround + col = gtk.TreeViewColumn() + render_pixbuf = gtk.CellRendererPixbuf() + col.pack_start(render_pixbuf, expand = False) + self.tree.append_column(col) + col.set_visible(False) + self.tree.set_expander_column(col) + + #signals + TARGETS = [('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0)] + self.tree.enable_model_drag_source( gtk.gdk.BUTTON1_MASK, TARGETS, + gtk.gdk.ACTION_DEFAULT| gtk.gdk.ACTION_MOVE) + self.tree.enable_model_drag_dest(TARGETS, gtk.gdk.ACTION_DEFAULT) + self.tree.connect('drag_data_get', self.drag_data_get_data) + self.tree.connect('drag_data_received', self.drag_data_received_data) + self.xml.signal_autoconnect(self) + self.id_signal_cb = self.status_combobox.connect('changed', + self.on_status_combobox_changed) + + self.collapsed_rows = gajim.config.get('collapsed_rows').split('\t') + self.draw_roster() + if len(gajim.connections) == 0: # if no account + self.plugin.windows['account_modification'] = \ + config.Account_modification_window(self.plugin)