diff --git a/plugins/gtkgui/groupchat_window.py b/plugins/gtkgui/groupchat_window.py new file mode 100644 index 000000000..58fab56b4 --- /dev/null +++ b/plugins/gtkgui/groupchat_window.py @@ -0,0 +1,698 @@ +## plugins/groupchat_window.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - Nikos Kouremenos +## - Alex Podaras +## +## 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 time +import sre #usefull later + +from dialogs import * + +from common import i18n + +_ = i18n._ +APP = i18n.APP +gtk.glade.bindtextdomain(APP, i18n.DIR) +gtk.glade.textdomain(APP) + +GTKGUI_GLADE='plugins/gtkgui/gtkgui.glade' + +class Groupchat_window: + def on_groupchat_window_delete_event(self, widget, event): + """close window""" + for room_jid in self.xmls: + if time.time() - self.last_message_time[room_jid] < 2: + dialog = Confirmation_dialog(_('You received a message in the room %s in the last two seconds.\nDo you still want to close this window ?') % \ + room_jid.split('@')[0]) + if dialog.get_response() != gtk.RESPONSE_YES: + return True #stop the propagation of the event + + def on_groupchat_window_destroy(self, widget): + for room_jid in self.xmls: + self.plugin.send('GC_STATUS', self.account, (self.nicks[room_jid], \ + room_jid, 'offline', 'offline')) + del self.plugin.windows[self.account]['gc'][room_jid] + if self.plugin.windows[self.account]['gc'].has_key('tabbed'): + del self.plugin.windows[self.account]['gc']['tabbed'] + + def update_tags(self): + for room_jid in self.tagIn: + self.tagIn[room_jid].set_property('foreground', \ + self.plugin.config['inmsgcolor']) + self.tagInBold[room_jid].set_property('foreground', \ + self.plugin.config['inmsgcolor']) + self.tagOut[room_jid].set_property('foreground', \ + self.plugin.config['outmsgcolor']) + self.tagStatus[room_jid].set_property('foreground', \ + self.plugin.config['statusmsgcolor']) + + def get_role_iter(self, room_jid, role): + model = self.list_treeview[room_jid].get_model() + fin = False + iter = model.get_iter_root() + if not iter: + return None + while not fin: + role_name = model.get_value(iter, 2) + if role == role_name: + return iter + iter = model.iter_next(iter) + if not iter: + fin = True + return None + + def get_user_iter(self, room_jid, nick): + model = self.list_treeview[room_jid].get_model() + fin = False + role_iter = model.get_iter_root() + if not role_iter: + return None + while not fin: + fin2 = False + user_iter = model.iter_children(role_iter) + if not user_iter: + fin2=True + while not fin2: + if nick == model.get_value(user, 1): + return user_iter + user_iter = model.iter_next(user_iter) + if not user_iter: + fin2 = True + role_iter = model.iter_next(role_iter) + if not role_iter: + fin = True + return None + + def get_nick_list(self, room_jid): + model = self.list_treeview[room_jid].get_model() + list = [] + fin = False + role = model.get_iter_root() + if not role: + return list + while not fin: + fin2 = False + user = model.iter_children(role) + if not user: + fin2=True + while not fin2: + nick = model.get_value(user, 1) + list.append(nick) + user = model.iter_next(user) + if not user: + fin2 = True + role = model.iter_next(role) + if not role: + fin = True + return list + + def remove_user(self, room_jid, nick): + """Remove a user from the roster""" + model = self.list_treeview[room_jid].get_model() + iter = self.get_user_iter(room_jid, nick) + if not iter: + return + parent_iter = model.iter_parent(iter) + model.remove(iter) + if model.iter_n_children(parent_iter) == 0: + model.remove(parent_iter) + + def add_user_to_roster(self, room_jid, nick, show, role, jid): + model = self.list_treeview[room_jid].get_model() + img = self.plugin.roster.pixbufs[show] + role_iter = self.get_role_iter(room_jid, role) + if not role_iter: + role_iter = model.append(None, (self.plugin.roster.pixbufs['closed']\ + , role + 's', role)) + iter = model.append(role_iter, (img, nick, jid)) + self.list_treeview[room_jid].expand_row((model.get_path(role_iter)), \ + False) + return iter + + def get_role(self, room_jid, jid_iter): + model = self.list_treeview[room_jid].get_model() + path = model.get_path(jid_iter)[0] + iter = model.get_iter(path) + return model.get_value(iter, 2) + + def chg_user_status(self, room_jid, nick, show, status, role, affiliation, \ + jid, reason, actor, statusCode, account): + """When a user change his status""" + model = self.list_treeview[room_jid].get_model() + if show == 'offline' or show == 'error': + if statusCode == '307': + self.print_conversation(_('%s has been kicked by %s: %s') % (nick, \ + jid, actor, reason)) + self.remove_user(room_jid, nick) + else: + iter = self.get_user_iter(room_jid, nick) + ji = jid + if jid: + ji = jid.split('/')[0] + if not iter: + iter = self.add_user_to_roster(room_jid, nick, show, role, ji) + else: + actual_role = self.get_role(room_jid, iter) + if role != actual_role: + self.remove_user(room_jid, nick) + self.add_user_to_roster(room_jid, nick, show, role, ji) + else: + img = self.plugin.roster.pixbufs[show] + model.set_value(iter, 0, img) + + def show_title(self): + """redraw the window's title""" + unread = 0 + for room_jid in self.nb_unread: + unread += self.nb_unread[room_jid] + start = "" + if unread > 1: + start = "[" + str(unread) + "] " + elif unread == 1: + start = "* " + chat = 'Groupchat in ' + room_jid + if len(self.xmls) > 1: + chat = 'Groupchat' + self.window.set_title(start + chat + ' (' + self.account + ')') + + def redraw_tab(self, room_jid): + """redraw the label of the tab""" + start = '' + if self.nb_unread[room_jid] > 1: + start = "[" + str(self.nb_unread[room_jid]) + "] " + elif self.nb_unread[room_jid] == 1: + start = "* " + room = room_jid.split('@')[0] + child = self.xmls[room_jid].get_widget('group_vbox') + self.chat_notebook.set_tab_label_text(child, start + room) + + def get_active_jid(self): + active_child = self.chat_notebook.get_nth_page(\ + self.chat_notebook.get_current_page()) + active_jid = '' + for room_jid in self.xmls: + child = self.xmls[room_jid].get_widget('group_vbox') + if child == active_child: + active_jid = room_jid + break + return active_jid + + def set_subject(self, room_jid, subject): + self.subjects[room_jid] = subject + self.xml.get_widget('subject_entry').set_text(subject) + + def on_set_button_clicked(self, widget): + room_jid = self.get_active_jid() + subject = self.xml.get_widget('subject_entry').get_text() + self.plugin.send('GC_SUBJECT', self.account, (room_jid, subject)) + + def on_close_button_clicked(self, button): + room_jid = self.get_active_jid() + self.remove_tab(room_jid) + + def on_message_textview_key_press_event(self, widget, event): + """When a key is pressed: + if enter is pressed without the shit key, message (if not empty) is sent + and printed in the conversation. Tab does autocompete in nickames""" + if event.keyval == gtk.keysyms.Return: # ENTER + if (event.state & gtk.gdk.SHIFT_MASK): + return 0 + message_buffer = widget.get_buffer() + start_iter = message_buffer.get_start_iter() + end_iter = message_buffer.get_end_iter() + txt = message_buffer.get_text(start_iter, end_iter, 0) + if txt != '': + room_jid = self.get_active_jid() + self.plugin.send('GC_MSG', self.account, (room_jid, txt)) + message_buffer.set_text('', -1) + widget.grab_focus() + return 1 + elif event.keyval == gtk.keysyms.Tab: # TAB + room_jid = self.get_active_jid() + list_nick = self.get_nick_list(room_jid) + message_buffer = widget.get_buffer() + start_iter = message_buffer.get_start_iter() + cursor_position = message_buffer.get_insert() + end_iter = message_buffer.get_iter_at_mark(cursor_position) + text = message_buffer.get_text(start_iter, end_iter, 0) + if not text: + return 0 + splited_text = text.split() + begin = splited_text[-1] + for nick in list_nick: + if nick.find(begin) == 0: + if len(splited_text) == 1: + add = ': ' + else: + add = ' ' + message_buffer.insert_at_cursor(nick[len(begin):] + add) + return 1 + return 0 + + def on_groupchat_window_key_press_event(self, widget, event): + st = "1234567890" # humans count from 1, pc from 0 :P + room_jid = self.get_active_jid() + if event.keyval == gtk.keysyms.Escape: #ESCAPE + self.remove_tab(room_jid) + elif event.string and event.string in st \ + and (event.state & gtk.gdk.MOD1_MASK): # alt + 1,2,3 ... + self.chat_notebook.set_current_page(st.index(event.string)) + elif event.keyval == gtk.keysyms.Page_Down: # PAGEDOWN + if event.state & gtk.gdk.CONTROL_MASK: + current = self.chat_notebook.get_current_page() + if current > 0: + self.chat_notebook.set_current_page(current-1) +# else: +# self.chat_notebook.set_current_page(\ +# self.chat_notebook.get_n_pages()-1) + elif event.state & gtk.gdk.SHIFT_MASK: + conversation_textview = self.xmls[room_jid].\ + get_widget('conversation_textview') + rect = conversation_textview.get_visible_rect() + iter = conversation_textview.get_iter_at_location(rect.x,\ + rect.y + rect.height) + conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 0) + elif event.keyval == gtk.keysyms.Page_Up: # PAGEUP + if event.state & gtk.gdk.CONTROL_MASK: + current = self.chat_notebook.get_current_page() + if current < (self.chat_notebook.get_n_pages()-1): + self.chat_notebook.set_current_page(current+1) +# else: +# self.chat_notebook.set_current_page(0) + elif event.state & gtk.gdk.SHIFT_MASK: + conversation_textview = self.xmls[room_jid].\ + get_widget('conversation_textview') + rect = conversation_textview.get_visible_rect() + iter = conversation_textview.get_iter_at_location(rect.x, rect.y) + conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 1) + elif event.keyval == gtk.keysyms.Tab and \ + (event.state & gtk.gdk.CONTROL_MASK): # CTRL+TAB + current = self.chat_notebook.get_current_page() + if current < (self.chat_notebook.get_n_pages()-1): + self.chat_notebook.set_current_page(current+1) + else: + self.chat_notebook.set_current_page(0) + + '''FIXME: + NOT GOOD steals focus from Subject entry and I cannot find a way to prevent this + + else: # it's a normal key press make sure message_textview has focus + message_textview = self.xmls[room_jid].get_widget('message_textview') + if not message_textview.is_focus(): + message_textview.grab_focus() + ''' + + def print_conversation(self, text, room_jid, contact = '', tim = None): + """Print a line in the conversation : + if contact is set : it's a message from someone + if contact is not set : it's a message from the server""" + conversation_textview = self.xmls[room_jid].\ + get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + if not text: + text = '' + end_iter = conversation_buffer.get_end_iter() + if self.plugin.config['print_time'] == 'always': + if not tim: + tim = time.localtime() + tim_format = time.strftime('[%H:%M:%S]', tim) + conversation_buffer.insert(end_iter, tim_format + ' ') + + otext = '' + ttext = '' + if contact: + if contact == self.nicks[room_jid]: + tag = 'outgoing' + else: + if self.nicks[room_jid].lower() in text.lower().split(): + tag = 'incoming_bold' + else: + tag = 'incoming' + self.last_message_time[room_jid] = time.time() + + if text.startswith('/me'): + ttext = contact + text[3:] + '\n' + else: + ttext = '<' + contact + '> ' + otext = text + '\n' + else: + tag = 'status' + ttext = text + '\n' + + conversation_buffer.insert_with_tags_by_name(end_iter, ttext, tag) + #TODO: emoticons, url grabber + conversation_buffer.insert(end_iter, otext) + #scroll to the end of the textview + conversation_textview.scroll_to_mark(conversation_buffer.get_mark('end'),\ + 0.1, 0, 0, 0) + if not self.window.is_active() and contact != '': + self.nb_unread[room_jid] += 1 + self.redraw_tab(room_jid) + self.show_title() + + def kick(self, widget, room_jid, nick): + """kick a user""" + self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, 'none')) + + def grant_voice(self, widget, room_jid, nick): + """grant voice privilege to a user""" + self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, \ + 'participant')) + + def revoke_voice(self, widget, room_jid, nick): + """revoke voice privilege to a user""" + self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, 'visitor')) + + def grant_moderator(self, widget, room_jid, nick): + """grant moderator privilege to a user""" + self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick,\ + 'moderator')) + + def revoke_moderator(self, widget, room_jid, nick): + """revoke moderator privilege to a user""" + self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, \ + 'participant')) + + def ban(self, widget, room_jid, jid): + """ban a user""" + self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ + 'outcast')) + + def grant_membership(self, widget, room_jid, jid): + """grant membership privilege to a user""" + self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ + 'member')) + + def revoke_membership(self, widget, room_jid, jid): + """revoke membership privilege to a user""" + self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ + 'none')) + + def grant_admin(self, widget, room_jid, jid): + """grant administrative privilege to a user""" + self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ + 'admin')) + + def revoke_admin(self, widget, room_jid, jid): + """revoke administrative privilege to a user""" + self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ + 'member')) + + def grant_owner(self, widget, room_jid, jid): + """grant owner privilege to a user""" + self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ + 'owner')) + + def revoke_owner(self, widget, room_jid, jid): + """revoke owner privilege to a user""" + self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ + 'admin')) + + def on_info(self, widget, jid): + """Call vcard_information_window class to display user's information""" + if not self.plugin.windows[self.account]['infos'].has_key(jid): + self.plugin.windows[self.account]['infos'][jid] = \ + vcard_information_window(jid, self.plugin, self.account, True) + self.plugin.send('ASK_VCARD', self.account, jid) + + def mk_menu(self, room_jid, event, iter): + """Make user's popup menu""" + model = self.list_treeview[room_jid].get_model() + nick = model.get_value(iter, 1) + jid = model.get_value(iter, 2) + + menu = gtk.Menu() + item = gtk.MenuItem(_('Privileges')) + menu.append(item) + + sub_menu = gtk.Menu() + item.set_submenu(sub_menu) + item = gtk.MenuItem(_('Kick')) + sub_menu.append(item) + item.connect('activate', self.kick, room_jid, nick) + item = gtk.MenuItem(_('Grant voice')) + sub_menu.append(item) + item.connect('activate', self.grant_voice, room_jid, nick) + item = gtk.MenuItem(_('Revoke voice')) + sub_menu.append(item) + item.connect('activate', self.revoke_voice, room_jid, nick) + item = gtk.MenuItem(_('Grant moderator')) + sub_menu.append(item) + item.connect('activate', self.grant_moderator, room_jid, nick) + item = gtk.MenuItem(_('Revoke moderator')) + sub_menu.append(item) + item.connect('activate', self.revoke_moderator, room_jid, nick) + if jid: + item = gtk.MenuItem() + sub_menu.append(item) + + item = gtk.MenuItem(_('Ban')) + sub_menu.append(item) + item.connect('activate', self.ban, room_jid, jid) + item = gtk.MenuItem(_('Grant membership')) + sub_menu.append(item) + item.connect('activate', self.grant_membership, room_jid, jid) + item = gtk.MenuItem(_('Revoke membership')) + sub_menu.append(item) + item.connect('activate', self.revoke_membership, room_jid, jid) + item = gtk.MenuItem(_('Grant admin')) + sub_menu.append(item) + item.connect('activate', self.grant_admin, room_jid, jid) + item = gtk.MenuItem(_('Revoke admin')) + sub_menu.append(item) + item.connect('activate', self.revoke_admin, room_jid, jid) + item = gtk.MenuItem(_('Grant owner')) + sub_menu.append(item) + item.connect('activate', self.grant_owner, room_jid, jid) + item = gtk.MenuItem(_('Revoke owner')) + sub_menu.append(item) + item.connect('activate', self.revoke_owner, room_jid, jid) + + item = gtk.MenuItem() + menu.append(item) + + item = gtk.MenuItem(_('Information')) + menu.append(item) + item.connect('activate', self.on_info, jid) + + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def on_groupchat_window_focus_in_event(self, widget, event): + """When window get focus""" + room_jid = self.get_active_jid() + if self.nb_unread[room_jid] > 0: + self.nb_unread[room_jid] = 0 + self.redraw_tab(room_jid) + self.show_title() + self.plugin.systray.remove_jid(room_jid, self.account) + + def on_chat_notebook_switch_page(self, notebook, page, page_num): + new_child = notebook.get_nth_page(page_num) + new_jid = '' + for room_jid in self.xmls: + child = self.xmls[room_jid].get_widget('group_vbox') + if child == new_child: + new_jid = room_jid + break + self.xml.get_widget('subject_entry').set_text(self.subjects[new_jid]) + if self.nb_unread[new_jid] > 0: + self.nb_unread[new_jid] = 0 + self.redraw_tab(new_jid) + self.show_title() + self.plugin.systray.remove_jid(new_jid, self.account) + + def active_tab(self, room_jid): + child = self.xmls[room_jid].get_widget('group_vbox') + self.chat_notebook.set_current_page(self.chat_notebook.page_num(child)) + self.xmls[room_jid].get_widget('message_textview').grab_focus() + + def remove_tab(self, room_jid): + if time.time() - self.last_message_time[room_jid] < 2: + dialog = Confirmation_dialog(_('You received a message in the room %s in the last two seconds.\nDo you still want to close this tab ?') % \ + room_jid.split('@')[0]) + if dialog.get_response() != gtk.RESPONSE_YES: + return + if len(self.xmls) == 1: + self.window.destroy() + else: + self.plugin.send('GC_STATUS', self.account, (self.nicks[room_jid], \ + room_jid, 'offline', 'offline')) + self.chat_notebook.remove_page(self.chat_notebook.get_current_page()) + del self.plugin.windows[self.account]['gc'][room_jid] + del self.nicks[room_jid] + del self.nb_unread[room_jid] + del self.last_message_time[room_jid] + del self.xmls[room_jid] + del self.tagIn[room_jid] + del self.tagInBold[room_jid] + del self.tagOut[room_jid] + del self.tagStatus[room_jid] + del self.list_treeview[room_jid] + del self.subjects[room_jid] + if len(self.xmls) == 1: + self.chat_notebook.set_show_tabs(False) + self.show_title() + + def new_group(self, room_jid, nick): + self.nb_unread[room_jid] = 0 + self.last_message_time[room_jid] = 0 + self.nicks[room_jid] = nick + self.subjects[room_jid] = '' + self.xmls[room_jid] = gtk.glade.XML(GTKGUI_GLADE, 'group_vbox', APP) + self.list_treeview[room_jid] = self.xmls[room_jid].\ + get_widget('list_treeview') + + #status_image, nickname, real_jid + store = gtk.TreeStore(gtk.Image, str, str) + column = gtk.TreeViewColumn('contacts') + render_text = ImageCellRenderer() + column.pack_start(render_text, expand = False) + column.add_attribute(render_text, 'image', 0) + render_text = gtk.CellRendererText() + column.pack_start(render_text, expand = True) + column.add_attribute(render_text, 'text', 1) + + self.list_treeview[room_jid].append_column(column) + self.list_treeview[room_jid].set_model(store) + + column = gtk.TreeViewColumn() + render = gtk.CellRendererPixbuf() + column.pack_start(render, expand = False) + self.list_treeview[room_jid].append_column(column) + column.set_visible(False) + self.list_treeview[room_jid].set_expander_column(column) + + conversation_textview = self.xmls[room_jid].\ + get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + end_iter = conversation_buffer.get_end_iter() + conversation_buffer.create_mark('end', end_iter, 0) + self.tagIn[room_jid] = conversation_buffer.create_tag('incoming') + # (nk) what is this? + self.tagInBold[room_jid] = conversation_buffer.create_tag('incoming_bold') + color = self.plugin.config['inmsgcolor'] + self.tagIn[room_jid].set_property('foreground', color) + self.tagInBold[room_jid].set_property('foreground', color) + self.tagInBold[room_jid].set_property('weight', 700) + self.tagOut[room_jid] = conversation_buffer.create_tag('outgoing') + color = self.plugin.config['outmsgcolor'] + self.tagOut[room_jid].set_property('foreground', color) + self.tagStatus[room_jid] = conversation_buffer.create_tag('status') + color = self.plugin.config['statusmsgcolor'] + self.tagStatus[room_jid].set_property('foreground', color) + + self.xmls[room_jid].signal_autoconnect(self) + + self.chat_notebook.append_page(self.xmls[room_jid].\ + get_widget('group_vbox')) + if len(self.xmls) > 1: + self.chat_notebook.set_show_tabs(True) + + self.redraw_tab(room_jid) + self.show_title() + + def on_list_treeview_button_press_event(self, widget, event): + """popup user's group's or agent menu""" + if event.type == gtk.gdk.BUTTON_PRESS: + if event.button == 3: + try: + path, column, x, y = widget.get_path_at_pos(int(event.x), \ + int(event.y)) + except TypeError: + widget.get_selection().unselect_all() + return False + model = widget.get_model() + iter = model.get_iter(path) + if len(path) == 2: + room_jid = self.get_active_jid() + self.mk_menu(room_jid, event, iter) + return True + if event.button == 1: + try: + path, column, x, y = widget.get_path_at_pos(int(event.x), \ + int(event.y)) + except TypeError: + widget.get_selection().unselect_all() + return False + + def on_list_treeview_key_release_event(self, widget, event): + if event.type == gtk.gdk.KEY_RELEASE: + if event.keyval == gtk.keysyms.Escape: + widget.get_selection().unselect_all() + return False + + def on_list_treeview_row_activated(self, widget, path, col=0): + """When an iter is dubble clicked : + open the chat window""" + model = widget.get_model() + iter = model.get_iter(path) + if len(path) == 1: + if (widget.row_expanded(path)): + widget.collapse_row(path) + else: + widget.expand_row(path, False) + + def on_list_treeview_row_expanded(self, widget, iter, path): + """When a row is expanded : + change the icon of the arrow""" + model = widget.get_model() + model.set_value(iter, 0, self.plugin.roster.pixbufs['opened']) + + def on_list_treeview_row_collapsed(self, widget, iter, path): + """When a row is collapsed : + change the icon of the arrow""" + model = widget.get_model() + model.set_value(iter, 0, self.plugin.roster.pixbufs['closed']) + + def __init__(self, room_jid, nick, plugin, account): + self.xml = gtk.glade.XML(GTKGUI_GLADE, 'groupchat_window', APP) + self.chat_notebook = self.xml.get_widget('chat_notebook') + self.chat_notebook.remove_page(0) + self.plugin = plugin + self.account = account + self.xmls = {} + self.tagIn = {} + self.tagInBold = {} + self.tagOut = {} + self.tagStatus = {} + self.nicks = {} + self.nb_unread = {} + self.last_message_time = {} + self.list_treeview = {} + self.subjects = {} + self.window = self.xml.get_widget('groupchat_window') + self.new_group(room_jid, nick) + self.show_title() + self.xml.signal_connect('on_groupchat_window_destroy', \ + self.on_groupchat_window_destroy) + self.xml.signal_connect('on_groupchat_window_delete_event', \ + self.on_groupchat_window_delete_event) + self.xml.signal_connect('on_groupchat_window_focus_in_event', \ + self.on_groupchat_window_focus_in_event) + self.xml.signal_connect('on_groupchat_window_key_press_event', \ + self.on_groupchat_window_key_press_event) + self.xml.signal_connect('on_chat_notebook_switch_page', \ + self.on_chat_notebook_switch_page) + self.xml.signal_connect('on_set_button_clicked', \ + self.on_set_button_clicked) diff --git a/plugins/gtkgui/gtkgui.glade b/plugins/gtkgui/gtkgui.glade index c40930d89..51cf72e5a 100644 --- a/plugins/gtkgui/gtkgui.glade +++ b/plugins/gtkgui/gtkgui.glade @@ -963,10 +963,9 @@ True GTK_RELIEF_NORMAL True - True + False False True - 0 diff --git a/plugins/gtkgui/gtkgui.py b/plugins/gtkgui/gtkgui.py index 5beeacfd6..be8337298 100644 --- a/plugins/gtkgui/gtkgui.py +++ b/plugins/gtkgui/gtkgui.py @@ -54,30 +54,9 @@ import gobject import os import time import sys -import sre import Queue -import common.optparser import common.sleepy -from common import i18n - -_ = i18n._ -APP = i18n.APP -gtk.glade.bindtextdomain(APP, i18n.DIR) -gtk.glade.textdomain(APP) - -from dialogs import * -from config import * - -def usage(): - #TODO: use i18n - print 'usage :', sys.argv[0], ' [OPTION]' - print ' -p\tport on which the sock plugin listen' - print ' -h, --help\tdisplay this help and exit' - - -GTKGUI_GLADE='plugins/gtkgui/gtkgui.glade' - class ImageCellRenderer(gtk.GenericCellRenderer): __gproperties__ = { @@ -172,7 +151,6 @@ class ImageCellRenderer(gtk.GenericCellRenderer): gobject.type_register(ImageCellRenderer) - class User: """Information concerning each users""" def __init__(self, *args): @@ -200,2862 +178,31 @@ class User: self.keyID = args[9] else: raise TypeError, _('bad arguments') -class tabbed_chat_window: - """Class for tabbed chat window""" - def __init__(self, user, plugin, account): - self.xml = gtk.glade.XML(GTKGUI_GLADE, 'tabbed_chat_window', APP) - self.chat_notebook = self.xml.get_widget('chat_notebook') - self.chat_notebook.remove_page(0) - self.plugin = plugin - self.account = account - self.change_cursor = None - self.xmls = {} - self.tagIn = {} - self.tagOut = {} - self.tagStatus = {} - self.users = {} - self.nb_unread = {} - self.last_message_time = {} - self.print_time_timeout_id = {} - self.window = self.xml.get_widget('tabbed_chat_window') - self.new_user(user) - self.show_title() - self.xml.signal_connect('on_tabbed_chat_window_destroy', \ - self.on_tabbed_chat_window_destroy) - self.xml.signal_connect('on_tabbed_chat_window_delete_event', \ - self.on_tabbed_chat_window_delete_event) - self.xml.signal_connect('on_tabbed_chat_window_focus_in_event', \ - self.on_tabbed_chat_window_focus_in_event) - self.xml.signal_connect('on_tabbed_chat_window_key_press_event', \ - self.on_tabbed_chat_window_key_press_event) - self.xml.signal_connect('on_chat_notebook_switch_page', \ - self.on_chat_notebook_switch_page) - - def update_tags(self): - for jid in self.tagIn: - self.tagIn[jid].set_property("foreground", \ - self.plugin.config['inmsgcolor']) - self.tagOut[jid].set_property("foreground", \ - self.plugin.config['outmsgcolor']) - self.tagStatus[jid].set_property("foreground", \ - self.plugin.config['statusmsgcolor']) +from tabbed_chat_window import * +from groupchat_window import * +from history_window import * +from roster_window import * +from systray import * +from dialogs import * +from config import * - def update_print_time(self): - if self.plugin.config['print_time'] != 'sometimes': - list_jid = self.print_time_timeout_id.keys() - for jid in list_jid: - gobject.source_remove(self.print_time_timeout_id[jid]) - del self.print_time_timeout_id[jid] - else: - for jid in self.xmls: - if not self.print_time_timeout_id.has_key(jid): - self.print_time_timeout(jid) - self.print_time_timeout_id[jid] = gobject.timeout_add(300000, \ - self.print_time_timeout, jid) +from common import i18n - def show_title(self): - """redraw the window's title""" - unread = 0 - for jid in self.nb_unread: - unread += self.nb_unread[jid] - start = "" - if unread > 1: - start = "[" + str(unread) + "] " - elif unread == 1: - start = "* " - chat = self.users[jid].name - if len(self.xmls) > 1: - chat = 'Chat' - self.window.set_title(start + chat + ' (' + self.account + ')') +_ = i18n._ +APP = i18n.APP +gtk.glade.bindtextdomain(APP, i18n.DIR) +gtk.glade.textdomain(APP) - def draw_widgets(self, user): - """draw the widgets in a tab (status_image, contact_button ...) - according to the the information in the user variable""" - jid = user.jid - status_image = self.xmls[jid].get_widget('status_image') - image = self.plugin.roster.pixbufs[user.show] - if image.get_storage_type() == gtk.IMAGE_ANIMATION: - status_image.set_from_animation(image.get_animation()) - elif image.get_storage_type() == gtk.IMAGE_PIXBUF: - status_image.set_from_pixbuf(image.get_pixbuf()) - contact_button = self.xmls[jid].get_widget('contact_button') - contact_button.set_label(user.name + ' <' + jid + '>') - if not user.keyID: - self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(False) +def usage(): + #TODO: use i18n + print 'usage :', sys.argv[0], ' [OPTION]' + print ' -p\tport on which the sock plugin listen' + print ' -h, --help\tdisplay this help and exit' - def redraw_tab(self, jid): - """redraw the label of the tab""" - start = '' - if self.nb_unread[jid] > 1: - start = "[" + str(self.nb_unread[jid]) + "] " - elif self.nb_unread[jid] == 1: - start = "* " - child = self.xmls[jid].get_widget('chat_vbox') - self.chat_notebook.set_tab_label_text(child, start + self.users[jid].name) - def set_image(self, image, jid): - if image.get_storage_type() == gtk.IMAGE_ANIMATION: - self.xmls[jid].get_widget('status_image').\ - set_from_animation(image.get_animation()) - elif image.get_storage_type() == gtk.IMAGE_PIXBUF: - self.xmls[jid].get_widget('status_image').\ - set_from_pixbuf(image.get_pixbuf()) +GTKGUI_GLADE='plugins/gtkgui/gtkgui.glade' - def on_tabbed_chat_window_delete_event(self, widget, event): - """close window""" - for jid in self.users: - if time.time() - self.last_message_time[jid] < 2: # 2 seconds - dialog = Confirmation_dialog(_('You received a message from %s in the last two seconds.\nDo you still want to close this window ?') % jid) - if dialog.get_response() != gtk.RESPONSE_YES: - return True #stop the propagation of the event - def on_tabbed_chat_window_destroy(self, widget): - #clean self.plugin.windows[self.account]['chats'] - for jid in self.users: - del self.plugin.windows[self.account]['chats'][jid] - if self.print_time_timeout_id.has_key(jid): - gobject.source_remove(self.print_time_timeout_id[jid]) - if self.plugin.windows[self.account]['chats'].has_key('tabbed'): - del self.plugin.windows[self.account]['chats']['tabbed'] - - def get_active_jid(self): - active_child = self.chat_notebook.get_nth_page(\ - self.chat_notebook.get_current_page()) - active_jid = '' - for jid in self.xmls: - child = self.xmls[jid].get_widget('chat_vbox') - if child == active_child: - active_jid = jid - break - return active_jid - - def on_clear_button_clicked(self, widget): - """When clear button is pressed : - clear the conversation""" - jid = self.get_active_jid() - conversation_buffer = self.xmls[jid].get_widget('conversation_textview').\ - get_buffer() - start, end = conversation_buffer.get_bounds() - conversation_buffer.delete(start, end) - - def on_close_button_clicked(self, button): - """When close button is pressed : - close a tab""" - jid = self.get_active_jid() - self.remove_tab(jid) - - def on_tabbed_chat_window_focus_in_event(self, widget, event): - """When window get focus""" - jid = self.get_active_jid() - conversation_textview = self.xmls[jid].\ - get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - end_iter = conversation_buffer.get_end_iter() - end_rect = conversation_textview.get_iter_location(end_iter) - visible_rect = conversation_textview.get_visible_rect() - if end_rect.y <= (visible_rect.y + visible_rect.height): - #we are at the end - if self.nb_unread[jid] > 0: - self.nb_unread[jid] = 0 - self.redraw_tab(jid) - self.show_title() - self.plugin.systray.remove_jid(jid, self.account) - - def on_history_button_clicked(self, widget): - """When history button is pressed : call history window""" - jid = self.get_active_jid() - if not self.plugin.windows['logs'].has_key(jid): - self.plugin.windows['logs'][jid] = history_window(self.plugin, jid) - - def on_chat_notebook_switch_page(self, notebook, page, page_num): - new_child = notebook.get_nth_page(page_num) - new_jid = '' - for jid in self.xmls: - child = self.xmls[jid].get_widget('chat_vbox') - if child == new_child: - new_jid = jid - break - conversation_textview = self.xmls[new_jid].\ - get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - end_iter = conversation_buffer.get_end_iter() - end_rect = conversation_textview.get_iter_location(end_iter) - visible_rect = conversation_textview.get_visible_rect() - if end_rect.y <= (visible_rect.y + visible_rect.height): - #we are at the end - if self.nb_unread[new_jid] > 0: - self.nb_unread[new_jid] = 0 - self.redraw_tab(new_jid) - self.show_title() - self.plugin.systray.remove_jid(new_jid, self.account) - - def active_tab(self, jid): - child = self.xmls[jid].get_widget('chat_vbox') - self.chat_notebook.set_current_page(\ - self.chat_notebook.page_num(child)) - - def remove_tab(self, jid): - if time.time() - self.last_message_time[jid] < 2: - dialog = Confirmation_dialog(_('You received a message from %s in the last two seconds.\nDo you still want to close this tab ?') % jid) - if dialog.get_response() != gtk.RESPONSE_YES: - return - - if len(self.xmls) == 1: - self.window.destroy() - else: - if self.print_time_timeout_id.has_key(jid): - gobject.source_remove(self.print_time_timeout_id[jid]) - del self.print_time_timeout_id[jid] - self.chat_notebook.remove_page(\ - self.chat_notebook.get_current_page()) - del self.plugin.windows[self.account]['chats'][jid] - del self.users[jid] - del self.nb_unread[jid] - del self.last_message_time[jid] - del self.xmls[jid] - del self.tagIn[jid] - del self.tagOut[jid] - del self.tagStatus[jid] - if len(self.xmls) == 1: - self.chat_notebook.set_show_tabs(False) - self.show_title() - - def new_user(self, user): - self.nb_unread[user.jid] = 0 - self.last_message_time[user.jid] = 0 - self.users[user.jid] = user - self.xmls[user.jid] = gtk.glade.XML(GTKGUI_GLADE, 'chat_vbox', APP) - - conversation_textview = \ - self.xmls[user.jid].get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - end_iter = conversation_buffer.get_end_iter() - conversation_buffer.create_mark('end', end_iter, 0) - self.tagIn[user.jid] = conversation_buffer.create_tag('incoming') - color = self.plugin.config['inmsgcolor'] - self.tagIn[user.jid].set_property('foreground', color) - self.tagOut[user.jid] = conversation_buffer.create_tag('outgoing') - color = self.plugin.config['outmsgcolor'] - self.tagOut[user.jid].set_property('foreground', color) - self.tagStatus[user.jid] = conversation_buffer.create_tag('status') - color = self.plugin.config['statusmsgcolor'] - self.tagStatus[user.jid].set_property('foreground', color) - - tag = conversation_buffer.create_tag('time_sometimes') - tag.set_property('foreground', '#9e9e9e') - tag.set_property('scale', pango.SCALE_SMALL) - tag.set_property('justification', gtk.JUSTIFY_CENTER) - - tag = conversation_buffer.create_tag('url') - tag.set_property('foreground', '#0000ff') - tag.set_property('underline', pango.UNDERLINE_SINGLE) - tag.connect('event', self.hyperlink_handler, 'url') - - tag = conversation_buffer.create_tag('mail') - tag.set_property('foreground', '#0000ff') - tag.set_property('underline', pango.UNDERLINE_SINGLE) - tag.connect('event', self.hyperlink_handler, 'mail') - - tag = conversation_buffer.create_tag('bold') - tag.set_property('weight', pango.WEIGHT_BOLD) - - tag = conversation_buffer.create_tag('italic') - tag.set_property('style', pango.STYLE_ITALIC) - - tag = conversation_buffer.create_tag('underline') - tag.set_property('underline', pango.UNDERLINE_SINGLE) - - self.xmls[user.jid].signal_autoconnect(self) - conversation_scrolledwindow = self.xmls[user.jid].\ - get_widget('conversation_scrolledwindow') - conversation_scrolledwindow.get_vadjustment().connect('value-changed', \ - self.on_conversation_vadjustment_value_changed) - - self.chat_notebook.append_page(self.xmls[user.jid].\ - get_widget('chat_vbox')) - if len(self.xmls) > 1: - self.chat_notebook.set_show_tabs(True) - - self.redraw_tab(user.jid) - self.draw_widgets(user) - self.show_title() - - #print queued messages - if self.plugin.queues[self.account].has_key(user.jid): - self.read_queue(user.jid) - if user.show != 'online': - self.print_conversation(_("%s is now %s (%s)") % (user.name, \ - user.show, user.status), user.jid, 'status') - - if self.plugin.config['print_time'] == 'sometimes': - self.print_time_timeout(user.jid) - self.print_time_timeout_id[user.jid] = gobject.timeout_add(300000, \ - self.print_time_timeout, user.jid) - - def on_message_textview_key_press_event(self, widget, event): - """When a key is pressed : - if enter is pressed without the shit key, message (if not empty) is sent - and printed in the conversation""" - if event.keyval == gtk.keysyms.Return: - if (event.state & gtk.gdk.SHIFT_MASK): - return 0 - message_buffer = widget.get_buffer() - start_iter = message_buffer.get_start_iter() - end_iter = message_buffer.get_end_iter() - message = message_buffer.get_text(start_iter, end_iter, 0) - if message != '': - keyID = '' - jid = self.get_active_jid() - if self.xmls[jid].get_widget('gpg_togglebutton').get_active(): - keyID = self.users[jid].keyID - self.plugin.send('MSG', self.account, (jid, message, keyID)) - message_buffer.set_text('', -1) - self.print_conversation(message, jid, jid) - return 1 - return 0 - - def on_tabbed_chat_window_key_press_event(self, widget, event): - st = '1234567890' # zero is here cause humans count from 1, pc from 0 :P - jid = self.get_active_jid() - if event.keyval == gtk.keysyms.Escape: # ESCAPE - self.remove_tab(jid) - elif event.string and event.string in st \ - and (event.state & gtk.gdk.MOD1_MASK): # alt + 1,2,3.. - self.chat_notebook.set_current_page(st.index(event.string)) - elif event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN - if event.state & gtk.gdk.CONTROL_MASK: - current = self.chat_notebook.get_current_page() - if current > 0: - self.chat_notebook.set_current_page(current-1) -# else: -# self.chat_notebook.set_current_page(\ -# self.chat_notebook.get_n_pages()-1) - elif event.state & gtk.gdk.SHIFT_MASK: - conversation_textview = self.xmls[jid].\ - get_widget('conversation_textview') - rect = conversation_textview.get_visible_rect() - iter = conversation_textview.get_iter_at_location(rect.x,\ - rect.y + rect.height) - conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 0) - elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP - if event.state & gtk.gdk.CONTROL_MASK: - current = self.chat_notebook.get_current_page() - if current < (self.chat_notebook.get_n_pages()-1): - self.chat_notebook.set_current_page(current+1) -# else: -# self.chat_notebook.set_current_page(0) - elif event.state & gtk.gdk.SHIFT_MASK: - conversation_textview = self.xmls[jid].\ - get_widget('conversation_textview') - rect = conversation_textview.get_visible_rect() - iter = conversation_textview.get_iter_at_location(rect.x, rect.y) - conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 1) - elif event.keyval == gtk.keysyms.Tab and \ - (event.state & gtk.gdk.CONTROL_MASK): # CTRL + TAB - current = self.chat_notebook.get_current_page() - if current < (self.chat_notebook.get_n_pages()-1): - self.chat_notebook.set_current_page(current+1) - else: - self.chat_notebook.set_current_page(0) - elif (event.state & gtk.gdk.CONTROL_MASK) or (event.keyval ==\ - gtk.keysyms.Control_L) or (event.keyval == gtk.keysyms.Control_R): - # we pressed a control key or ctrl+sth : we don't block the event - # in order to let ctrl+c do its work - pass - else: # it's a normal key press make sure message_textview has focus - message_textview = self.xmls[jid].get_widget('message_textview') - if not message_textview.is_focus(): - message_textview.grab_focus() - - def on_contact_button_clicked(self, widget): - """When button contact is clicked""" - jid = self.get_active_jid() - user = self.users[jid] - self.plugin.roster.on_info(widget, user, self.account) - - def read_queue(self, jid): - """read queue and print messages containted in it""" - q = self.plugin.queues[self.account][jid] - user = self.users[jid] - while not q.empty(): - event = q.get() - self.print_conversation(event[0], jid, tim = event[1]) - self.plugin.roster.nb_unread -= 1 - self.plugin.roster.show_title() - del self.plugin.queues[self.account][jid] - self.plugin.roster.redraw_jid(jid, self.account) - self.plugin.systray.remove_jid(jid, self.account) - showOffline = self.plugin.config['showoffline'] - if (user.show == 'offline' or user.show == 'error') and \ - not showOffline: - if len(self.plugin.roster.contacts[self.account][jid]) == 1: - self.plugin.roster.remove_user(user, self.account) - - def on_conversation_vadjustment_value_changed(self, widget): - jid = self.get_active_jid() - if not self.nb_unread[jid]: - return - conversation_textview = self.xmls[jid].get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - end_iter = conversation_buffer.get_end_iter() - end_rect = conversation_textview.get_iter_location(end_iter) - visible_rect = conversation_textview.get_visible_rect() - if end_rect.y <= (visible_rect.y + visible_rect.height) and \ - self.window.is_active(): - #we are at the end - self.nb_unread[jid] = 0 - self.redraw_tab(jid) - self.show_title() - self.plugin.systray.remove_jid(jid, self.account) - - def on_conversation_textview_motion_notify_event(self, widget, event): - """change the cursor to a hand when we are on a mail or an url""" - x, y, spam = widget.window.get_pointer() - x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) - tags = widget.get_iter_at_location(x, y).get_tags() - if self.change_cursor: - widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(\ - gtk.gdk.Cursor(gtk.gdk.XTERM)) - self.change_cursor = None - for tag in tags: - if tag == widget.get_buffer().get_tag_table().lookup('url') or \ - tag == widget.get_buffer().get_tag_table().lookup('mail'): - widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(\ - gtk.gdk.Cursor(gtk.gdk.HAND2)) - self.change_cursor = tag - return False - - def on_conversation_textview_button_press_event(self, widget, event): - # Do not open the standard popup menu, so we block right button click - # on a taged text - if event.button == 3: - win = widget.get_window(gtk.TEXT_WINDOW_TEXT) - x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,\ - int(event.x), int(event.y)) - iter = widget.get_iter_at_location(x, y) - tags = iter.get_tags() - if tags: - return True - - def print_time_timeout(self, jid): - if not jid in self.xmls.keys(): - return 0 - if self.plugin.config['print_time'] == 'sometimes': - conversation_textview = self.xmls[jid].\ - get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - end_iter = conversation_buffer.get_end_iter() - tim = time.localtime() - tim_format = time.strftime('%H:%M', tim) - conversation_buffer.insert_with_tags_by_name(end_iter, tim_format + \ - '\n', 'time_sometimes') - #scroll to the end of the textview - end_rect = conversation_textview.get_iter_location(end_iter) - visible_rect = conversation_textview.get_visible_rect() - if end_rect.y <= (visible_rect.y + visible_rect.height): - #we are at the end - conversation_textview.scroll_to_mark(conversation_buffer.\ - get_mark('end'), 0.1, 0, 0, 0) - return 1 - if self.print_time_timeout_id.has_key(jid): - del self.print_time_timeout_id[jid] - return 0 - - def on_open_link_activated(self, widget, kind, text): - self.plugin.launch_browser_mailer(kind, text) - - def on_copy_link_activated(self, widget, text): - clip = gtk.clipboard_get() - clip.set_text(text) - - def make_link_menu(self, event, kind, text): - menu = gtk.Menu() - if kind == 'mail': - item = gtk.MenuItem(_('_Open email composer')) - else: - item = gtk.MenuItem(_('_Open link')) - item.connect('activate', self.on_open_link_activated, kind, text) - menu.append(item) - if kind == 'mail': - item = gtk.MenuItem(_('_Copy email address')) - else: # It's an url - item = gtk.MenuItem(_('_Copy link address')) - item.connect('activate', self.on_copy_link_activated, text) - menu.append(item) - - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - menu.reposition() - - def hyperlink_handler(self, texttag, widget, event, iter, kind): - if event.type == gtk.gdk.BUTTON_RELEASE: - begin_iter = iter.copy() - #we get the begining of the tag - while not begin_iter.begins_tag(texttag): - begin_iter.backward_word_start() - end_iter = iter.copy() - #we get the end of the tag - while not end_iter.ends_tag(texttag): - end_iter.forward_word_end() - word = begin_iter.get_text(end_iter) - if event.button == 3: - self.make_link_menu(event, kind, word) - else: - #we launch the correct application - self.plugin.launch_browser_mailer(kind, word) - - def print_special_text(self, text, jid, other_tag): - conversation_textview = self.xmls[jid].get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - - # make it CAPS (emoticons keys are all CAPS) - possible_emot_ascii_caps = text.upper() - if possible_emot_ascii_caps in self.plugin.emoticons.keys(): - #it's an emoticon - emot_ascii = possible_emot_ascii_caps - print 'emoticon:', emot_ascii - end_iter = conversation_buffer.get_end_iter() - conversation_buffer.insert_pixbuf(end_iter, \ - self.plugin.emoticons[emot_ascii]) - return - elif text.startswith('mailto:'): - #it's a mail - tag = 'mail' - print tag - elif self.plugin.sth_at_sth_dot_sth_re.match(text): #returns match object - #or None - #it's a mail - tag = 'mail' - print tag - elif text.startswith('*') and text.endswith('*'): - #it's a bold text - tag = 'bold' - text = text[1:-1] # remove * * - elif text.startswith('/') and text.endswith('/'): - #it's an italic text - tag = 'italic' - text = text[1:-1] # remove / / - print tag - elif text.startswith('_') and text.endswith('_'): - #it's an underlined text - tag = 'underline' - text = text[1:-1] # remove _ _ - print tag - else: - #it's a url - tag = 'url' - print tag - - end_iter = conversation_buffer.get_end_iter() - if tag in ['bold', 'italic', 'underline'] and other_tag: - conversation_buffer.insert_with_tags_by_name(end_iter, text,\ - other_tag, tag) - else: - conversation_buffer.insert_with_tags_by_name(end_iter, text, tag) - - def print_conversation(self, text, jid, contact = '', tim = None): - """Print a line in the conversation : - if contact is set to status : it's a status message - if contact is set to another value : it's an outgoing message - if contact is not set : it's an incomming message""" - user = self.users[jid] - conversation_textview = self.xmls[jid].get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - print_all_special = False - if not text: - text = '' - end_iter = conversation_buffer.get_end_iter() - if self.plugin.config['print_time'] == 'always': - if not tim: - tim = time.localtime() - tim_format = time.strftime("[%H:%M:%S]", tim) - conversation_buffer.insert(end_iter, tim_format + ' ') - - otext = '' - ttext = '' - if contact == 'status': - tag = 'status' - ttext = text + '\n' - print_all_special = True - else: - if contact: - tag = 'outgoing' - name = self.plugin.nicks[self.account] - else: - tag = 'incoming' - name = user.name - self.last_message_time[jid] = time.time() - - if text.startswith('/me'): - ttext = name + text[3:] + '\n' - print_all_special = True - else: - ttext = '<' + name + '> ' - otext = text + '\n' - #if it's a status we print special words - if not print_all_special: - conversation_buffer.insert_with_tags_by_name(end_iter, ttext, tag) - else: - otext = ttext - - start = 0 - end = 0 - index = 0 - - if self.plugin.config['useemoticons']: # search for emoticons & urls - my_re = sre.compile(self.plugin.emot_and_basic_pattern, sre.IGNORECASE) - iterator = my_re.finditer(otext) - else: # search for just urls - my_re = sre.compile(self.plugin.basic_pattern, sre.IGNORECASE) - iterator = my_re.finditer(otext) - for match in iterator: - start, end = match.span() - special_text = otext[start:end] - if start != 0: - text_before_special_text = otext[index:start] - end_iter = conversation_buffer.get_end_iter() - if print_all_special: - conversation_buffer.insert_with_tags_by_name(end_iter, \ - text_before_special_text, tag) - else: - conversation_buffer.insert(end_iter, text_before_special_text) - if print_all_special: - self.print_special_text(special_text, jid, tag) - else: - self.print_special_text(special_text, jid, '') - index = end # update index - - #add the rest in the index and after - end_iter = conversation_buffer.get_end_iter() - if print_all_special: - conversation_buffer.insert_with_tags_by_name(end_iter, \ - otext[index:], tag) - else: - conversation_buffer.insert(end_iter, otext[index:]) - - #scroll to the end of the textview - end_rect = conversation_textview.get_iter_location(end_iter) - visible_rect = conversation_textview.get_visible_rect() - end = False - if end_rect.y <= (visible_rect.y + visible_rect.height) or \ - (contact and contact != 'status'): - #we are at the end or we are sending something - end = True - conversation_textview.scroll_to_mark(conversation_buffer.\ - get_mark('end'), 0.1, 0, 0, 0) - if ((jid != self.get_active_jid()) or (not self.window.is_active()) or \ - (not end)) and contact == '': - self.nb_unread[jid] += 1 - self.redraw_tab(jid) - self.show_title() - -class Groupchat_window: - def on_groupchat_window_delete_event(self, widget, event): - """close window""" - for room_jid in self.xmls: - if time.time() - self.last_message_time[room_jid] < 2: - dialog = Confirmation_dialog(_('You received a message in the room %s in the last two seconds.\nDo you still want to close this window ?') % \ - room_jid.split('@')[0]) - if dialog.get_response() != gtk.RESPONSE_YES: - return True #stop the propagation of the event - - def on_groupchat_window_destroy(self, widget): - for room_jid in self.xmls: - self.plugin.send('GC_STATUS', self.account, (self.nicks[room_jid], \ - room_jid, 'offline', 'offline')) - del self.plugin.windows[self.account]['gc'][room_jid] - if self.plugin.windows[self.account]['gc'].has_key('tabbed'): - del self.plugin.windows[self.account]['gc']['tabbed'] - - def update_tags(self): - for room_jid in self.tagIn: - self.tagIn[room_jid].set_property('foreground', \ - self.plugin.config['inmsgcolor']) - self.tagInBold[room_jid].set_property('foreground', \ - self.plugin.config['inmsgcolor']) - self.tagOut[room_jid].set_property('foreground', \ - self.plugin.config['outmsgcolor']) - self.tagStatus[room_jid].set_property('foreground', \ - self.plugin.config['statusmsgcolor']) - - def get_role_iter(self, room_jid, role): - model = self.list_treeview[room_jid].get_model() - fin = False - iter = model.get_iter_root() - if not iter: - return None - while not fin: - role_name = model.get_value(iter, 2) - if role == role_name: - return iter - iter = model.iter_next(iter) - if not iter: - fin = True - return None - - def get_user_iter(self, room_jid, nick): - model = self.list_treeview[room_jid].get_model() - fin = False - role_iter = model.get_iter_root() - if not role_iter: - return None - while not fin: - fin2 = False - user_iter = model.iter_children(role_iter) - if not user_iter: - fin2=True - while not fin2: - if nick == model.get_value(user, 1): - return user_iter - user_iter = model.iter_next(user_iter) - if not user_iter: - fin2 = True - role_iter = model.iter_next(role_iter) - if not role_iter: - fin = True - return None - - def get_nick_list(self, room_jid): - model = self.list_treeview[room_jid].get_model() - list = [] - fin = False - role = model.get_iter_root() - if not role: - return list - while not fin: - fin2 = False - user = model.iter_children(role) - if not user: - fin2=True - while not fin2: - nick = model.get_value(user, 1) - list.append(nick) - user = model.iter_next(user) - if not user: - fin2 = True - role = model.iter_next(role) - if not role: - fin = True - return list - - def remove_user(self, room_jid, nick): - """Remove a user from the roster""" - model = self.list_treeview[room_jid].get_model() - iter = self.get_user_iter(room_jid, nick) - if not iter: - return - parent_iter = model.iter_parent(iter) - model.remove(iter) - if model.iter_n_children(parent_iter) == 0: - model.remove(parent_iter) - - def add_user_to_roster(self, room_jid, nick, show, role, jid): - model = self.list_treeview[room_jid].get_model() - img = self.plugin.roster.pixbufs[show] - role_iter = self.get_role_iter(room_jid, role) - if not role_iter: - role_iter = model.append(None, (self.plugin.roster.pixbufs['closed']\ - , role + 's', role)) - iter = model.append(role_iter, (img, nick, jid)) - self.list_treeview[room_jid].expand_row((model.get_path(role_iter)), \ - False) - return iter - - def get_role(self, room_jid, jid_iter): - model = self.list_treeview[room_jid].get_model() - path = model.get_path(jid_iter)[0] - iter = model.get_iter(path) - return model.get_value(iter, 2) - - def chg_user_status(self, room_jid, nick, show, status, role, affiliation, \ - jid, reason, actor, statusCode, account): - """When a user change his status""" - model = self.list_treeview[room_jid].get_model() - if show == 'offline' or show == 'error': - if statusCode == '307': - self.print_conversation(_('%s has been kicked by %s: %s') % (nick, \ - jid, actor, reason)) - self.remove_user(room_jid, nick) - else: - iter = self.get_user_iter(room_jid, nick) - ji = jid - if jid: - ji = jid.split('/')[0] - if not iter: - iter = self.add_user_to_roster(room_jid, nick, show, role, ji) - else: - actual_role = self.get_role(room_jid, iter) - if role != actual_role: - self.remove_user(room_jid, nick) - self.add_user_to_roster(room_jid, nick, show, role, ji) - else: - img = self.plugin.roster.pixbufs[show] - model.set_value(iter, 0, img) - - def show_title(self): - """redraw the window's title""" - unread = 0 - for room_jid in self.nb_unread: - unread += self.nb_unread[room_jid] - start = "" - if unread > 1: - start = "[" + str(unread) + "] " - elif unread == 1: - start = "* " - chat = 'Groupchat in ' + room_jid - if len(self.xmls) > 1: - chat = 'Groupchat' - self.window.set_title(start + chat + ' (' + self.account + ')') - - def redraw_tab(self, room_jid): - """redraw the label of the tab""" - start = '' - if self.nb_unread[room_jid] > 1: - start = "[" + str(self.nb_unread[room_jid]) + "] " - elif self.nb_unread[room_jid] == 1: - start = "* " - room = room_jid.split('@')[0] - child = self.xmls[room_jid].get_widget('group_vbox') - self.chat_notebook.set_tab_label_text(child, start + room) - - def get_active_jid(self): - active_child = self.chat_notebook.get_nth_page(\ - self.chat_notebook.get_current_page()) - active_jid = '' - for room_jid in self.xmls: - child = self.xmls[room_jid].get_widget('group_vbox') - if child == active_child: - active_jid = room_jid - break - return active_jid - - def set_subject(self, room_jid, subject): - self.subjects[room_jid] = subject - self.xml.get_widget('subject_entry').set_text(subject) - - def on_set_button_clicked(self, widget): - room_jid = self.get_active_jid() - subject = self.xml.get_widget('subject_entry').get_text() - self.plugin.send('GC_SUBJECT', self.account, (room_jid, subject)) - - def on_close_button_clicked(self, button): - room_jid = self.get_active_jid() - self.remove_tab(room_jid) - - def on_message_textview_key_press_event(self, widget, event): - """When a key is pressed: - if enter is pressed without the shit key, message (if not empty) is sent - and printed in the conversation. Tab does autocompete in nickames""" - if event.keyval == gtk.keysyms.Return: # ENTER - if (event.state & gtk.gdk.SHIFT_MASK): - return 0 - message_buffer = widget.get_buffer() - start_iter = message_buffer.get_start_iter() - end_iter = message_buffer.get_end_iter() - txt = message_buffer.get_text(start_iter, end_iter, 0) - if txt != '': - room_jid = self.get_active_jid() - self.plugin.send('GC_MSG', self.account, (room_jid, txt)) - message_buffer.set_text('', -1) - widget.grab_focus() - return 1 - elif event.keyval == gtk.keysyms.Tab: # TAB - room_jid = self.get_active_jid() - list_nick = self.get_nick_list(room_jid) - message_buffer = widget.get_buffer() - start_iter = message_buffer.get_start_iter() - cursor_position = message_buffer.get_insert() - end_iter = message_buffer.get_iter_at_mark(cursor_position) - text = message_buffer.get_text(start_iter, end_iter, 0) - if not text: - return 0 - splited_text = text.split() - begin = splited_text[-1] - for nick in list_nick: - if nick.find(begin) == 0: - if len(splited_text) == 1: - add = ': ' - else: - add = ' ' - message_buffer.insert_at_cursor(nick[len(begin):] + add) - return 1 - return 0 - - def on_groupchat_window_key_press_event(self, widget, event): - st = "1234567890" # humans count from 1, pc from 0 :P - room_jid = self.get_active_jid() - if event.keyval == gtk.keysyms.Escape: #ESCAPE - self.remove_tab(room_jid) - elif event.string and event.string in st \ - and (event.state & gtk.gdk.MOD1_MASK): # alt + 1,2,3 ... - self.chat_notebook.set_current_page(st.index(event.string)) - elif event.keyval == gtk.keysyms.Page_Down: # PAGEDOWN - if event.state & gtk.gdk.CONTROL_MASK: - current = self.chat_notebook.get_current_page() - if current > 0: - self.chat_notebook.set_current_page(current-1) -# else: -# self.chat_notebook.set_current_page(\ -# self.chat_notebook.get_n_pages()-1) - elif event.state & gtk.gdk.SHIFT_MASK: - conversation_textview = self.xmls[room_jid].\ - get_widget('conversation_textview') - rect = conversation_textview.get_visible_rect() - iter = conversation_textview.get_iter_at_location(rect.x,\ - rect.y + rect.height) - conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 0) - elif event.keyval == gtk.keysyms.Page_Up: # PAGEUP - if event.state & gtk.gdk.CONTROL_MASK: - current = self.chat_notebook.get_current_page() - if current < (self.chat_notebook.get_n_pages()-1): - self.chat_notebook.set_current_page(current+1) -# else: -# self.chat_notebook.set_current_page(0) - elif event.state & gtk.gdk.SHIFT_MASK: - conversation_textview = self.xmls[room_jid].\ - get_widget('conversation_textview') - rect = conversation_textview.get_visible_rect() - iter = conversation_textview.get_iter_at_location(rect.x, rect.y) - conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 1) - elif event.keyval == gtk.keysyms.Tab and \ - (event.state & gtk.gdk.CONTROL_MASK): # CTRL+TAB - current = self.chat_notebook.get_current_page() - if current < (self.chat_notebook.get_n_pages()-1): - self.chat_notebook.set_current_page(current+1) - else: - self.chat_notebook.set_current_page(0) - - '''FIXME: - NOT GOOD steals focus from Subject entry and I cannot find a way to prevent this - - else: # it's a normal key press make sure message_textview has focus - message_textview = self.xmls[room_jid].get_widget('message_textview') - if not message_textview.is_focus(): - message_textview.grab_focus() - ''' - - def print_conversation(self, text, room_jid, contact = '', tim = None): - """Print a line in the conversation : - if contact is set : it's a message from someone - if contact is not set : it's a message from the server""" - conversation_textview = self.xmls[room_jid].\ - get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - if not text: - text = '' - end_iter = conversation_buffer.get_end_iter() - if self.plugin.config['print_time'] == 'always': - if not tim: - tim = time.localtime() - tim_format = time.strftime('[%H:%M:%S]', tim) - conversation_buffer.insert(end_iter, tim_format + ' ') - - otext = '' - ttext = '' - if contact: - if contact == self.nicks[room_jid]: - tag = 'outgoing' - else: - if self.nicks[room_jid].lower() in text.lower().split(): - tag = 'incoming_bold' - else: - tag = 'incoming' - self.last_message_time[room_jid] = time.time() - - if text.startswith('/me'): - ttext = contact + text[3:] + '\n' - else: - ttext = '<' + contact + '> ' - otext = text + '\n' - else: - tag = 'status' - ttext = text + '\n' - - conversation_buffer.insert_with_tags_by_name(end_iter, ttext, tag) - #TODO: emoticons, url grabber - conversation_buffer.insert(end_iter, otext) - #scroll to the end of the textview - conversation_textview.scroll_to_mark(conversation_buffer.get_mark('end'),\ - 0.1, 0, 0, 0) - if not self.window.is_active() and contact != '': - self.nb_unread[room_jid] += 1 - self.redraw_tab(room_jid) - self.show_title() - - def kick(self, widget, room_jid, nick): - """kick a user""" - self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, 'none')) - - def grant_voice(self, widget, room_jid, nick): - """grant voice privilege to a user""" - self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, \ - 'participant')) - - def revoke_voice(self, widget, room_jid, nick): - """revoke voice privilege to a user""" - self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, 'visitor')) - - def grant_moderator(self, widget, room_jid, nick): - """grant moderator privilege to a user""" - self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, 'moderator')) - - def revoke_moderator(self, widget, room_jid, nick): - """revoke moderator privilege to a user""" - self.plugin.send('GC_SET_ROLE', self.account, (room_jid, nick, \ - 'participant')) - - def ban(self, widget, room_jid, jid): - """ban a user""" - self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ - 'outcast')) - - def grant_membership(self, widget, room_jid, jid): - """grant membership privilege to a user""" - self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ - 'member')) - - def revoke_membership(self, widget, room_jid, jid): - """revoke membership privilege to a user""" - self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ - 'none')) - - def grant_admin(self, widget, room_jid, jid): - """grant administrative privilege to a user""" - self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ - 'admin')) - - def revoke_admin(self, widget, room_jid, jid): - """revoke administrative privilege to a user""" - self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ - 'member')) - - def grant_owner(self, widget, room_jid, jid): - """grant owner privilege to a user""" - self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ - 'owner')) - - def revoke_owner(self, widget, room_jid, jid): - """revoke owner privilege to a user""" - self.plugin.send('GC_SET_AFFILIATION', self.account, (room_jid, jid, \ - 'admin')) - - def on_info(self, widget, jid): - """Call vcard_information_window class to display user's information""" - if not self.plugin.windows[self.account]['infos'].has_key(jid): - self.plugin.windows[self.account]['infos'][jid] = \ - vcard_information_window(jid, self.plugin, self.account, True) - self.plugin.send('ASK_VCARD', self.account, jid) - - def mk_menu(self, room_jid, event, iter): - """Make user's popup menu""" - model = self.list_treeview[room_jid].get_model() - nick = model.get_value(iter, 1) - jid = model.get_value(iter, 2) - - menu = gtk.Menu() - item = gtk.MenuItem(_('Privileges')) - menu.append(item) - - sub_menu = gtk.Menu() - item.set_submenu(sub_menu) - item = gtk.MenuItem(_('Kick')) - sub_menu.append(item) - item.connect('activate', self.kick, room_jid, nick) - item = gtk.MenuItem(_('Grant voice')) - sub_menu.append(item) - item.connect('activate', self.grant_voice, room_jid, nick) - item = gtk.MenuItem(_('Revoke voice')) - sub_menu.append(item) - item.connect('activate', self.revoke_voice, room_jid, nick) - item = gtk.MenuItem(_('Grant moderator')) - sub_menu.append(item) - item.connect('activate', self.grant_moderator, room_jid, nick) - item = gtk.MenuItem(_('Revoke moderator')) - sub_menu.append(item) - item.connect('activate', self.revoke_moderator, room_jid, nick) - if jid: - item = gtk.MenuItem() - sub_menu.append(item) - - item = gtk.MenuItem(_('Ban')) - sub_menu.append(item) - item.connect('activate', self.ban, room_jid, jid) - item = gtk.MenuItem(_('Grant membership')) - sub_menu.append(item) - item.connect('activate', self.grant_membership, room_jid, jid) - item = gtk.MenuItem(_('Revoke membership')) - sub_menu.append(item) - item.connect('activate', self.revoke_membership, room_jid, jid) - item = gtk.MenuItem(_('Grant admin')) - sub_menu.append(item) - item.connect('activate', self.grant_admin, room_jid, jid) - item = gtk.MenuItem(_('Revoke admin')) - sub_menu.append(item) - item.connect('activate', self.revoke_admin, room_jid, jid) - item = gtk.MenuItem(_('Grant owner')) - sub_menu.append(item) - item.connect('activate', self.grant_owner, room_jid, jid) - item = gtk.MenuItem(_('Revoke owner')) - sub_menu.append(item) - item.connect('activate', self.revoke_owner, room_jid, jid) - - item = gtk.MenuItem() - menu.append(item) - - item = gtk.MenuItem(_('Information')) - menu.append(item) - item.connect('activate', self.on_info, jid) - - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - menu.reposition() - - def on_groupchat_window_focus_in_event(self, widget, event): - """When window get focus""" - room_jid = self.get_active_jid() - if self.nb_unread[room_jid] > 0: - self.nb_unread[room_jid] = 0 - self.redraw_tab(room_jid) - self.show_title() - self.plugin.systray.remove_jid(room_jid, self.account) - - def on_chat_notebook_switch_page(self, notebook, page, page_num): - new_child = notebook.get_nth_page(page_num) - new_jid = '' - for room_jid in self.xmls: - child = self.xmls[room_jid].get_widget('group_vbox') - if child == new_child: - new_jid = room_jid - break - self.xml.get_widget('subject_entry').set_text(self.subjects[new_jid]) - if self.nb_unread[new_jid] > 0: - self.nb_unread[new_jid] = 0 - self.redraw_tab(new_jid) - self.show_title() - self.plugin.systray.remove_jid(new_jid, self.account) - - def active_tab(self, room_jid): - child = self.xmls[room_jid].get_widget('group_vbox') - self.chat_notebook.set_current_page(self.chat_notebook.page_num(child)) - self.xmls[room_jid].get_widget('message_textview').grab_focus() - - def remove_tab(self, room_jid): - if time.time() - self.last_message_time[room_jid] < 2: - dialog = Confirmation_dialog(_('You received a message in the room %s in the last two seconds.\nDo you still want to close this tab ?') % \ - room_jid.split('@')[0]) - if dialog.get_response() != gtk.RESPONSE_YES: - return - if len(self.xmls) == 1: - self.window.destroy() - else: - self.plugin.send('GC_STATUS', self.account, (self.nicks[room_jid], \ - room_jid, 'offline', 'offline')) - self.chat_notebook.remove_page(self.chat_notebook.get_current_page()) - del self.plugin.windows[self.account]['gc'][room_jid] - del self.nicks[room_jid] - del self.nb_unread[room_jid] - del self.last_message_time[room_jid] - del self.xmls[room_jid] - del self.tagIn[room_jid] - del self.tagInBold[room_jid] - del self.tagOut[room_jid] - del self.tagStatus[room_jid] - del self.list_treeview[room_jid] - del self.subjects[room_jid] - if len(self.xmls) == 1: - self.chat_notebook.set_show_tabs(False) - self.show_title() - - def new_group(self, room_jid, nick): - self.nb_unread[room_jid] = 0 - self.last_message_time[room_jid] = 0 - self.nicks[room_jid] = nick - self.subjects[room_jid] = '' - self.xmls[room_jid] = gtk.glade.XML(GTKGUI_GLADE, 'group_vbox', APP) - self.list_treeview[room_jid] = self.xmls[room_jid].\ - get_widget('list_treeview') - - #status_image, nickname, real_jid - store = gtk.TreeStore(gtk.Image, str, str) - column = gtk.TreeViewColumn('contacts') - render_text = ImageCellRenderer() - column.pack_start(render_text, expand = False) - column.add_attribute(render_text, 'image', 0) - render_text = gtk.CellRendererText() - column.pack_start(render_text, expand = True) - column.add_attribute(render_text, 'text', 1) - - self.list_treeview[room_jid].append_column(column) - self.list_treeview[room_jid].set_model(store) - - column = gtk.TreeViewColumn() - render = gtk.CellRendererPixbuf() - column.pack_start(render, expand = False) - self.list_treeview[room_jid].append_column(column) - column.set_visible(False) - self.list_treeview[room_jid].set_expander_column(column) - - conversation_textview = self.xmls[room_jid].\ - get_widget('conversation_textview') - conversation_buffer = conversation_textview.get_buffer() - end_iter = conversation_buffer.get_end_iter() - conversation_buffer.create_mark('end', end_iter, 0) - self.tagIn[room_jid] = conversation_buffer.create_tag('incoming') - # (nk) what is this? - self.tagInBold[room_jid] = conversation_buffer.create_tag('incoming_bold') - color = self.plugin.config['inmsgcolor'] - self.tagIn[room_jid].set_property('foreground', color) - self.tagInBold[room_jid].set_property('foreground', color) - self.tagInBold[room_jid].set_property('weight', 700) - self.tagOut[room_jid] = conversation_buffer.create_tag('outgoing') - color = self.plugin.config['outmsgcolor'] - self.tagOut[room_jid].set_property('foreground', color) - self.tagStatus[room_jid] = conversation_buffer.create_tag('status') - color = self.plugin.config['statusmsgcolor'] - self.tagStatus[room_jid].set_property('foreground', color) - - self.xmls[room_jid].signal_autoconnect(self) - - self.chat_notebook.append_page(self.xmls[room_jid].\ - get_widget('group_vbox')) - if len(self.xmls) > 1: - self.chat_notebook.set_show_tabs(True) - - self.redraw_tab(room_jid) - self.show_title() - - def on_list_treeview_button_press_event(self, widget, event): - """popup user's group's or agent menu""" - if event.type == gtk.gdk.BUTTON_PRESS: - if event.button == 3: - try: - path, column, x, y = widget.get_path_at_pos(int(event.x), \ - int(event.y)) - except TypeError: - widget.get_selection().unselect_all() - return False - model = widget.get_model() - iter = model.get_iter(path) - if len(path) == 2: - room_jid = self.get_active_jid() - self.mk_menu(room_jid, event, iter) - return True - if event.button == 1: - try: - path, column, x, y = widget.get_path_at_pos(int(event.x), \ - int(event.y)) - except TypeError: - widget.get_selection().unselect_all() - return False - - def on_list_treeview_key_release_event(self, widget, event): - if event.type == gtk.gdk.KEY_RELEASE: - if event.keyval == gtk.keysyms.Escape: - widget.get_selection().unselect_all() - return False - - def on_list_treeview_row_activated(self, widget, path, col=0): - """When an iter is dubble clicked : - open the chat window""" - model = widget.get_model() - iter = model.get_iter(path) - if len(path) == 1: - if (widget.row_expanded(path)): - widget.collapse_row(path) - else: - widget.expand_row(path, False) - - def on_list_treeview_row_expanded(self, widget, iter, path): - """When a row is expanded : - change the icon of the arrow""" - model = widget.get_model() - model.set_value(iter, 0, self.plugin.roster.pixbufs['opened']) - - def on_list_treeview_row_collapsed(self, widget, iter, path): - """When a row is collapsed : - change the icon of the arrow""" - model = widget.get_model() - model.set_value(iter, 0, self.plugin.roster.pixbufs['closed']) - - def __init__(self, room_jid, nick, plugin, account): - self.xml = gtk.glade.XML(GTKGUI_GLADE, 'groupchat_window', APP) - self.chat_notebook = self.xml.get_widget('chat_notebook') - self.chat_notebook.remove_page(0) - self.plugin = plugin - self.account = account - self.xmls = {} - self.tagIn = {} - self.tagInBold = {} - self.tagOut = {} - self.tagStatus = {} - self.nicks = {} - self.nb_unread = {} - self.last_message_time = {} - self.list_treeview = {} - self.subjects = {} - self.window = self.xml.get_widget('groupchat_window') - self.new_group(room_jid, nick) - self.show_title() - self.xml.signal_connect('on_groupchat_window_destroy', \ - self.on_groupchat_window_destroy) - self.xml.signal_connect('on_groupchat_window_delete_event', \ - self.on_groupchat_window_delete_event) - self.xml.signal_connect('on_groupchat_window_focus_in_event', \ - self.on_groupchat_window_focus_in_event) - self.xml.signal_connect('on_groupchat_window_key_press_event', \ - self.on_groupchat_window_key_press_event) - self.xml.signal_connect('on_chat_notebook_switch_page', \ - self.on_chat_notebook_switch_page) - self.xml.signal_connect('on_set_button_clicked', \ - self.on_set_button_clicked) - -class history_window: - """Class for bowser agent window : - to know the agents on the selected server""" - def on_history_window_destroy(self, widget): - """close window""" - del self.plugin.windows['logs'][self.jid] - - def on_close_button_clicked(self, widget): - """When Close button is clicked""" - widget.get_toplevel().destroy() - - def on_earliest_button_clicked(self, widget): - start, end = self.history_buffer.get_bounds() - self.history_buffer.delete(start, end) - self.earliest_button.set_sensitive(False) - self.previous_button.set_sensitive(False) - self.forward_button.set_sensitive(True) - self.latest_button.set_sensitive(True) - end = 50 - if end > self.nb_line: - end = self.nb_line - self.plugin.send('LOG_GET_RANGE', None, (self.jid, 0, end)) - self.num_begin = self.nb_line - - def on_previous_button_clicked(self, widget): - start, end = self.history_buffer.get_bounds() - self.history_buffer.delete(start, end) - self.earliest_button.set_sensitive(True) - self.previous_button.set_sensitive(True) - self.forward_button.set_sensitive(True) - self.latest_button.set_sensitive(True) - begin = self.num_begin - 50 - if begin < 0: - begin = 0 - end = begin + 50 - if end > self.nb_line: - end = self.nb_line - self.plugin.send('LOG_GET_RANGE', None, (self.jid, begin, end)) - self.num_begin = self.nb_line - - def on_forward_button_clicked(self, widget): - start, end = self.history_buffer.get_bounds() - self.history_buffer.delete(start, end) - self.earliest_button.set_sensitive(True) - self.previous_button.set_sensitive(True) - self.forward_button.set_sensitive(True) - self.latest_button.set_sensitive(True) - begin = self.num_begin + 50 - if begin > self.nb_line: - begin = self.nb_line - end = begin + 50 - if end > self.nb_line: - end = self.nb_line - self.plugin.send('LOG_GET_RANGE', None, (self.jid, begin, end)) - self.num_begin = self.nb_line - - def on_latest_button_clicked(self, widget): - start, end = self.history_buffer.get_bounds() - self.history_buffer.delete(start, end) - self.earliest_button.set_sensitive(True) - self.previous_button.set_sensitive(True) - self.forward_button.set_sensitive(False) - self.latest_button.set_sensitive(False) - begin = self.nb_line - 50 - if begin < 0: - begin = 0 - self.plugin.send('LOG_GET_RANGE', None, (self.jid, begin, self.nb_line)) - self.num_begin = self.nb_line - - def new_line(self, infos): - """write a new line""" - #infos = [num_line, date, type, data] - if infos[0] < self.num_begin: - self.num_begin = infos[0] - if infos[0] == 50: - self.earliest_button.set_sensitive(False) - self.previous_button.set_sensitive(False) - if infos[0] == self.nb_line: - self.forward_button.set_sensitive(False) - self.latest_button.set_sensitive(False) - start_iter = self.history_buffer.get_start_iter() - end_iter = self.history_buffer.get_end_iter() - tim = time.strftime("[%x %X] ", time.localtime(float(infos[1]))) - self.history_buffer.insert(start_iter, tim) - if infos[2] == 'recv': - msg = ':'.join(infos[3][0:]) - msg = msg.replace('\\n', '\n') - self.history_buffer.insert_with_tags_by_name(start_iter, msg, \ - 'incoming') - elif infos[2] == 'sent': - msg = ':'.join(infos[3][0:]) - msg = msg.replace('\\n', '\n') - self.history_buffer.insert_with_tags_by_name(start_iter, msg, \ - 'outgoing') - else: - msg = ':'.join(infos[3][1:]) - msg = msg.replace('\\n', '\n') - self.history_buffer.insert_with_tags_by_name(start_iter, \ - _('Status is now : ') + infos[3][0]+' : ' + msg, 'status') - - def set_nb_line(self, nb_line): - self.nb_line = nb_line - self.num_begin = nb_line - - def __init__(self, plugin, jid): - self.plugin = plugin - self.jid = jid - self.nb_line = 0 - self.num_begin = 0 - xml = gtk.glade.XML(GTKGUI_GLADE, 'history_window', APP) - self.window = xml.get_widget('history_window') - self.history_buffer = xml.get_widget('history_textview').get_buffer() - self.earliest_button = xml.get_widget('earliest_button') - self.previous_button = xml.get_widget('previous_button') - self.forward_button = xml.get_widget('forward_button') - self.latest_button = xml.get_widget('latest_button') - xml.signal_autoconnect(self) - tagIn = self.history_buffer.create_tag('incoming') - color = self.plugin.config['inmsgcolor'] - tagIn.set_property('foreground', color) - tagOut = self.history_buffer.create_tag('outgoing') - color = self.plugin.config['outmsgcolor'] - tagOut.set_property('foreground', color) - tagStatus = self.history_buffer.create_tag('status') - color = self.plugin.config['statusmsgcolor'] - tagStatus.set_property('foreground', color) - self.plugin.send('LOG_NB_LINE', None, jid) - -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() - fin = False - account = model.get_iter_root() - if not account: - return None - while not fin: - account_name = model.get_value(account, 3) - if name == account_name: - return account - account = model.iter_next(account) - if not account: - fin = True - return None - - def get_group_iter(self, name, account): - model = self.tree.get_model() - root = self.get_account_iter(account) - fin = False - group = model.iter_children(root) - if not group: - fin = True - while not fin: - group_name = model.get_value(group, 3) - if name == group_name: - return group - group = model.iter_next(group) - if not group: - fin = True - return None - - 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) - if not group: - return found - while not fin: - fin2 = False - user = model.iter_children(group) - if not user: - fin2=True - while not fin2: - if jid == model.get_value(user, 3): - found.append(user) - user = model.iter_next(user) - if not user: - fin2 = True - group = model.iter_next(group) - if not group: - fin = True - 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', 'online', 'away', 'xa', 'dnd', 'invisible'] - status = statuss[self.plugin.connected[account]] - model.append(None, (self.pixbufs[status], account, 'account', account,\ - account, False)) - - def add_user_to_roster(self, jid, account): - """Add a user to the roster and add groups if they aren't in roster""" - showOffline = self.plugin.config['showoffline'] - if not self.contacts[account].has_key(jid): - return - users = self.contacts[account][jid] - user = users[0] - if user.groups == []: - if user.jid.find("@") <= 0: - user.groups.append('Agents') - else: - user.groups.append('general') - - if (user.show == 'offline' or user.show == 'error') and not showOffline\ - and not 'Agents' in user.groups 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.pixbufs['closed'], g, 'group', \ - g, account, False)) - if not self.groups[account].has_key(g): #It can probably never append - if account+g in self.hidden_lines: - self.groups[account][g] = {'expand': False} - else: - self.groups[account][g] = {'expand': True} - if not account in self.hidden_lines and not self.plugin.config['mergeaccounts']: - self.tree.expand_row((model.get_path(iterG)[0]), False) - - typestr = 'user' - if g == 'Agents': - typestr = 'agent' - - model.append(iterG, (self.pixbufs[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.redraw_jid(jid, account) - - def remove_user(self, user, account): - """Remove a user from the roster""" - model = self.tree.get_model() - for i in self.get_user_iter(user.jid, account): - parent_i = model.iter_parent(i) - model.remove(i) - if model.iter_n_children(parent_i) == 0: - model.remove(parent_i) - - def redraw_jid(self, jid, account): - """draw the correct pixbuf 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 - for iter in iters: - if self.plugin.queues[account].has_key(jid): - img = self.pixbufs['message'] - else: - if user.sub == 'none': - if user.ask == 'subscribe': - img = self.pixbufs['requested'] - else: - img = self.pixbufs['not in the roster'] - else: - img = self.pixbufs[user.show] - model.set_value(iter, 0, img) - model.set_value(iter, 1, name) - - def makemenu(self): - """create the browse agents, add contact & join groupchat sub menus""" - # try to avoid WIDGET_REALIZED_FOR_EVENT failed which freezes gajim - new_message_menuitem = self.xml.get_widget('new_message_menuitem') - join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') - add_contact_menuitem = self.xml.get_widget('add_contact_menuitem') - browse_agents_menuitem = self.xml.get_widget('browse_agents_menuitem') - if len(self.plugin.accounts.keys()) > 0: - new_message_menuitem.set_sensitive(True) - join_gc_menuitem.set_sensitive(True) - add_contact_menuitem.set_sensitive(True) - browse_agents_menuitem.set_sensitive(True) - else: - new_message_menuitem.set_sensitive(False) - join_gc_menuitem.set_sensitive(False) - add_contact_menuitem.set_sensitive(False) - browse_agents_menuitem.set_sensitive(False) - if len(self.plugin.accounts.keys()) > 1: # 2 or more accounts? make submenus - #add - sub_menu = gtk.Menu() - add_contact_menuitem.set_submenu(sub_menu) - for account in self.plugin.accounts.keys(): - item = gtk.MenuItem('using ' + account + ' account') - sub_menu.append(item) - item.connect("activate", self.on_add_contact, account) - sub_menu.show_all() - #agents - sub_menu = gtk.Menu() - browse_agents_menuitem.set_submenu(sub_menu) - for account in self.plugin.accounts.keys(): - item = gtk.MenuItem('using ' + account + ' account') - sub_menu.append(item) - item.connect("activate", self.on_browse_agents, account) - sub_menu.show_all() - #join gc - sub_menu = gtk.Menu() - join_gc_menuitem.set_submenu(sub_menu) - for account in self.plugin.accounts.keys(): - item = gtk.MenuItem('using ' + account + ' account') - sub_menu.append(item) - item.connect("activate", self.on_join_gc, account) - sub_menu.show_all() - #new message - sub_menu = gtk.Menu() - new_message_menuitem.set_submenu(sub_menu) - for account in self.plugin.accounts.keys(): - item = gtk.MenuItem('using ' + account + ' account') - sub_menu.append(item) - item.connect("activate", self.on_new_message_menuitem_activate, account) - sub_menu.show_all() - elif len(self.plugin.accounts.keys()) == 1: - #add - if not self.add_contact_handler_id: - self.add_contact_handler_id = self.xml.get_widget('add_contact_menuitem').connect( - "activate", self.on_add_contact, self.plugin.accounts.keys()[0]) - #agents - if not self.browse_agents_handler_id: - self.browse_agents_handler_id = self.xml.get_widget( - 'browse_agents_menuitem').connect("activate", self.on_browse_agents, - self.plugin.accounts.keys()[0]) - #join_gc - if not self.join_gc_handler_id: - self.join_gc_handler_id = self.xml.get_widget('join_gc_menuitem').connect( - "activate", self.on_join_gc, self.plugin.accounts.keys()[0]) - if not self.new_message_menuitem_handler_id: - self.new_message_menuitem_handler_id = self.xml.get_widget('new_message_menuitem').connect( - "activate", self.on_new_message_menuitem_activate, self.plugin.accounts.keys()[0]) - - def draw_roster(self): - """Clear and draw roster""" - self.makemenu() - self.tree.get_model().clear() - for acct in self.contacts.keys(): - 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 = jids[1:] - #get name - name = array[jid]['name'] - if not name: - if ji.find("@") <= 0: - name = ji - else: - name = jid.split('@')[0] - #get show - show = array[jid]['show'] - if not show: - show = 'offline' - - user1 = User(ji, name, array[jid]['groups'], show, \ - array[jid]['status'], array[jid]['sub'], array[jid]['ask'], \ - resource, 0, '') - #when we draw the roster, we can't have twice the same user with - # 2 resources - self.contacts[account][ji] = [user1] - for g in array[jid]['groups'] : - if not g in self.groups[account].keys(): - if account+g in self.hidden_lines: - self.groups[account][g] = {'expand': False} - else: - self.groups[account][g] = {'expand': True} - - def chg_user_status(self, user, show, status, account): - """When a user change his status""" - showOffline = self.plugin.config['showoffline'] - model = self.tree.get_model() - if (show == 'offline' or show == 'error') and not showOffline and \ - not self.plugin.queues[account].has_key(user.jid): - if len(self.contacts[account][user.jid]) > 1: - luser = self.contacts[account][user.jid] - for u in luser: - if u.resource == user.resource: - luser.remove(u) - self.redraw_jid(user.jid, account) - break - else: - self.remove_user(user, account) - iters = [] - else: - if not self.get_user_iter(user.jid, account): - self.add_user_to_roster(user.jid, account) - self.redraw_jid(user.jid, account) - users = self.contacts[account][user.jid] - for u in users: - if u.resource == user.resource: - u.show = show - u.status = status - u.keyID = user.keyID - break - #Print status in chat window - if self.plugin.windows[account]['chats'].has_key(user.jid): - prio = 0 - sho = users[0].show - for u in users: - if u.priority > prio: - prio = u.priority - sho = u.show - img = self.pixbufs[sho] - self.plugin.windows[account]['chats'][user.jid].\ - set_image(img, user.jid) - name = user.name - if user.resource != '': - name += '/'+user.resource - self.plugin.windows[account]['chats'][user.jid].print_conversation(\ - _("%s is now %s (%s)") % (name, show, status), user.jid, 'status') - - def on_info(self, widget, user, account): - """Call vcard_information_window class to display user's information""" - if not self.plugin.windows[account]['infos'].has_key(user.jid): - self.plugin.windows[account]['infos'][user.jid] = \ - vcard_information_window(user, self.plugin, account) - - def on_agent_logging(self, widget, jid, state, account): - """When an agent is requested to log in or off""" - self.plugin.send('AGENT_LOGGING', account, (jid, state)) - - def on_remove_agent(self, widget, jid, account): - """When an agent is requested to log in or off""" - window = Confirmation_dialog(_('Are you sure you want to remove the agent %s from your roster?') % jid) - if window.get_response() == gtk.RESPONSE_YES: - self.plugin.send('UNSUB_AGENT', account, jid) - for u in self.contacts[account][jid]: - self.remove_user(u, account) - del self.contacts[account][u.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_history(self, widget, user): - """When history button is pressed : call log window""" - if not self.plugin.windows['logs'].has_key(user.jid): - self.plugin.windows['logs'][user.jid] = 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) - 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 contact list, please.'), account) - - item = gtk.MenuItem() - menu.append(item) - 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) - menu = gtk.Menu() - item = gtk.MenuItem(_("Log on")) - if self.contacts[account][jid][0].show != 'offline': - item.set_sensitive(False) - menu.append(item) - item.connect("activate", self.on_agent_logging, jid, 'available', account) - - item = gtk.MenuItem(_("Log off")) - if self.contacts[account][jid][0].show == 'offline': - 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(_("Remove")) - menu.append(item) - item.connect("activate", self.on_remove_agent, jid, account) - - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - menu.reposition() - - def on_edit_account(self, widget, account): - if not self.plugin.windows.has_key('account_modification_window'): - infos = self.plugin.accounts[account] - infos['accname'] = account - infos['jid'] = self.plugin.accounts[account]["name"] + \ - '@' + self.plugin.accounts[account]["hostname"] - self.plugin.windows['account_modification_window'] = \ - Account_modification_window(self.plugin, infos) - - 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(_("Away")) - sub_menu.append(item) - item.connect("activate", self.change_status, account, 'away') - item = gtk.MenuItem(_("NA")) - sub_menu.append(item) - item.connect("activate", self.change_status, account, 'xa') - item = gtk.MenuItem(_("DND")) - 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(_("_Browse agents")) - menu.append(item) - item.connect("activate", self.on_browse_agents, account) - item = gtk.MenuItem(_("_Add contact")) - menu.append(item) - item.connect("activate", self.on_add_contact, account) - item = gtk.MenuItem(_('_New message')) - menu.append(item) - item.connect("activate", self.on_new_message_menuitem_activate, account) - if not self.plugin.connected[account]: - item.set_sensitive(False) - - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - menu.reposition() - - def authorize(self, widget, jid, account): - """Authorize a user""" - self.plugin.send('AUTH', account, jid) - - def req_sub(self, widget, jid, txt, account, pseudo=None): - """Request subscription to a user""" - if not pseudo: - pseudo = jid - self.plugin.send('SUB', account, (jid, txt)) - if not self.contacts[account].has_key(jid): - user1 = User(jid, pseudo, ['general'], 'requested', \ - 'requested', 'none', 'subscribe', '', 0, '') - self.contacts[account][jid] = [user1] - self.add_user_to_roster(jid, account) - - def on_roster_treeview_key_release_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 user's group's or agent menu""" - if event.type == gtk.gdk.BUTTON_PRESS: - if event.button == 3: - 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 - 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: - 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' or type == 'account'): - # The integer 30 is the width of the first CellRenderer (see - # iconCellDataFunc function) - if x <= 30: - if (self.tree.row_expanded(path)): - self.tree.collapse_row(path) - else: - self.tree.expand_row(path, False) - return False - - def on_req_usub(self, widget, user, account): - """Remove a user""" - window = Confirmation_dialog(_("Are you sure you want to remove %s (%s) from your roster?") % (user.name, user.jid)) - if window.get_response() == gtk.RESPONSE_YES: - self.plugin.send('UNSUB', account, user.jid) - for u in self.contacts[account][user.jid]: - self.remove_user(u, account) - del self.contacts[account][u.jid] - - def send_status(self, account, status, txt, autoconnect=0): - if status != 'offline': - if not self.plugin.connected[account]: - model = self.tree.get_model() - accountIter = self.get_account_iter(account) - if accountIter: - model.set_value(accountIter, 0, self.pixbufs['connecting']) - self.plugin.systray.set_status('connecting') - - save_pass = 0 - if self.plugin.accounts[account].has_key('savepass'): - save_pass = self.plugin.accounts[account]['savepass'] - if not save_pass and not self.plugin.connected[account]: - passphrase = '' - w = Passphrase_dialog(_('Enter your password for account %s') \ - % account, 'Save password', autoconnect) - if autoconnect: - gtk.main() - passphrase, save = w.get_pass() - else: - passphrase, save = w.run() - if passphrase == -1: - if accountIter: - model.set_value(accountIter, 0, self.pixbufs['offline']) - self.set_cb() - return - self.plugin.send('PASSPHRASE', account, passphrase) - if save: - self.plugin.accounts[account]['savepass'] = 1 - self.plugin.accounts[account]['password'] = passphrase - - keyid = None - save_gpg_pass = 0 - if self.plugin.accounts[account].has_key('savegpgpass'): - save_gpg_pass = self.plugin.accounts[account]['savegpgpass'] - if self.plugin.accounts[account].has_key('keyid'): - keyid = self.plugin.accounts[account]['keyid'] - if keyid and not self.plugin.connected[account] and \ - self.plugin.config['usegpg']: - if save_gpg_pass: - passphrase = self.plugin.accounts[account]['gpgpassword'] - else: - passphrase = '' - w = Passphrase_dialog(\ - _('Enter GPG key passphrase for account %s') % account, \ - 'Save passphrase', autoconnect) - if autoconnect: - gtk.main() - passphrase, save = w.get_pass() - else: - passphrase, save = w.run() - if passphrase == -1: - passphrase = '' - if save: - self.plugin.accounts[account]['savegpgpass'] = 1 - self.plugin.accounts[account]['gpgpassword'] = passphrase - self.plugin.send('GPGPASSPHRASE', account, passphrase) - self.plugin.send('STATUS', account, (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] - self.plugin.send('GC_STATUS', account, (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, status): - if (status == 'online' and not self.plugin.config['ask_online_status']) \ - or (status == 'offline' and not \ - self.plugin.config['ask_offline_status']): - return status - w = Away_message_dialog(self.plugin) - message = w.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_cb_changed(self, widget): - """When we change our status""" - model = self.cb.get_model() - active = self.cb.get_active() - if active < 0: - return - accounts = self.plugin.accounts.keys() - if len(accounts) == 0: - Error_dialog(_("You must setup an account before connecting to jabber network.")) - self.set_cb() - return - status = model[active][0] - message = self.get_status_message(status) - if message == -1: - self.set_cb() - return - for acct in accounts: - if self.plugin.accounts[acct].has_key('sync_with_global_status'): - if not self.plugin.accounts[acct]['sync_with_global_status']: - continue - self.send_status(acct, status, message) - - def set_cb(self): - #table to change index in plugin.connected to index in combobox - table = {0:5, 1:0, 2:1, 3:2, 4:3, 5:4} - maxi = 0 - if len(self.plugin.connected.values()): - maxi = max(self.plugin.connected.values()) - #temporarily block signal in order not to send status that we show - #in the combobox - self.cb.handler_block(self.id_signal_cb) - self.cb.set_active(table[maxi]) - self.cb.handler_unblock(self.id_signal_cb) - statuss = ['offline', 'online', 'away', 'xa', 'dnd', 'invisible'] - self.plugin.systray.set_status(statuss[maxi]) - image = self.pixbufs[statuss[maxi]] - if image.get_storage_type() == gtk.IMAGE_ANIMATION: - pixbuf = image.get_animation().get_static_image() - self.window.set_icon(pixbuf) - elif image.get_storage_type() == gtk.IMAGE_PIXBUF: - self.window.set_icon(image.get_pixbuf()) - - 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.pixbufs[status]) - statuss = ['offline', 'online', 'away', 'xa', 'dnd', 'invisible'] - if status == 'offline': - for jid in self.contacts[account]: - luser = self.contacts[account][jid] - for user in luser: - self.chg_user_status(user, 'offline', 'Disconnected', account) - self.plugin.connected[account] = statuss.index(status) - self.set_cb() - - def new_chat(self, user, account): - if self.plugin.config['usetabbedchat']: - if not self.plugin.windows[account]['chats'].has_key('tabbed'): - self.plugin.windows[account]['chats']['tabbed'] = \ - 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'] - self.plugin.windows[account]['chats']['tabbed'].window.present() - else: - self.plugin.windows[account]['chats'][user.jid] = \ - tabbed_chat_window(user, self.plugin, account) - - def new_group(self, jid, nick, account): - if self.plugin.config['usetabbedchat']: - if not self.plugin.windows[account]['gc'].has_key('tabbed'): - self.plugin.windows[account]['gc']['tabbed'] = \ - Groupchat_window(jid, nick, self.plugin, account) - else: - self.plugin.windows[account]['gc']['tabbed'].new_group(jid, nick) - self.plugin.windows[account]['gc'][jid] = \ - self.plugin.windows[account]['gc']['tabbed'] - self.plugin.windows[account]['gc']['tabbed'].window.present() - else: - self.plugin.windows[account]['gc'][user.jid] = \ - Groupchat_window(jid, nick, self.plugin, account) - - def on_message(self, jid, msg, tim, account): - """when we receive a message""" - if not self.contacts[account].has_key(jid): - user1 = User(jid, jid, ['not in the roster'], \ - 'not in the roster', 'not in the roster', 'none', None, '', 0, '') - 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 = self.plugin.config['autopopup'] - autopopupaway = self.plugin.config['autopopupaway'] - if (autopopup == 0 or ( not autopopupaway and \ - self.plugin.connected[account] > 1)) and not \ - self.plugin.windows[account]['chats'].has_key(jid): - #We save it in a queue - if not self.plugin.queues[account].has_key(jid): - model = self.tree.get_model() - self.plugin.queues[account][jid] = Queue.Queue(50) - self.redraw_jid(jid, account) - self.plugin.systray.add_jid(jid, account) -# tim = time.strftime("[%H:%M:%S]") - self.plugin.queues[account][jid].put((msg, tim)) - self.nb_unread += 1 - self.show_title() - 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) - self.plugin.windows[account]['chats'][jid].print_conversation(msg, \ - jid, tim = tim) - if not self.plugin.windows[account]['chats'][jid].window.\ - get_property('is-active'): - self.plugin.systray.add_jid(jid, account) - - def on_preferences_menuitem_activate(self, widget): - """When preferences is selected : - call the preferences_window class""" - if not self.plugin.windows.has_key('preferences'): - self.plugin.windows['preferences'] = preferences_window(self.plugin) - - def on_add_contact(self, widget, account): - """When add user is selected : - call the add_contact_window class""" - add_contact_window(self.plugin, account) - - def on_join_gc(self, widget, account): - """When Join Groupchat is selected : - call the join_gc class""" - join_groupchat_window(self.plugin, account) - - def on_new_message_menuitem_activate(self, widget, account): - """When new message menuitem is activated: - call the New_message_dialog class""" - New_message_dialog(self.plugin, account) - - def on_about_menuitem_activate(self, widget): - """When about is selected : - call the about class""" - About_dialog(self.plugin) - - def on_accounts_menuitem_activate(self, widget): - """When accounts is seleted : - call the accounts class to modify accounts""" - if not self.plugin.windows.has_key('accounts_window'): - self.plugin.windows['accounts_window'] = 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_gajim_window_delete_event(self, widget, event): - """When we want to close the window""" - if self.plugin.systray_visible: - self.window.iconify() - else: - self.quit_gtkgui_plugin() - return 1 - - def quit_gtkgui_plugin(self): - """When we quit the gtk plugin : - tell that to the core and exit gtk""" - if self.plugin.config.has_key('saveposition'): - if self.plugin.config['saveposition']: - self.plugin.config['x-position'], self.plugin.config['y-position']=\ - self.window.get_position() - self.plugin.config['width'], self.plugin.config['height'] = \ - self.window.get_size() - - self.plugin.config['hiddenlines'] = '\t'.join(self.hidden_lines) - self.plugin.send('CONFIG', None, ('GtkGui', self.plugin.config, 'GtkGui')) - self.plugin.send('QUIT', None, ('gtkgui', 1)) - print _("plugin gtkgui stopped") - self.close_all(self.plugin.windows) - self.plugin.hide_systray() - gtk.main_quit() - - def on_quit_menuitem_activate(self, widget): - self.quit_gtkgui_plugin() - - def on_roster_treeview_row_activated(self, widget, path, col=0): - """When an iter is dubble 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): - if self.plugin.config['usetabbedchat']: - self.plugin.windows[account]['chats'][jid].active_tab(jid) - self.plugin.windows[account]['chats'][jid].window.present() - elif self.contacts[account].has_key(jid): - self.new_chat(self.contacts[account][jid][0], account) - self.plugin.windows[account]['chats'][jid].active_tab(jid) - - 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.pixbufs['opened']) - jid = model.get_value(iter, 3) - self.groups[account][jid]['expand'] = True - if account+jid in self.hidden_lines: - self.hidden_lines.remove(account+jid) - elif type == 'account': - if account in self.hidden_lines: - self.hidden_lines.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.pixbufs['closed']) - jid = model.get_value(iter, 3) - self.groups[account][jid]['expand'] = False - if not account+jid in self.hidden_lines: - self.hidden_lines.append(account+jid) - elif type == 'account': - if not account in self.hidden_lines: - self.hidden_lines.append(account) - - def on_editing_canceled (self, cell): - """editing have 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 - self.plugin.send('UPDUSER', account, (jid, new_text, \ - self.contacts[account][jid][0].groups)) - self.redraw_jid(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) - self.plugin.send('UPDUSER', account, (user.jid, user.name, \ - user.groups)) - model.set_value(iter, 5, False) - - def on_browse_agents(self, widget, account): - """When browse agent is selected : - Call browse class""" - if not self.plugin.windows[account].has_key('browser'): - self.plugin.windows[account]['browser'] = \ - agent_browser_window(self.plugin, account) - - def mkpixbufs(self): - """initialise pixbufs array""" - iconstyle = self.plugin.config['iconstyle'] - if not iconstyle: - iconstyle = 'sun' - self.path = 'plugins/gtkgui/icons/' + iconstyle + '/' - self.pixbufs = {} - 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(self.path + state_file + '.gif') - files.append(self.path + state_file + '.png') - files.append(self.path + state_file + '.xpm') - image = gtk.Image() - image.show() - self.pixbufs[state] = image - for file in files: - if not os.path.exists(file): - continue - image.set_from_file(file) - break - - def sound_is_ok(self, sound): - if not os.path.exists(sound): - return 0 - return 1 - - def on_show_offline_contacts_menuitem_activate(self, widget): - """when show offline option is changed: - redraw the treeview""" - self.plugin.config['showoffline'] = 1 - self.plugin.config['showoffline'] - self.plugin.send('CONFIG', None, ('GtkGui', self.plugin.config, 'GtkGui')) - 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', \ - self.plugin.config['accountbgcolor']) - renderer.set_property('xalign', 0) - elif model.get_value(iter, 2) == 'group': - renderer.set_property('cell-background', \ - self.plugin.config['groupbgcolor']) - renderer.set_property('xalign', 0.3) - else: - renderer.set_property('cell-background', \ - self.plugin.config['userbgcolor']) - renderer.set_property('xalign', 1) - renderer.set_property('width', 30) - - 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', \ - self.plugin.config['accounttextcolor']) - renderer.set_property('cell-background', \ - self.plugin.config['accountbgcolor']) - renderer.set_property('font', self.plugin.config['accountfont']) - renderer.set_property('xpad', 0) - elif model.get_value(iter, 2) == 'group': - renderer.set_property('foreground', \ - self.plugin.config['grouptextcolor']) - renderer.set_property('cell-background', \ - self.plugin.config['groupbgcolor']) - renderer.set_property('font', self.plugin.config['groupfont']) - renderer.set_property('xpad', 8) - else: - renderer.set_property('foreground', \ - self.plugin.config['usertextcolor']) - renderer.set_property('cell-background', \ - self.plugin.config['userbgcolor']) - renderer.set_property('font', self.plugin.config['userfont']) - renderer.set_property('xpad', 16) - - 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 - type = model.get_value(iter1, 2) - if type == 'group': - if name1 == 'Agents': - return 1 - if name2 == 'Agents': - 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): - 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\ - and path_dest[1] == 0: #droped before the first group - return - if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: - #droped before a group : we drop it in the previous group - path_dest = (path_dest[0], path_dest[1]-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: #droped on an account - return - if path_dest[0] != path_source[0]: #droped in another account - return - grp_source = model.get_value(model.iter_parent(iter_source), 3) - if grp_source == 'Agents': - return - account = model.get_value(model.get_iter(path_dest[0]), 3) - if len(path_dest) == 2: - grp_dest = model.get_value(iter_dest, 3) - elif len(path_dest) == 3: - grp_dest = model.get_value(model.iter_parent(iter_dest), 3) - if grp_source == grp_dest: - return - for u in self.contacts[account][data]: - u.groups.remove(grp_source) - u.groups.append(grp_dest) - self.plugin.send('UPDUSER', account, (u.jid, u.name, u.groups)) - parent_i = model.iter_parent(iter_source) - if model.iter_n_children(parent_i) == 1: #this was the only child - model.remove(parent_i) - self.add_user_to_roster(data, account) - if context.action == gtk.gdk.ACTION_MOVE: - context.finish(True, True, etime) - return - - def show_title(self): - 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, 'gajim_window', APP) - self.window = self.xml.get_widget('gajim_window') - self.tree = self.xml.get_widget('roster_treeview') - self.plugin = plugin - self.nb_unread = 0 - self.add_contact_handler_id = 0 - self.browse_agents_handler_id = 0 - self.join_gc_handler_id = 0 - self.new_message_menuitem_handler_id = 0 - self.regroup = 0 - if self.plugin.config.has_key('mergeaccounts'): - self.regroup = self.plugin.config['mergeaccounts'] - if self.plugin.config.has_key('saveposition'): - self.window.hide() - if self.plugin.config['saveposition']: - if self.plugin.config.has_key('x-position') and \ - self.plugin.config.has_key('y-position'): - self.window.move(self.plugin.config['x-position'], \ - self.plugin.config['y-position']) - if self.plugin.config.has_key('width') and \ - self.plugin.config.has_key('height'): - self.window.resize(self.plugin.config['width'], \ - self.plugin.config['height']) - self.window.show_all() - self.groups = {} - self.contacts = {} - for a in self.plugin.accounts.keys(): - self.contacts[a] = {} - self.groups[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.mkpixbufs() - - liststore = gtk.ListStore(gobject.TYPE_STRING, gtk.Image) - self.cb = gtk.ComboBox() - self.xml.get_widget('vbox1').pack_end(self.cb, False) - cell = ImageCellRenderer() - self.cb.pack_start(cell, False) - self.cb.add_attribute(cell, 'image', 1) - cell = gtk.CellRendererText() - self.cb.pack_start(cell, True) - self.cb.add_attribute(cell, 'text', 0) - for status in ['online', 'away', 'xa', 'dnd', 'invisible', 'offline']: - iter = liststore.append([status, self.pixbufs[status]]) - self.cb.show_all() - self.cb.set_model(liststore) - self.cb.set_active(5) - - showOffline = self.plugin.config['showoffline'] - self.xml.get_widget('show_offline_contacts_menuitem').set_active(showOffline) - - #columns - col = gtk.TreeViewColumn() - self.tree.append_column(col) - render_pixbuf = ImageCellRenderer() - 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) - #need gtk2.4 - #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) - - 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.cb.connect('changed', self.on_cb_changed) - - self.hidden_lines = self.plugin.config['hiddenlines'].split('\t') - self.draw_roster() - if len(self.plugin.accounts) == 0: - self.plugin.windows['accounts_window'] = Accounts_window(self.plugin) - self.plugin.windows['account_modification_window'] = \ - Account_modification_window(self.plugin, {}) - -class systrayDummy: - """Class when we don't want icon in the systray""" - def add_jid(self, jid, account): - pass - def remove_jid(self, jid, account): - pass - def set_status(self, status): - pass - def show_icon(self): - pass - def hide_icon(self): - pass - def __init__(self): - self.t = gtk.Button() - - -class systray: - """Class for icon in the systray""" - def set_img(self): - if len(self.jids) > 0: - status = 'message' - else: - status = self.status - image = self.plugin.roster.pixbufs[status] - if image.get_storage_type() == gtk.IMAGE_ANIMATION: - self.img_tray.set_from_animation(image.get_animation()) - elif image.get_storage_type() == gtk.IMAGE_PIXBUF: - self.img_tray.set_from_pixbuf(image.get_pixbuf()) - - def add_jid(self, jid, account): - list = [account, jid] - if not list in self.jids: - self.jids.append(list) - self.set_img() - - def remove_jid(self, jid, account): - list = [account, jid] - if list in self.jids: - self.jids.remove(list) - self.set_img() - - def set_status(self, status): - self.status = status - self.set_img() - - def set_cb(self, widget, status): - statuss = ['online', 'away', 'xa', 'dnd', 'invisible', 'offline'] - self.plugin.roster.cb.set_active(statuss.index(status)) - - def start_chat(self, widget, account, jid): - if self.plugin.windows[account]['chats'].has_key(jid): - self.plugin.windows[account]['chats'][jid].window.present() - elif self.plugin.roster.contacts[account].has_key(jid): - self.plugin.roster.new_chat( - self.plugin.roster.contacts[account][jid][0], account) - - def mk_menu(self, event): - menu = gtk.Menu() - item = gtk.TearoffMenuItem() - menu.append(item) - - 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.set_cb, 'online') - item = gtk.MenuItem(_("Away")) - sub_menu.append(item) - item.connect("activate", self.set_cb, 'away') - item = gtk.MenuItem(_("NA")) - sub_menu.append(item) - item.connect("activate", self.set_cb, 'xa') - item = gtk.MenuItem(_("DND")) - sub_menu.append(item) - item.connect("activate", self.set_cb, 'dnd') - item = gtk.MenuItem(_("Invisible")) - sub_menu.append(item) - item.connect("activate", self.set_cb, 'invisible') - item = gtk.MenuItem() - sub_menu.append(item) - item = gtk.MenuItem(_("Offline")) - sub_menu.append(item) - item.connect("activate", self.set_cb, 'offline') - - item = gtk.MenuItem() - menu.append(item) - - item = gtk.MenuItem(_("Chat with")) - menu.append(item) - menu_account = gtk.Menu() - item.set_submenu(menu_account) - for account in self.plugin.accounts.keys(): - item = gtk.MenuItem(account) - menu_account.append(item) - menu_group = gtk.Menu() - item.set_submenu(menu_group) - for group in self.plugin.roster.groups[account].keys(): - if group == 'Agents': - continue - item = gtk.MenuItem(group) - menu_group.append(item) - menu_user = gtk.Menu() - item.set_submenu(menu_user) - for users in self.plugin.roster.contacts[account].values(): - user = users[0] - if group in user.groups and user.show != 'offline' and \ - user.show != 'error': - item = gtk.MenuItem(user.name.replace('_', '__')) - menu_user.append(item) - item.connect("activate", self.start_chat, account, user.jid) - - item = gtk.MenuItem() - menu.append(item) - - item = gtk.MenuItem(_("Quit")) - menu.append(item) - item.connect("activate", self.plugin.roster.on_quit_menuitem_activate) - - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - menu.reposition() - - def on_clicked(self, widget, event): - if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: - if len(self.jids) == 0: - win = self.plugin.roster.window - if win.iconify_initially: - win.deiconify() - else: - if win.is_active(): - win.iconify() - else: - win.present() - else: - account = self.jids[0][0] - jid = self.jids[0][1] - if self.plugin.windows[account]['gc'].has_key(jid): - self.plugin.windows[account]['gc'][jid].active_tab(jid) - self.plugin.windows[account]['gc'][jid].window.present() - elif self.plugin.windows[account]['chats'].has_key(jid): - self.plugin.windows[account]['chats'][jid].active_tab(jid) - self.plugin.windows[account]['chats'][jid].window.present() - else: - self.plugin.roster.new_chat( - self.plugin.roster.contacts[account][jid][0], account) - if event.button == 3: - self.mk_menu(event) - - def show_icon(self): - if not self.t: - self.t = trayicon.TrayIcon("Gajim") - eb = gtk.EventBox() - eb.connect("button-press-event", self.on_clicked) - self.tip = gtk.Tooltips() - self.tip.set_tip(self.t, 'Gajim') - self.img_tray = gtk.Image() - eb.add(self.img_tray) - self.t.add(eb) - self.set_img() - self.t.show_all() - - def hide_icon(self): - if self.t: - self.t.destroy() - self.t = None - - def __init__(self, plugin): - self.plugin = plugin - self.jids = [] - self.t = None - self.img_tray = gtk.Image() - self.status = 'offline' - - class plugin: """Class called by the core in a new thread""" @@ -3580,8 +727,8 @@ class plugin: def __init__(self, quIN, quOUT): gtk.gdk.threads_init() #(asterix) I don't have pygtk 2.6 for the moment, so I cannot test - gtk.about_dialog_set_email_hook(self.launch_browser_mailer, 'mail') - gtk.about_dialog_set_url_hook(self.launch_browser_mailer, 'url') +# gtk.about_dialog_set_email_hook(self.launch_browser_mailer, 'mail') +# gtk.about_dialog_set_url_hook(self.launch_browser_mailer, 'url') self.queueIN = quIN self.queueOUT = quOUT self.send('REG_MESSAGE', 'gtkgui', ['ROSTER', 'WARNING', 'STATUS', \ diff --git a/plugins/gtkgui/history_window.py b/plugins/gtkgui/history_window.py new file mode 100644 index 000000000..8524e8bca --- /dev/null +++ b/plugins/gtkgui/history_window.py @@ -0,0 +1,160 @@ +## plugins/history_window.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - Nikos Kouremenos +## - Alex Podaras +## +## 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 time + +from common import i18n + +_ = i18n._ +APP = i18n.APP +gtk.glade.bindtextdomain(APP, i18n.DIR) +gtk.glade.textdomain(APP) + +GTKGUI_GLADE='plugins/gtkgui/gtkgui.glade' + +class history_window: + """Class for bowser agent window : + to know the agents on the selected server""" + def on_history_window_destroy(self, widget): + """close window""" + del self.plugin.windows['logs'][self.jid] + + def on_close_button_clicked(self, widget): + """When Close button is clicked""" + widget.get_toplevel().destroy() + + def on_earliest_button_clicked(self, widget): + start, end = self.history_buffer.get_bounds() + self.history_buffer.delete(start, end) + self.earliest_button.set_sensitive(False) + self.previous_button.set_sensitive(False) + self.forward_button.set_sensitive(True) + self.latest_button.set_sensitive(True) + end = 50 + if end > self.nb_line: + end = self.nb_line + self.plugin.send('LOG_GET_RANGE', None, (self.jid, 0, end)) + self.num_begin = self.nb_line + + def on_previous_button_clicked(self, widget): + start, end = self.history_buffer.get_bounds() + self.history_buffer.delete(start, end) + self.earliest_button.set_sensitive(True) + self.previous_button.set_sensitive(True) + self.forward_button.set_sensitive(True) + self.latest_button.set_sensitive(True) + begin = self.num_begin - 50 + if begin < 0: + begin = 0 + end = begin + 50 + if end > self.nb_line: + end = self.nb_line + self.plugin.send('LOG_GET_RANGE', None, (self.jid, begin, end)) + self.num_begin = self.nb_line + + def on_forward_button_clicked(self, widget): + start, end = self.history_buffer.get_bounds() + self.history_buffer.delete(start, end) + self.earliest_button.set_sensitive(True) + self.previous_button.set_sensitive(True) + self.forward_button.set_sensitive(True) + self.latest_button.set_sensitive(True) + begin = self.num_begin + 50 + if begin > self.nb_line: + begin = self.nb_line + end = begin + 50 + if end > self.nb_line: + end = self.nb_line + self.plugin.send('LOG_GET_RANGE', None, (self.jid, begin, end)) + self.num_begin = self.nb_line + + def on_latest_button_clicked(self, widget): + start, end = self.history_buffer.get_bounds() + self.history_buffer.delete(start, end) + self.earliest_button.set_sensitive(True) + self.previous_button.set_sensitive(True) + self.forward_button.set_sensitive(False) + self.latest_button.set_sensitive(False) + begin = self.nb_line - 50 + if begin < 0: + begin = 0 + self.plugin.send('LOG_GET_RANGE', None, (self.jid, begin, self.nb_line)) + self.num_begin = self.nb_line + + def new_line(self, infos): + """write a new line""" + #infos = [num_line, date, type, data] + if infos[0] < self.num_begin: + self.num_begin = infos[0] + if infos[0] == 50: + self.earliest_button.set_sensitive(False) + self.previous_button.set_sensitive(False) + if infos[0] == self.nb_line: + self.forward_button.set_sensitive(False) + self.latest_button.set_sensitive(False) + start_iter = self.history_buffer.get_start_iter() + end_iter = self.history_buffer.get_end_iter() + tim = time.strftime("[%x %X] ", time.localtime(float(infos[1]))) + self.history_buffer.insert(start_iter, tim) + if infos[2] == 'recv': + msg = ':'.join(infos[3][0:]) + msg = msg.replace('\\n', '\n') + self.history_buffer.insert_with_tags_by_name(start_iter, msg, \ + 'incoming') + elif infos[2] == 'sent': + msg = ':'.join(infos[3][0:]) + msg = msg.replace('\\n', '\n') + self.history_buffer.insert_with_tags_by_name(start_iter, msg, \ + 'outgoing') + else: + msg = ':'.join(infos[3][1:]) + msg = msg.replace('\\n', '\n') + self.history_buffer.insert_with_tags_by_name(start_iter, \ + _('Status is now : ') + infos[3][0]+' : ' + msg, 'status') + + def set_nb_line(self, nb_line): + self.nb_line = nb_line + self.num_begin = nb_line + + def __init__(self, plugin, jid): + self.plugin = plugin + self.jid = jid + self.nb_line = 0 + self.num_begin = 0 + xml = gtk.glade.XML(GTKGUI_GLADE, 'history_window', APP) + self.window = xml.get_widget('history_window') + self.history_buffer = xml.get_widget('history_textview').get_buffer() + self.earliest_button = xml.get_widget('earliest_button') + self.previous_button = xml.get_widget('previous_button') + self.forward_button = xml.get_widget('forward_button') + self.latest_button = xml.get_widget('latest_button') + xml.signal_autoconnect(self) + tagIn = self.history_buffer.create_tag('incoming') + color = self.plugin.config['inmsgcolor'] + tagIn.set_property('foreground', color) + tagOut = self.history_buffer.create_tag('outgoing') + color = self.plugin.config['outmsgcolor'] + tagOut.set_property('foreground', color) + tagStatus = self.history_buffer.create_tag('status') + color = self.plugin.config['statusmsgcolor'] + tagStatus.set_property('foreground', color) + self.plugin.send('LOG_NB_LINE', None, jid) diff --git a/plugins/gtkgui/roster_window.py b/plugins/gtkgui/roster_window.py new file mode 100644 index 000000000..049c67a78 --- /dev/null +++ b/plugins/gtkgui/roster_window.py @@ -0,0 +1,1283 @@ +## plugins/roster.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - Nikos Kouremenos +## - Alex Podaras +## +## 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 Queue +import common.sleepy + +from tabbed_chat_window import * +from gtkgui import ImageCellRenderer, User +from dialogs import * +from config import * + +from common import i18n + +_ = i18n._ +APP = i18n.APP +gtk.glade.bindtextdomain(APP, i18n.DIR) +gtk.glade.textdomain(APP) + +GTKGUI_GLADE='plugins/gtkgui/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() + fin = False + account = model.get_iter_root() + if not account: + return None + while not fin: + account_name = model.get_value(account, 3) + if name == account_name: + return account + account = model.iter_next(account) + if not account: + fin = True + return None + + def get_group_iter(self, name, account): + model = self.tree.get_model() + root = self.get_account_iter(account) + fin = False + group = model.iter_children(root) + if not group: + fin = True + while not fin: + group_name = model.get_value(group, 3) + if name == group_name: + return group + group = model.iter_next(group) + if not group: + fin = True + return None + + 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) + if not group: + return found + while not fin: + fin2 = False + user = model.iter_children(group) + if not user: + fin2=True + while not fin2: + if jid == model.get_value(user, 3): + found.append(user) + user = model.iter_next(user) + if not user: + fin2 = True + group = model.iter_next(group) + if not group: + fin = True + 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', 'online', 'away', 'xa', 'dnd', 'invisible'] + status = statuss[self.plugin.connected[account]] + model.append(None, (self.pixbufs[status], account, 'account', account,\ + account, False)) + + def add_user_to_roster(self, jid, account): + """Add a user to the roster and add groups if they aren't in roster""" + showOffline = self.plugin.config['showoffline'] + if not self.contacts[account].has_key(jid): + return + users = self.contacts[account][jid] + user = users[0] + if user.groups == []: + if user.jid.find("@") <= 0: + user.groups.append('Agents') + else: + user.groups.append('general') + + if (user.show == 'offline' or user.show == 'error') and not showOffline\ + and not 'Agents' in user.groups 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.pixbufs['closed'], g, 'group', \ + g, account, False)) + if not self.groups[account].has_key(g): #It can probably never append + if account+g in self.hidden_lines: + self.groups[account][g] = {'expand': False} + else: + self.groups[account][g] = {'expand': True} + if not account in self.hidden_lines and not self.plugin.config['mergeaccounts']: + self.tree.expand_row((model.get_path(iterG)[0]), False) + + typestr = 'user' + if g == 'Agents': + typestr = 'agent' + + model.append(iterG, (self.pixbufs[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.redraw_jid(jid, account) + + def remove_user(self, user, account): + """Remove a user from the roster""" + model = self.tree.get_model() + for i in self.get_user_iter(user.jid, account): + parent_i = model.iter_parent(i) + model.remove(i) + if model.iter_n_children(parent_i) == 0: + model.remove(parent_i) + + def redraw_jid(self, jid, account): + """draw the correct pixbuf 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 + for iter in iters: + if self.plugin.queues[account].has_key(jid): + img = self.pixbufs['message'] + else: + if user.sub == 'none': + if user.ask == 'subscribe': + img = self.pixbufs['requested'] + else: + img = self.pixbufs['not in the roster'] + else: + img = self.pixbufs[user.show] + model.set_value(iter, 0, img) + model.set_value(iter, 1, name) + + def makemenu(self): + """create the browse agents, add contact & join groupchat sub menus""" + # try to avoid WIDGET_REALIZED_FOR_EVENT failed which freezes gajim + new_message_menuitem = self.xml.get_widget('new_message_menuitem') + join_gc_menuitem = self.xml.get_widget('join_gc_menuitem') + add_contact_menuitem = self.xml.get_widget('add_contact_menuitem') + browse_agents_menuitem = self.xml.get_widget('browse_agents_menuitem') + if len(self.plugin.accounts.keys()) > 0: + new_message_menuitem.set_sensitive(True) + join_gc_menuitem.set_sensitive(True) + add_contact_menuitem.set_sensitive(True) + browse_agents_menuitem.set_sensitive(True) + else: + new_message_menuitem.set_sensitive(False) + join_gc_menuitem.set_sensitive(False) + add_contact_menuitem.set_sensitive(False) + browse_agents_menuitem.set_sensitive(False) + if len(self.plugin.accounts.keys()) > 1: # 2 or more accounts? make submenus + #add + sub_menu = gtk.Menu() + add_contact_menuitem.set_submenu(sub_menu) + for account in self.plugin.accounts.keys(): + item = gtk.MenuItem('using ' + account + ' account') + sub_menu.append(item) + item.connect("activate", self.on_add_contact, account) + sub_menu.show_all() + #agents + sub_menu = gtk.Menu() + browse_agents_menuitem.set_submenu(sub_menu) + for account in self.plugin.accounts.keys(): + item = gtk.MenuItem('using ' + account + ' account') + sub_menu.append(item) + item.connect("activate", self.on_browse_agents, account) + sub_menu.show_all() + #join gc + sub_menu = gtk.Menu() + join_gc_menuitem.set_submenu(sub_menu) + for account in self.plugin.accounts.keys(): + item = gtk.MenuItem('using ' + account + ' account') + sub_menu.append(item) + item.connect("activate", self.on_join_gc, account) + sub_menu.show_all() + #new message + sub_menu = gtk.Menu() + new_message_menuitem.set_submenu(sub_menu) + for account in self.plugin.accounts.keys(): + item = gtk.MenuItem('using ' + account + ' account') + sub_menu.append(item) + item.connect("activate", self.on_new_message_menuitem_activate, account) + sub_menu.show_all() + elif len(self.plugin.accounts.keys()) == 1: + #add + if not self.add_contact_handler_id: + self.add_contact_handler_id = self.xml.get_widget('add_contact_menuitem').connect( + "activate", self.on_add_contact, self.plugin.accounts.keys()[0]) + #agents + if not self.browse_agents_handler_id: + self.browse_agents_handler_id = self.xml.get_widget( + 'browse_agents_menuitem').connect("activate", self.on_browse_agents, + self.plugin.accounts.keys()[0]) + #join_gc + if not self.join_gc_handler_id: + self.join_gc_handler_id = self.xml.get_widget('join_gc_menuitem').connect( + "activate", self.on_join_gc, self.plugin.accounts.keys()[0]) + if not self.new_message_menuitem_handler_id: + self.new_message_menuitem_handler_id = self.xml.get_widget('new_message_menuitem').connect( + "activate", self.on_new_message_menuitem_activate, self.plugin.accounts.keys()[0]) + + def draw_roster(self): + """Clear and draw roster""" + self.makemenu() + self.tree.get_model().clear() + for acct in self.contacts.keys(): + 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 = jids[1:] + #get name + name = array[jid]['name'] + if not name: + if ji.find("@") <= 0: + name = ji + else: + name = jid.split('@')[0] + #get show + show = array[jid]['show'] + if not show: + show = 'offline' + + user1 = User(ji, name, array[jid]['groups'], show, \ + array[jid]['status'], array[jid]['sub'], array[jid]['ask'], \ + resource, 0, '') + #when we draw the roster, we can't have twice the same user with + # 2 resources + self.contacts[account][ji] = [user1] + for g in array[jid]['groups'] : + if not g in self.groups[account].keys(): + if account+g in self.hidden_lines: + self.groups[account][g] = {'expand': False} + else: + self.groups[account][g] = {'expand': True} + + def chg_user_status(self, user, show, status, account): + """When a user change his status""" + showOffline = self.plugin.config['showoffline'] + model = self.tree.get_model() + if (show == 'offline' or show == 'error') and not showOffline and \ + not self.plugin.queues[account].has_key(user.jid): + if len(self.contacts[account][user.jid]) > 1: + luser = self.contacts[account][user.jid] + for u in luser: + if u.resource == user.resource: + luser.remove(u) + self.redraw_jid(user.jid, account) + break + else: + self.remove_user(user, account) + iters = [] + else: + if not self.get_user_iter(user.jid, account): + self.add_user_to_roster(user.jid, account) + self.redraw_jid(user.jid, account) + users = self.contacts[account][user.jid] + for u in users: + if u.resource == user.resource: + u.show = show + u.status = status + u.keyID = user.keyID + break + #Print status in chat window + if self.plugin.windows[account]['chats'].has_key(user.jid): + prio = 0 + sho = users[0].show + for u in users: + if u.priority > prio: + prio = u.priority + sho = u.show + img = self.pixbufs[sho] + self.plugin.windows[account]['chats'][user.jid].\ + set_image(img, user.jid) + name = user.name + if user.resource != '': + name += '/'+user.resource + self.plugin.windows[account]['chats'][user.jid].print_conversation(\ + _("%s is now %s (%s)") % (name, show, status), user.jid, 'status') + + def on_info(self, widget, user, account): + """Call vcard_information_window class to display user's information""" + if not self.plugin.windows[account]['infos'].has_key(user.jid): + self.plugin.windows[account]['infos'][user.jid] = \ + vcard_information_window(user, self.plugin, account) + + def on_agent_logging(self, widget, jid, state, account): + """When an agent is requested to log in or off""" + self.plugin.send('AGENT_LOGGING', account, (jid, state)) + + def on_remove_agent(self, widget, jid, account): + """When an agent is requested to log in or off""" + window = Confirmation_dialog(_('Are you sure you want to remove the agent %s from your roster?') % jid) + if window.get_response() == gtk.RESPONSE_YES: + self.plugin.send('UNSUB_AGENT', account, jid) + for u in self.contacts[account][jid]: + self.remove_user(u, account) + del self.contacts[account][u.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_history(self, widget, user): + """When history button is pressed : call log window""" + if not self.plugin.windows['logs'].has_key(user.jid): + self.plugin.windows['logs'][user.jid] = 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) + 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 contact list, please.'), account) + + item = gtk.MenuItem() + menu.append(item) + 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) + menu = gtk.Menu() + item = gtk.MenuItem(_("Log on")) + if self.contacts[account][jid][0].show != 'offline': + item.set_sensitive(False) + menu.append(item) + item.connect("activate", self.on_agent_logging, jid, 'available', account) + + item = gtk.MenuItem(_("Log off")) + if self.contacts[account][jid][0].show == 'offline': + 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(_("Remove")) + menu.append(item) + item.connect("activate", self.on_remove_agent, jid, account) + + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def on_edit_account(self, widget, account): + if not self.plugin.windows.has_key('account_modification_window'): + infos = self.plugin.accounts[account] + infos['accname'] = account + infos['jid'] = self.plugin.accounts[account]["name"] + \ + '@' + self.plugin.accounts[account]["hostname"] + self.plugin.windows['account_modification_window'] = \ + Account_modification_window(self.plugin, infos) + + 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(_("Away")) + sub_menu.append(item) + item.connect("activate", self.change_status, account, 'away') + item = gtk.MenuItem(_("NA")) + sub_menu.append(item) + item.connect("activate", self.change_status, account, 'xa') + item = gtk.MenuItem(_("DND")) + 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(_("_Browse agents")) + menu.append(item) + item.connect("activate", self.on_browse_agents, account) + item = gtk.MenuItem(_("_Add contact")) + menu.append(item) + item.connect("activate", self.on_add_contact, account) + item = gtk.MenuItem(_('_New message')) + menu.append(item) + item.connect("activate", self.on_new_message_menuitem_activate, account) + if not self.plugin.connected[account]: + item.set_sensitive(False) + + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def authorize(self, widget, jid, account): + """Authorize a user""" + self.plugin.send('AUTH', account, jid) + + def req_sub(self, widget, jid, txt, account, pseudo=None): + """Request subscription to a user""" + if not pseudo: + pseudo = jid + self.plugin.send('SUB', account, (jid, txt)) + if not self.contacts[account].has_key(jid): + user1 = User(jid, pseudo, ['general'], 'requested', \ + 'requested', 'none', 'subscribe', '', 0, '') + self.contacts[account][jid] = [user1] + self.add_user_to_roster(jid, account) + + def on_roster_treeview_key_release_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 user's group's or agent menu""" + if event.type == gtk.gdk.BUTTON_PRESS: + if event.button == 3: + 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 + 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: + 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' or type == 'account'): + # The integer 30 is the width of the first CellRenderer (see + # iconCellDataFunc function) + if x <= 30: + if (self.tree.row_expanded(path)): + self.tree.collapse_row(path) + else: + self.tree.expand_row(path, False) + return False + + def on_req_usub(self, widget, user, account): + """Remove a user""" + window = Confirmation_dialog(_("Are you sure you want to remove %s (%s) from your roster?") % (user.name, user.jid)) + if window.get_response() == gtk.RESPONSE_YES: + self.plugin.send('UNSUB', account, user.jid) + for u in self.contacts[account][user.jid]: + self.remove_user(u, account) + del self.contacts[account][u.jid] + + def send_status(self, account, status, txt, autoconnect=0): + if status != 'offline': + if not self.plugin.connected[account]: + model = self.tree.get_model() + accountIter = self.get_account_iter(account) + if accountIter: + model.set_value(accountIter, 0, self.pixbufs['connecting']) + self.plugin.systray.set_status('connecting') + + save_pass = 0 + if self.plugin.accounts[account].has_key('savepass'): + save_pass = self.plugin.accounts[account]['savepass'] + if not save_pass and not self.plugin.connected[account]: + passphrase = '' + w = Passphrase_dialog(_('Enter your password for account %s') \ + % account, 'Save password', autoconnect) + if autoconnect: + gtk.main() + passphrase, save = w.get_pass() + else: + passphrase, save = w.run() + if passphrase == -1: + if accountIter: + model.set_value(accountIter, 0, self.pixbufs['offline']) + self.set_cb() + return + self.plugin.send('PASSPHRASE', account, passphrase) + if save: + self.plugin.accounts[account]['savepass'] = 1 + self.plugin.accounts[account]['password'] = passphrase + + keyid = None + save_gpg_pass = 0 + if self.plugin.accounts[account].has_key('savegpgpass'): + save_gpg_pass = self.plugin.accounts[account]['savegpgpass'] + if self.plugin.accounts[account].has_key('keyid'): + keyid = self.plugin.accounts[account]['keyid'] + if keyid and not self.plugin.connected[account] and \ + self.plugin.config['usegpg']: + if save_gpg_pass: + passphrase = self.plugin.accounts[account]['gpgpassword'] + else: + passphrase = '' + w = Passphrase_dialog(\ + _('Enter GPG key passphrase for account %s') % account, \ + 'Save passphrase', autoconnect) + if autoconnect: + gtk.main() + passphrase, save = w.get_pass() + else: + passphrase, save = w.run() + if passphrase == -1: + passphrase = '' + if save: + self.plugin.accounts[account]['savegpgpass'] = 1 + self.plugin.accounts[account]['gpgpassword'] = passphrase + self.plugin.send('GPGPASSPHRASE', account, passphrase) + self.plugin.send('STATUS', account, (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] + self.plugin.send('GC_STATUS', account, (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, status): + if (status == 'online' and not self.plugin.config['ask_online_status']) \ + or (status == 'offline' and not \ + self.plugin.config['ask_offline_status']): + return status + w = Away_message_dialog(self.plugin) + message = w.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_cb_changed(self, widget): + """When we change our status""" + model = self.cb.get_model() + active = self.cb.get_active() + if active < 0: + return + accounts = self.plugin.accounts.keys() + if len(accounts) == 0: + Error_dialog(_("You must setup an account before connecting to jabber network.")) + self.set_cb() + return + status = model[active][0] + message = self.get_status_message(status) + if message == -1: + self.set_cb() + return + for acct in accounts: + if self.plugin.accounts[acct].has_key('sync_with_global_status'): + if not self.plugin.accounts[acct]['sync_with_global_status']: + continue + self.send_status(acct, status, message) + + def set_cb(self): + #table to change index in plugin.connected to index in combobox + table = {0:5, 1:0, 2:1, 3:2, 4:3, 5:4} + maxi = 0 + if len(self.plugin.connected.values()): + maxi = max(self.plugin.connected.values()) + #temporarily block signal in order not to send status that we show + #in the combobox + self.cb.handler_block(self.id_signal_cb) + self.cb.set_active(table[maxi]) + self.cb.handler_unblock(self.id_signal_cb) + statuss = ['offline', 'online', 'away', 'xa', 'dnd', 'invisible'] + self.plugin.systray.set_status(statuss[maxi]) + image = self.pixbufs[statuss[maxi]] + if image.get_storage_type() == gtk.IMAGE_ANIMATION: + pixbuf = image.get_animation().get_static_image() + self.window.set_icon(pixbuf) + elif image.get_storage_type() == gtk.IMAGE_PIXBUF: + self.window.set_icon(image.get_pixbuf()) + + 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.pixbufs[status]) + statuss = ['offline', 'online', 'away', 'xa', 'dnd', 'invisible'] + if status == 'offline': + for jid in self.contacts[account]: + luser = self.contacts[account][jid] + for user in luser: + self.chg_user_status(user, 'offline', 'Disconnected', account) + self.plugin.connected[account] = statuss.index(status) + self.set_cb() + + def new_chat(self, user, account): + if self.plugin.config['usetabbedchat']: + if not self.plugin.windows[account]['chats'].has_key('tabbed'): + self.plugin.windows[account]['chats']['tabbed'] = \ + 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'] + self.plugin.windows[account]['chats']['tabbed'].window.present() + else: + self.plugin.windows[account]['chats'][user.jid] = \ + tabbed_chat_window(user, self.plugin, account) + + def new_group(self, jid, nick, account): + if self.plugin.config['usetabbedchat']: + if not self.plugin.windows[account]['gc'].has_key('tabbed'): + self.plugin.windows[account]['gc']['tabbed'] = \ + Groupchat_window(jid, nick, self.plugin, account) + else: + self.plugin.windows[account]['gc']['tabbed'].new_group(jid, nick) + self.plugin.windows[account]['gc'][jid] = \ + self.plugin.windows[account]['gc']['tabbed'] + self.plugin.windows[account]['gc']['tabbed'].window.present() + else: + self.plugin.windows[account]['gc'][user.jid] = \ + Groupchat_window(jid, nick, self.plugin, account) + + def on_message(self, jid, msg, tim, account): + """when we receive a message""" + if not self.contacts[account].has_key(jid): + user1 = User(jid, jid, ['not in the roster'], \ + 'not in the roster', 'not in the roster', 'none', None, '', 0, '') + 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 = self.plugin.config['autopopup'] + autopopupaway = self.plugin.config['autopopupaway'] + if (autopopup == 0 or ( not autopopupaway and \ + self.plugin.connected[account] > 1)) and not \ + self.plugin.windows[account]['chats'].has_key(jid): + #We save it in a queue + if not self.plugin.queues[account].has_key(jid): + model = self.tree.get_model() + self.plugin.queues[account][jid] = Queue.Queue(50) + self.redraw_jid(jid, account) + self.plugin.systray.add_jid(jid, account) + self.plugin.queues[account][jid].put((msg, tim)) + self.nb_unread += 1 + self.show_title() + 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) + self.plugin.windows[account]['chats'][jid].print_conversation(msg, \ + jid, tim = tim) + if not self.plugin.windows[account]['chats'][jid].window.\ + get_property('is-active'): + self.plugin.systray.add_jid(jid, account) + + def on_preferences_menuitem_activate(self, widget): + """When preferences is selected : + call the preferences_window class""" + if not self.plugin.windows.has_key('preferences'): + self.plugin.windows['preferences'] = preferences_window(self.plugin) + + def on_add_contact(self, widget, account): + """When add user is selected : + call the add_contact_window class""" + add_contact_window(self.plugin, account) + + def on_join_gc(self, widget, account): + """When Join Groupchat is selected : + call the join_gc class""" + join_groupchat_window(self.plugin, account) + + def on_new_message_menuitem_activate(self, widget, account): + """When new message menuitem is activated: + call the New_message_dialog class""" + New_message_dialog(self.plugin, account) + + def on_about_menuitem_activate(self, widget): + """When about is selected : + call the about class""" + About_dialog(self.plugin) + + def on_accounts_menuitem_activate(self, widget): + """When accounts is seleted : + call the accounts class to modify accounts""" + if not self.plugin.windows.has_key('accounts_window'): + self.plugin.windows['accounts_window'] = 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_gajim_window_delete_event(self, widget, event): + """When we want to close the window""" + if self.plugin.systray_visible: + self.window.iconify() + else: + self.quit_gtkgui_plugin() + return 1 + + def quit_gtkgui_plugin(self): + """When we quit the gtk plugin : + tell that to the core and exit gtk""" + if self.plugin.config.has_key('saveposition'): + if self.plugin.config['saveposition']: + self.plugin.config['x-position'], self.plugin.config['y-position']=\ + self.window.get_position() + self.plugin.config['width'], self.plugin.config['height'] = \ + self.window.get_size() + + self.plugin.config['hiddenlines'] = '\t'.join(self.hidden_lines) + self.plugin.send('CONFIG', None, ('GtkGui', self.plugin.config, 'GtkGui')) + self.plugin.send('QUIT', None, ('gtkgui', 1)) + print _("plugin gtkgui stopped") + self.close_all(self.plugin.windows) + self.plugin.hide_systray() + gtk.main_quit() + + def on_quit_menuitem_activate(self, widget): + self.quit_gtkgui_plugin() + + def on_roster_treeview_row_activated(self, widget, path, col=0): + """When an iter is dubble 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): + if self.plugin.config['usetabbedchat']: + self.plugin.windows[account]['chats'][jid].active_tab(jid) + self.plugin.windows[account]['chats'][jid].window.present() + elif self.contacts[account].has_key(jid): + self.new_chat(self.contacts[account][jid][0], account) + self.plugin.windows[account]['chats'][jid].active_tab(jid) + + 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.pixbufs['opened']) + jid = model.get_value(iter, 3) + self.groups[account][jid]['expand'] = True + if account+jid in self.hidden_lines: + self.hidden_lines.remove(account+jid) + elif type == 'account': + if account in self.hidden_lines: + self.hidden_lines.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.pixbufs['closed']) + jid = model.get_value(iter, 3) + self.groups[account][jid]['expand'] = False + if not account+jid in self.hidden_lines: + self.hidden_lines.append(account+jid) + elif type == 'account': + if not account in self.hidden_lines: + self.hidden_lines.append(account) + + def on_editing_canceled (self, cell): + """editing have 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 + self.plugin.send('UPDUSER', account, (jid, new_text, \ + self.contacts[account][jid][0].groups)) + self.redraw_jid(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) + self.plugin.send('UPDUSER', account, (user.jid, user.name, \ + user.groups)) + model.set_value(iter, 5, False) + + def on_browse_agents(self, widget, account): + """When browse agent is selected : + Call browse class""" + if not self.plugin.windows[account].has_key('browser'): + self.plugin.windows[account]['browser'] = \ + agent_browser_window(self.plugin, account) + + def mkpixbufs(self): + """initialise pixbufs array""" + iconstyle = self.plugin.config['iconstyle'] + if not iconstyle: + iconstyle = 'sun' + self.path = 'plugins/gtkgui/icons/' + iconstyle + '/' + self.pixbufs = {} + 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(self.path + state_file + '.gif') + files.append(self.path + state_file + '.png') + files.append(self.path + state_file + '.xpm') + image = gtk.Image() + image.show() + self.pixbufs[state] = image + for file in files: + if not os.path.exists(file): + continue + image.set_from_file(file) + break + + def sound_is_ok(self, sound): + if not os.path.exists(sound): + return 0 + return 1 + + def on_show_offline_contacts_menuitem_activate(self, widget): + """when show offline option is changed: + redraw the treeview""" + self.plugin.config['showoffline'] = 1 - self.plugin.config['showoffline'] + self.plugin.send('CONFIG', None, ('GtkGui', self.plugin.config, 'GtkGui')) + 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', \ + self.plugin.config['accountbgcolor']) + renderer.set_property('xalign', 0) + elif model.get_value(iter, 2) == 'group': + renderer.set_property('cell-background', \ + self.plugin.config['groupbgcolor']) + renderer.set_property('xalign', 0.3) + else: + renderer.set_property('cell-background', \ + self.plugin.config['userbgcolor']) + renderer.set_property('xalign', 1) + renderer.set_property('width', 30) + + 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', \ + self.plugin.config['accounttextcolor']) + renderer.set_property('cell-background', \ + self.plugin.config['accountbgcolor']) + renderer.set_property('font', self.plugin.config['accountfont']) + renderer.set_property('xpad', 0) + elif model.get_value(iter, 2) == 'group': + renderer.set_property('foreground', \ + self.plugin.config['grouptextcolor']) + renderer.set_property('cell-background', \ + self.plugin.config['groupbgcolor']) + renderer.set_property('font', self.plugin.config['groupfont']) + renderer.set_property('xpad', 8) + else: + renderer.set_property('foreground', \ + self.plugin.config['usertextcolor']) + renderer.set_property('cell-background', \ + self.plugin.config['userbgcolor']) + renderer.set_property('font', self.plugin.config['userfont']) + renderer.set_property('xpad', 16) + + 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 + type = model.get_value(iter1, 2) + if type == 'group': + if name1 == 'Agents': + return 1 + if name2 == 'Agents': + 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): + 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\ + and path_dest[1] == 0: #droped before the first group + return + if position == gtk.TREE_VIEW_DROP_BEFORE and len(path_dest) == 2: + #droped before a group : we drop it in the previous group + path_dest = (path_dest[0], path_dest[1]-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: #droped on an account + return + if path_dest[0] != path_source[0]: #droped in another account + return + grp_source = model.get_value(model.iter_parent(iter_source), 3) + if grp_source == 'Agents': + return + account = model.get_value(model.get_iter(path_dest[0]), 3) + if len(path_dest) == 2: + grp_dest = model.get_value(iter_dest, 3) + elif len(path_dest) == 3: + grp_dest = model.get_value(model.iter_parent(iter_dest), 3) + if grp_source == grp_dest: + return + for u in self.contacts[account][data]: + u.groups.remove(grp_source) + u.groups.append(grp_dest) + self.plugin.send('UPDUSER', account, (u.jid, u.name, u.groups)) + parent_i = model.iter_parent(iter_source) + if model.iter_n_children(parent_i) == 1: #this was the only child + model.remove(parent_i) + self.add_user_to_roster(data, account) + if context.action == gtk.gdk.ACTION_MOVE: + context.finish(True, True, etime) + return + + def show_title(self): + 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, 'gajim_window', APP) + self.window = self.xml.get_widget('gajim_window') + self.tree = self.xml.get_widget('roster_treeview') + self.plugin = plugin + self.nb_unread = 0 + self.add_contact_handler_id = 0 + self.browse_agents_handler_id = 0 + self.join_gc_handler_id = 0 + self.new_message_menuitem_handler_id = 0 + self.regroup = 0 + if self.plugin.config.has_key('mergeaccounts'): + self.regroup = self.plugin.config['mergeaccounts'] + if self.plugin.config.has_key('saveposition'): + self.window.hide() + if self.plugin.config['saveposition']: + if self.plugin.config.has_key('x-position') and \ + self.plugin.config.has_key('y-position'): + self.window.move(self.plugin.config['x-position'], \ + self.plugin.config['y-position']) + if self.plugin.config.has_key('width') and \ + self.plugin.config.has_key('height'): + self.window.resize(self.plugin.config['width'], \ + self.plugin.config['height']) + self.window.show_all() + self.groups = {} + self.contacts = {} + for a in self.plugin.accounts.keys(): + self.contacts[a] = {} + self.groups[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.mkpixbufs() + + liststore = gtk.ListStore(gobject.TYPE_STRING, gtk.Image) + self.cb = gtk.ComboBox() + self.xml.get_widget('vbox1').pack_end(self.cb, False) + cell = ImageCellRenderer() + self.cb.pack_start(cell, False) + self.cb.add_attribute(cell, 'image', 1) + cell = gtk.CellRendererText() + self.cb.pack_start(cell, True) + self.cb.add_attribute(cell, 'text', 0) + for status in ['online', 'away', 'xa', 'dnd', 'invisible', 'offline']: + iter = liststore.append([status, self.pixbufs[status]]) + self.cb.show_all() + self.cb.set_model(liststore) + self.cb.set_active(5) + + showOffline = self.plugin.config['showoffline'] + self.xml.get_widget('show_offline_contacts_menuitem').set_active(showOffline) + + #columns + col = gtk.TreeViewColumn() + self.tree.append_column(col) + render_pixbuf = ImageCellRenderer() + 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) + #need gtk2.4 + #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) + + 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.cb.connect('changed', self.on_cb_changed) + + self.hidden_lines = self.plugin.config['hiddenlines'].split('\t') + self.draw_roster() + if len(self.plugin.accounts) == 0: + self.plugin.windows['accounts_window'] = Accounts_window(self.plugin) + self.plugin.windows['account_modification_window'] = \ + Account_modification_window(self.plugin, {}) diff --git a/plugins/gtkgui/systray.py b/plugins/gtkgui/systray.py new file mode 100644 index 000000000..3ccacafd5 --- /dev/null +++ b/plugins/gtkgui/systray.py @@ -0,0 +1,208 @@ +## plugins/systray.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - Nikos Kouremenos +## - Alex Podaras +## +## 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 + +from common import i18n + +_ = i18n._ +APP = i18n.APP +gtk.glade.bindtextdomain(APP, i18n.DIR) +gtk.glade.textdomain(APP) + +GTKGUI_GLADE='plugins/gtkgui/gtkgui.glade' + +class systrayDummy: + """Class when we don't want icon in the systray""" + def add_jid(self, jid, account): + pass + def remove_jid(self, jid, account): + pass + def set_status(self, status): + pass + def show_icon(self): + pass + def hide_icon(self): + pass + def __init__(self): + self.t = gtk.Button() + + +class systray: + """Class for icon in the systray""" + def set_img(self): + if len(self.jids) > 0: + status = 'message' + else: + status = self.status + image = self.plugin.roster.pixbufs[status] + if image.get_storage_type() == gtk.IMAGE_ANIMATION: + self.img_tray.set_from_animation(image.get_animation()) + elif image.get_storage_type() == gtk.IMAGE_PIXBUF: + self.img_tray.set_from_pixbuf(image.get_pixbuf()) + + def add_jid(self, jid, account): + list = [account, jid] + if not list in self.jids: + self.jids.append(list) + self.set_img() + + def remove_jid(self, jid, account): + list = [account, jid] + if list in self.jids: + self.jids.remove(list) + self.set_img() + + def set_status(self, status): + self.status = status + self.set_img() + + def set_cb(self, widget, status): + statuss = ['online', 'away', 'xa', 'dnd', 'invisible', 'offline'] + self.plugin.roster.cb.set_active(statuss.index(status)) + + def start_chat(self, widget, account, jid): + if self.plugin.windows[account]['chats'].has_key(jid): + self.plugin.windows[account]['chats'][jid].window.present() + elif self.plugin.roster.contacts[account].has_key(jid): + self.plugin.roster.new_chat( + self.plugin.roster.contacts[account][jid][0], account) + + def mk_menu(self, event): + menu = gtk.Menu() + item = gtk.TearoffMenuItem() + menu.append(item) + + 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.set_cb, 'online') + item = gtk.MenuItem(_("Away")) + sub_menu.append(item) + item.connect("activate", self.set_cb, 'away') + item = gtk.MenuItem(_("NA")) + sub_menu.append(item) + item.connect("activate", self.set_cb, 'xa') + item = gtk.MenuItem(_("DND")) + sub_menu.append(item) + item.connect("activate", self.set_cb, 'dnd') + item = gtk.MenuItem(_("Invisible")) + sub_menu.append(item) + item.connect("activate", self.set_cb, 'invisible') + item = gtk.MenuItem() + sub_menu.append(item) + item = gtk.MenuItem(_("Offline")) + sub_menu.append(item) + item.connect("activate", self.set_cb, 'offline') + + item = gtk.MenuItem() + menu.append(item) + + item = gtk.MenuItem(_("Chat with")) + menu.append(item) + menu_account = gtk.Menu() + item.set_submenu(menu_account) + for account in self.plugin.accounts.keys(): + item = gtk.MenuItem(account) + menu_account.append(item) + menu_group = gtk.Menu() + item.set_submenu(menu_group) + for group in self.plugin.roster.groups[account].keys(): + if group == 'Agents': + continue + item = gtk.MenuItem(group) + menu_group.append(item) + menu_user = gtk.Menu() + item.set_submenu(menu_user) + for users in self.plugin.roster.contacts[account].values(): + user = users[0] + if group in user.groups and user.show != 'offline' and \ + user.show != 'error': + item = gtk.MenuItem(user.name.replace('_', '__')) + menu_user.append(item) + item.connect("activate", self.start_chat, account, user.jid) + + item = gtk.MenuItem() + menu.append(item) + + item = gtk.MenuItem(_("Quit")) + menu.append(item) + item.connect("activate", self.plugin.roster.on_quit_menuitem_activate) + + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def on_clicked(self, widget, event): + if event.type == gtk.gdk.BUTTON_PRESS and event.button == 1: + if len(self.jids) == 0: + win = self.plugin.roster.window + if win.iconify_initially: + win.deiconify() + else: + if win.is_active(): + win.iconify() + else: + win.present() + else: + account = self.jids[0][0] + jid = self.jids[0][1] + if self.plugin.windows[account]['gc'].has_key(jid): + self.plugin.windows[account]['gc'][jid].active_tab(jid) + self.plugin.windows[account]['gc'][jid].window.present() + elif self.plugin.windows[account]['chats'].has_key(jid): + self.plugin.windows[account]['chats'][jid].active_tab(jid) + self.plugin.windows[account]['chats'][jid].window.present() + else: + self.plugin.roster.new_chat( + self.plugin.roster.contacts[account][jid][0], account) + if event.button == 3: + self.mk_menu(event) + + def show_icon(self): + if not self.t: + self.t = trayicon.TrayIcon("Gajim") + eb = gtk.EventBox() + eb.connect("button-press-event", self.on_clicked) + self.tip = gtk.Tooltips() + self.tip.set_tip(self.t, 'Gajim') + self.img_tray = gtk.Image() + eb.add(self.img_tray) + self.t.add(eb) + self.set_img() + self.t.show_all() + + def hide_icon(self): + if self.t: + self.t.destroy() + self.t = None + + def __init__(self, plugin): + self.plugin = plugin + self.jids = [] + self.t = None + self.img_tray = gtk.Image() + self.status = 'offline' + global trayicon + import trayicon diff --git a/plugins/gtkgui/tabbed_chat_window.py b/plugins/gtkgui/tabbed_chat_window.py new file mode 100644 index 000000000..0013acb1c --- /dev/null +++ b/plugins/gtkgui/tabbed_chat_window.py @@ -0,0 +1,686 @@ +## plugins/tabbed_chat_window.py +## +## Gajim Team: +## - Yann Le Boulanger +## - Vincent Hanquez +## - Nikos Kouremenos +## - Alex Podaras +## +## 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 time +import sre + +from dialogs import * + +from common import i18n + +_ = i18n._ +APP = i18n.APP +gtk.glade.bindtextdomain(APP, i18n.DIR) +gtk.glade.textdomain(APP) + +GTKGUI_GLADE='plugins/gtkgui/gtkgui.glade' + +class tabbed_chat_window: + """Class for tabbed chat window""" + def __init__(self, user, plugin, account): + self.xml = gtk.glade.XML(GTKGUI_GLADE, 'tabbed_chat_window', APP) + self.chat_notebook = self.xml.get_widget('chat_notebook') + self.chat_notebook.remove_page(0) + self.plugin = plugin + self.account = account + self.change_cursor = None + self.xmls = {} + self.tagIn = {} + self.tagOut = {} + self.tagStatus = {} + self.users = {} + self.nb_unread = {} + self.last_message_time = {} + self.print_time_timeout_id = {} + self.window = self.xml.get_widget('tabbed_chat_window') + self.new_user(user) + self.show_title() + self.xml.signal_connect('on_tabbed_chat_window_destroy', \ + self.on_tabbed_chat_window_destroy) + self.xml.signal_connect('on_tabbed_chat_window_delete_event', \ + self.on_tabbed_chat_window_delete_event) + self.xml.signal_connect('on_tabbed_chat_window_focus_in_event', \ + self.on_tabbed_chat_window_focus_in_event) + self.xml.signal_connect('on_tabbed_chat_window_key_press_event', \ + self.on_tabbed_chat_window_key_press_event) + self.xml.signal_connect('on_chat_notebook_switch_page', \ + self.on_chat_notebook_switch_page) + + def update_tags(self): + for jid in self.tagIn: + self.tagIn[jid].set_property("foreground", \ + self.plugin.config['inmsgcolor']) + self.tagOut[jid].set_property("foreground", \ + self.plugin.config['outmsgcolor']) + self.tagStatus[jid].set_property("foreground", \ + self.plugin.config['statusmsgcolor']) + + def update_print_time(self): + if self.plugin.config['print_time'] != 'sometimes': + list_jid = self.print_time_timeout_id.keys() + for jid in list_jid: + gobject.source_remove(self.print_time_timeout_id[jid]) + del self.print_time_timeout_id[jid] + else: + for jid in self.xmls: + if not self.print_time_timeout_id.has_key(jid): + self.print_time_timeout(jid) + self.print_time_timeout_id[jid] = gobject.timeout_add(300000, \ + self.print_time_timeout, jid) + + def show_title(self): + """redraw the window's title""" + unread = 0 + for jid in self.nb_unread: + unread += self.nb_unread[jid] + start = "" + if unread > 1: + start = "[" + str(unread) + "] " + elif unread == 1: + start = "* " + chat = self.users[jid].name + if len(self.xmls) > 1: + chat = 'Chat' + self.window.set_title(start + chat + ' (' + self.account + ')') + + def draw_widgets(self, user): + """draw the widgets in a tab (status_image, contact_button ...) + according to the the information in the user variable""" + jid = user.jid + status_image = self.xmls[jid].get_widget('status_image') + image = self.plugin.roster.pixbufs[user.show] + if image.get_storage_type() == gtk.IMAGE_ANIMATION: + status_image.set_from_animation(image.get_animation()) + elif image.get_storage_type() == gtk.IMAGE_PIXBUF: + status_image.set_from_pixbuf(image.get_pixbuf()) + contact_button = self.xmls[jid].get_widget('contact_button') + contact_button.set_label(user.name + ' <' + jid + '>') + if not user.keyID: + self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(False) + + def redraw_tab(self, jid): + """redraw the label of the tab""" + start = '' + if self.nb_unread[jid] > 1: + start = "[" + str(self.nb_unread[jid]) + "] " + elif self.nb_unread[jid] == 1: + start = "* " + child = self.xmls[jid].get_widget('chat_vbox') + self.chat_notebook.set_tab_label_text(child, start + self.users[jid].name) + + def set_image(self, image, jid): + if image.get_storage_type() == gtk.IMAGE_ANIMATION: + self.xmls[jid].get_widget('status_image').\ + set_from_animation(image.get_animation()) + elif image.get_storage_type() == gtk.IMAGE_PIXBUF: + self.xmls[jid].get_widget('status_image').\ + set_from_pixbuf(image.get_pixbuf()) + + def on_tabbed_chat_window_delete_event(self, widget, event): + """close window""" + for jid in self.users: + if time.time() - self.last_message_time[jid] < 2: # 2 seconds + dialog = Confirmation_dialog(_('You received a message from %s in the last two seconds.\nDo you still want to close this window ?') % jid) + if dialog.get_response() != gtk.RESPONSE_YES: + return True #stop the propagation of the event + + def on_tabbed_chat_window_destroy(self, widget): + #clean self.plugin.windows[self.account]['chats'] + for jid in self.users: + del self.plugin.windows[self.account]['chats'][jid] + if self.print_time_timeout_id.has_key(jid): + gobject.source_remove(self.print_time_timeout_id[jid]) + if self.plugin.windows[self.account]['chats'].has_key('tabbed'): + del self.plugin.windows[self.account]['chats']['tabbed'] + + def get_active_jid(self): + active_child = self.chat_notebook.get_nth_page(\ + self.chat_notebook.get_current_page()) + active_jid = '' + for jid in self.xmls: + child = self.xmls[jid].get_widget('chat_vbox') + if child == active_child: + active_jid = jid + break + return active_jid + + def on_clear_button_clicked(self, widget): + """When clear button is pressed : + clear the conversation""" + jid = self.get_active_jid() + conversation_buffer = self.xmls[jid].get_widget('conversation_textview').\ + get_buffer() + start, end = conversation_buffer.get_bounds() + conversation_buffer.delete(start, end) + + def on_close_button_clicked(self, button): + """When close button is pressed : + close a tab""" + jid = self.get_active_jid() + self.remove_tab(jid) + + def on_tabbed_chat_window_focus_in_event(self, widget, event): + """When window get focus""" + jid = self.get_active_jid() + conversation_textview = self.xmls[jid].\ + get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + end_iter = conversation_buffer.get_end_iter() + end_rect = conversation_textview.get_iter_location(end_iter) + visible_rect = conversation_textview.get_visible_rect() + if end_rect.y <= (visible_rect.y + visible_rect.height): + #we are at the end + if self.nb_unread[jid] > 0: + self.nb_unread[jid] = 0 + self.redraw_tab(jid) + self.show_title() + self.plugin.systray.remove_jid(jid, self.account) + + def on_history_button_clicked(self, widget): + """When history button is pressed : call history window""" + jid = self.get_active_jid() + if not self.plugin.windows['logs'].has_key(jid): + self.plugin.windows['logs'][jid] = history_window(self.plugin, jid) + + def on_chat_notebook_switch_page(self, notebook, page, page_num): + new_child = notebook.get_nth_page(page_num) + new_jid = '' + for jid in self.xmls: + child = self.xmls[jid].get_widget('chat_vbox') + if child == new_child: + new_jid = jid + break + conversation_textview = self.xmls[new_jid].\ + get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + end_iter = conversation_buffer.get_end_iter() + end_rect = conversation_textview.get_iter_location(end_iter) + visible_rect = conversation_textview.get_visible_rect() + if end_rect.y <= (visible_rect.y + visible_rect.height): + #we are at the end + if self.nb_unread[new_jid] > 0: + self.nb_unread[new_jid] = 0 + self.redraw_tab(new_jid) + self.show_title() + self.plugin.systray.remove_jid(new_jid, self.account) + + def active_tab(self, jid): + child = self.xmls[jid].get_widget('chat_vbox') + self.chat_notebook.set_current_page(\ + self.chat_notebook.page_num(child)) + + def remove_tab(self, jid): + if time.time() - self.last_message_time[jid] < 2: + dialog = Confirmation_dialog(_('You received a message from %s in the last two seconds.\nDo you still want to close this tab ?') % jid) + if dialog.get_response() != gtk.RESPONSE_YES: + return + + if len(self.xmls) == 1: + self.window.destroy() + else: + if self.print_time_timeout_id.has_key(jid): + gobject.source_remove(self.print_time_timeout_id[jid]) + del self.print_time_timeout_id[jid] + self.chat_notebook.remove_page(\ + self.chat_notebook.get_current_page()) + del self.plugin.windows[self.account]['chats'][jid] + del self.users[jid] + del self.nb_unread[jid] + del self.last_message_time[jid] + del self.xmls[jid] + del self.tagIn[jid] + del self.tagOut[jid] + del self.tagStatus[jid] + if len(self.xmls) == 1: + self.chat_notebook.set_show_tabs(False) + self.show_title() + + def new_user(self, user): + self.nb_unread[user.jid] = 0 + self.last_message_time[user.jid] = 0 + self.users[user.jid] = user + self.xmls[user.jid] = gtk.glade.XML(GTKGUI_GLADE, 'chat_vbox', APP) + + conversation_textview = \ + self.xmls[user.jid].get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + end_iter = conversation_buffer.get_end_iter() + conversation_buffer.create_mark('end', end_iter, 0) + self.tagIn[user.jid] = conversation_buffer.create_tag('incoming') + color = self.plugin.config['inmsgcolor'] + self.tagIn[user.jid].set_property('foreground', color) + self.tagOut[user.jid] = conversation_buffer.create_tag('outgoing') + color = self.plugin.config['outmsgcolor'] + self.tagOut[user.jid].set_property('foreground', color) + self.tagStatus[user.jid] = conversation_buffer.create_tag('status') + color = self.plugin.config['statusmsgcolor'] + self.tagStatus[user.jid].set_property('foreground', color) + + tag = conversation_buffer.create_tag('time_sometimes') + tag.set_property('foreground', '#9e9e9e') + tag.set_property('scale', pango.SCALE_SMALL) + tag.set_property('justification', gtk.JUSTIFY_CENTER) + + tag = conversation_buffer.create_tag('url') + tag.set_property('foreground', '#0000ff') + tag.set_property('underline', pango.UNDERLINE_SINGLE) + tag.connect('event', self.hyperlink_handler, 'url') + + tag = conversation_buffer.create_tag('mail') + tag.set_property('foreground', '#0000ff') + tag.set_property('underline', pango.UNDERLINE_SINGLE) + tag.connect('event', self.hyperlink_handler, 'mail') + + tag = conversation_buffer.create_tag('bold') + tag.set_property('weight', pango.WEIGHT_BOLD) + + tag = conversation_buffer.create_tag('italic') + tag.set_property('style', pango.STYLE_ITALIC) + + tag = conversation_buffer.create_tag('underline') + tag.set_property('underline', pango.UNDERLINE_SINGLE) + + self.xmls[user.jid].signal_autoconnect(self) + conversation_scrolledwindow = self.xmls[user.jid].\ + get_widget('conversation_scrolledwindow') + conversation_scrolledwindow.get_vadjustment().connect('value-changed', \ + self.on_conversation_vadjustment_value_changed) + + self.chat_notebook.append_page(self.xmls[user.jid].\ + get_widget('chat_vbox')) + if len(self.xmls) > 1: + self.chat_notebook.set_show_tabs(True) + + self.redraw_tab(user.jid) + self.draw_widgets(user) + self.show_title() + + #print queued messages + if self.plugin.queues[self.account].has_key(user.jid): + self.read_queue(user.jid) + if user.show != 'online': + self.print_conversation(_("%s is now %s (%s)") % (user.name, \ + user.show, user.status), user.jid, 'status') + + if self.plugin.config['print_time'] == 'sometimes': + self.print_time_timeout(user.jid) + self.print_time_timeout_id[user.jid] = gobject.timeout_add(300000, \ + self.print_time_timeout, user.jid) + + def on_message_textview_key_press_event(self, widget, event): + """When a key is pressed : + if enter is pressed without the shit key, message (if not empty) is sent + and printed in the conversation""" + if event.keyval == gtk.keysyms.Return: + if (event.state & gtk.gdk.SHIFT_MASK): + return 0 + message_buffer = widget.get_buffer() + start_iter = message_buffer.get_start_iter() + end_iter = message_buffer.get_end_iter() + message = message_buffer.get_text(start_iter, end_iter, 0) + if message != '': + keyID = '' + jid = self.get_active_jid() + if self.xmls[jid].get_widget('gpg_togglebutton').get_active(): + keyID = self.users[jid].keyID + self.plugin.send('MSG', self.account, (jid, message, keyID)) + message_buffer.set_text('', -1) + self.print_conversation(message, jid, jid) + return 1 + return 0 + + def on_tabbed_chat_window_key_press_event(self, widget, event): + st = '1234567890' # zero is here cause humans count from 1, pc from 0 :P + jid = self.get_active_jid() + if event.keyval == gtk.keysyms.Escape: # ESCAPE + self.remove_tab(jid) + elif event.string and event.string in st \ + and (event.state & gtk.gdk.MOD1_MASK): # alt + 1,2,3.. + self.chat_notebook.set_current_page(st.index(event.string)) + elif event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN + if event.state & gtk.gdk.CONTROL_MASK: + current = self.chat_notebook.get_current_page() + if current > 0: + self.chat_notebook.set_current_page(current-1) +# else: +# self.chat_notebook.set_current_page(\ +# self.chat_notebook.get_n_pages()-1) + elif event.state & gtk.gdk.SHIFT_MASK: + conversation_textview = self.xmls[jid].\ + get_widget('conversation_textview') + rect = conversation_textview.get_visible_rect() + iter = conversation_textview.get_iter_at_location(rect.x,\ + rect.y + rect.height) + conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 0) + elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP + if event.state & gtk.gdk.CONTROL_MASK: + current = self.chat_notebook.get_current_page() + if current < (self.chat_notebook.get_n_pages()-1): + self.chat_notebook.set_current_page(current+1) +# else: +# self.chat_notebook.set_current_page(0) + elif event.state & gtk.gdk.SHIFT_MASK: + conversation_textview = self.xmls[jid].\ + get_widget('conversation_textview') + rect = conversation_textview.get_visible_rect() + iter = conversation_textview.get_iter_at_location(rect.x, rect.y) + conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 1) + elif event.keyval == gtk.keysyms.Tab and \ + (event.state & gtk.gdk.CONTROL_MASK): # CTRL + TAB + current = self.chat_notebook.get_current_page() + if current < (self.chat_notebook.get_n_pages()-1): + self.chat_notebook.set_current_page(current+1) + else: + self.chat_notebook.set_current_page(0) + elif (event.state & gtk.gdk.CONTROL_MASK) or (event.keyval ==\ + gtk.keysyms.Control_L) or (event.keyval == gtk.keysyms.Control_R): + # we pressed a control key or ctrl+sth : we don't block the event + # in order to let ctrl+c do its work + pass + else: # it's a normal key press make sure message_textview has focus + message_textview = self.xmls[jid].get_widget('message_textview') + if not message_textview.is_focus(): + message_textview.grab_focus() + + def on_contact_button_clicked(self, widget): + """When button contact is clicked""" + jid = self.get_active_jid() + user = self.users[jid] + self.plugin.roster.on_info(widget, user, self.account) + + def read_queue(self, jid): + """read queue and print messages containted in it""" + q = self.plugin.queues[self.account][jid] + user = self.users[jid] + while not q.empty(): + event = q.get() + self.print_conversation(event[0], jid, tim = event[1]) + self.plugin.roster.nb_unread -= 1 + self.plugin.roster.show_title() + del self.plugin.queues[self.account][jid] + self.plugin.roster.redraw_jid(jid, self.account) + self.plugin.systray.remove_jid(jid, self.account) + showOffline = self.plugin.config['showoffline'] + if (user.show == 'offline' or user.show == 'error') and \ + not showOffline: + if len(self.plugin.roster.contacts[self.account][jid]) == 1: + self.plugin.roster.remove_user(user, self.account) + + def on_conversation_vadjustment_value_changed(self, widget): + jid = self.get_active_jid() + if not self.nb_unread[jid]: + return + conversation_textview = self.xmls[jid].get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + end_iter = conversation_buffer.get_end_iter() + end_rect = conversation_textview.get_iter_location(end_iter) + visible_rect = conversation_textview.get_visible_rect() + if end_rect.y <= (visible_rect.y + visible_rect.height) and \ + self.window.is_active(): + #we are at the end + self.nb_unread[jid] = 0 + self.redraw_tab(jid) + self.show_title() + self.plugin.systray.remove_jid(jid, self.account) + + def on_conversation_textview_motion_notify_event(self, widget, event): + """change the cursor to a hand when we are on a mail or an url""" + x, y, spam = widget.window.get_pointer() + x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) + tags = widget.get_iter_at_location(x, y).get_tags() + if self.change_cursor: + widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(\ + gtk.gdk.Cursor(gtk.gdk.XTERM)) + self.change_cursor = None + for tag in tags: + if tag == widget.get_buffer().get_tag_table().lookup('url') or \ + tag == widget.get_buffer().get_tag_table().lookup('mail'): + widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(\ + gtk.gdk.Cursor(gtk.gdk.HAND2)) + self.change_cursor = tag + return False + + def on_conversation_textview_button_press_event(self, widget, event): + # Do not open the standard popup menu, so we block right button click + # on a taged text + if event.button == 3: + win = widget.get_window(gtk.TEXT_WINDOW_TEXT) + x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,\ + int(event.x), int(event.y)) + iter = widget.get_iter_at_location(x, y) + tags = iter.get_tags() + if tags: + return True + + def print_time_timeout(self, jid): + if not jid in self.xmls.keys(): + return 0 + if self.plugin.config['print_time'] == 'sometimes': + conversation_textview = self.xmls[jid].\ + get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + end_iter = conversation_buffer.get_end_iter() + tim = time.localtime() + tim_format = time.strftime('%H:%M', tim) + conversation_buffer.insert_with_tags_by_name(end_iter, tim_format + \ + '\n', 'time_sometimes') + #scroll to the end of the textview + end_rect = conversation_textview.get_iter_location(end_iter) + visible_rect = conversation_textview.get_visible_rect() + if end_rect.y <= (visible_rect.y + visible_rect.height): + #we are at the end + conversation_textview.scroll_to_mark(conversation_buffer.\ + get_mark('end'), 0.1, 0, 0, 0) + return 1 + if self.print_time_timeout_id.has_key(jid): + del self.print_time_timeout_id[jid] + return 0 + + def on_open_link_activated(self, widget, kind, text): + self.plugin.launch_browser_mailer(kind, text) + + def on_copy_link_activated(self, widget, text): + clip = gtk.clipboard_get() + clip.set_text(text) + + def make_link_menu(self, event, kind, text): + menu = gtk.Menu() + if kind == 'mail': + item = gtk.MenuItem(_('_Open email composer')) + else: + item = gtk.MenuItem(_('_Open link')) + item.connect('activate', self.on_open_link_activated, kind, text) + menu.append(item) + if kind == 'mail': + item = gtk.MenuItem(_('_Copy email address')) + else: # It's an url + item = gtk.MenuItem(_('_Copy link address')) + item.connect('activate', self.on_copy_link_activated, text) + menu.append(item) + + menu.popup(None, None, None, event.button, event.time) + menu.show_all() + menu.reposition() + + def hyperlink_handler(self, texttag, widget, event, iter, kind): + if event.type == gtk.gdk.BUTTON_RELEASE: + begin_iter = iter.copy() + #we get the begining of the tag + while not begin_iter.begins_tag(texttag): + begin_iter.backward_word_start() + end_iter = iter.copy() + #we get the end of the tag + while not end_iter.ends_tag(texttag): + end_iter.forward_word_end() + word = begin_iter.get_text(end_iter) + if event.button == 3: + self.make_link_menu(event, kind, word) + else: + #we launch the correct application + self.plugin.launch_browser_mailer(kind, word) + + def print_special_text(self, text, jid, other_tag): + conversation_textview = self.xmls[jid].get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + + # make it CAPS (emoticons keys are all CAPS) + possible_emot_ascii_caps = text.upper() + if possible_emot_ascii_caps in self.plugin.emoticons.keys(): + #it's an emoticon + emot_ascii = possible_emot_ascii_caps + print 'emoticon:', emot_ascii + end_iter = conversation_buffer.get_end_iter() + conversation_buffer.insert_pixbuf(end_iter, \ + self.plugin.emoticons[emot_ascii]) + return + elif text.startswith('mailto:'): + #it's a mail + tag = 'mail' + print tag + elif self.plugin.sth_at_sth_dot_sth_re.match(text): #returns match object + #or None + #it's a mail + tag = 'mail' + print tag + elif text.startswith('*') and text.endswith('*'): + #it's a bold text + tag = 'bold' + text = text[1:-1] # remove * * + elif text.startswith('/') and text.endswith('/'): + #it's an italic text + tag = 'italic' + text = text[1:-1] # remove / / + print tag + elif text.startswith('_') and text.endswith('_'): + #it's an underlined text + tag = 'underline' + text = text[1:-1] # remove _ _ + print tag + else: + #it's a url + tag = 'url' + print tag + + end_iter = conversation_buffer.get_end_iter() + if tag in ['bold', 'italic', 'underline'] and other_tag: + conversation_buffer.insert_with_tags_by_name(end_iter, text,\ + other_tag, tag) + else: + conversation_buffer.insert_with_tags_by_name(end_iter, text, tag) + + def print_conversation(self, text, jid, contact = '', tim = None): + """Print a line in the conversation : + if contact is set to status : it's a status message + if contact is set to another value : it's an outgoing message + if contact is not set : it's an incomming message""" + user = self.users[jid] + conversation_textview = self.xmls[jid].get_widget('conversation_textview') + conversation_buffer = conversation_textview.get_buffer() + print_all_special = False + if not text: + text = '' + end_iter = conversation_buffer.get_end_iter() + if self.plugin.config['print_time'] == 'always': + if not tim: + tim = time.localtime() + tim_format = time.strftime("[%H:%M:%S]", tim) + conversation_buffer.insert(end_iter, tim_format + ' ') + + otext = '' + ttext = '' + if contact == 'status': + tag = 'status' + ttext = text + '\n' + print_all_special = True + else: + if contact: + tag = 'outgoing' + name = self.plugin.nicks[self.account] + else: + tag = 'incoming' + name = user.name + self.last_message_time[jid] = time.time() + + if text.startswith('/me'): + ttext = name + text[3:] + '\n' + print_all_special = True + else: + ttext = '<' + name + '> ' + otext = text + '\n' + #if it's a status we print special words + if not print_all_special: + conversation_buffer.insert_with_tags_by_name(end_iter, ttext, tag) + else: + otext = ttext + + start = 0 + end = 0 + index = 0 + + if self.plugin.config['useemoticons']: # search for emoticons & urls + my_re = sre.compile(self.plugin.emot_and_basic_pattern, sre.IGNORECASE) + iterator = my_re.finditer(otext) + else: # search for just urls + my_re = sre.compile(self.plugin.basic_pattern, sre.IGNORECASE) + iterator = my_re.finditer(otext) + for match in iterator: + start, end = match.span() + special_text = otext[start:end] + if start != 0: + text_before_special_text = otext[index:start] + end_iter = conversation_buffer.get_end_iter() + if print_all_special: + conversation_buffer.insert_with_tags_by_name(end_iter, \ + text_before_special_text, tag) + else: + conversation_buffer.insert(end_iter, text_before_special_text) + if print_all_special: + self.print_special_text(special_text, jid, tag) + else: + self.print_special_text(special_text, jid, '') + index = end # update index + + #add the rest in the index and after + end_iter = conversation_buffer.get_end_iter() + if print_all_special: + conversation_buffer.insert_with_tags_by_name(end_iter, \ + otext[index:], tag) + else: + conversation_buffer.insert(end_iter, otext[index:]) + + #scroll to the end of the textview + end_rect = conversation_textview.get_iter_location(end_iter) + visible_rect = conversation_textview.get_visible_rect() + end = False + if end_rect.y <= (visible_rect.y + visible_rect.height) or \ + (contact and contact != 'status'): + #we are at the end or we are sending something + end = True + conversation_textview.scroll_to_mark(conversation_buffer.\ + get_mark('end'), 0.1, 0, 0, 0) + if ((jid != self.get_active_jid()) or (not self.window.is_active()) or \ + (not end)) and contact == '': + self.nb_unread[jid] += 1 + self.redraw_tab(jid) + self.show_title()