From ec24013da8b1af0684a50657fd423fb3327b6750 Mon Sep 17 00:00:00 2001 From: Travis Shirk Date: Tue, 10 Jan 2006 01:48:25 +0000 Subject: [PATCH] Out with the old. --- src/chat.py | 1120 ------------------------ src/groupchat_window.py | 1696 ------------------------------------- src/gtkgui.glade | 1426 ------------------------------- src/tabbed_chat_window.py | 965 --------------------- 4 files changed, 5207 deletions(-) delete mode 100644 src/chat.py delete mode 100644 src/groupchat_window.py delete mode 100644 src/tabbed_chat_window.py diff --git a/src/chat.py b/src/chat.py deleted file mode 100644 index c66ed1f4a..000000000 --- a/src/chat.py +++ /dev/null @@ -1,1120 +0,0 @@ -## chat.py -## -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## -## Copyright (C) 2003-2004 Yann Le Boulanger -## Vincent Hanquez -## Copyright (C) 2005 Yann Le Boulanger -## Vincent Hanquez -## Nikos Kouremenos -## Dimitur Kirov -## Travis Shirk -## Norman Rasmussen -## -## 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 math -import os - -import dialogs -import history_window -import gtkgui_helpers -import tooltips -import conversation_textview -import message_textview - -try: - import gtkspell - HAS_GTK_SPELL = True -except: - HAS_GTK_SPELL = False - -from common import gajim -from common import helpers -from common import i18n - -_ = i18n._ -APP = i18n.APP -gtk.glade.bindtextdomain(APP, i18n.DIR) -gtk.glade.textdomain(APP) - -GTKGUI_GLADE = 'gtkgui.glade' - -class Chat: - '''Class for chat/groupchat windows''' - def __init__(self, account, widget_name): - self.xml = gtk.glade.XML(GTKGUI_GLADE, widget_name, APP) - self.window = self.xml.get_widget(widget_name) - print type(self.window) - - self.widget_name = widget_name - - self.account = account - self.xmls = {} - self.conversation_textviews = {} # holds per jid conversation textview - self.message_textviews = {} # holds per jid message (where we write) textview - self.nb_unread = {} - self.print_time_timeout_id = {} - self.names = {} # what is printed in the tab (eg. contact.name) - self.childs = {} # holds the contents for every tab (VBox) - - # the following vars are used to keep history of user's messages - self.sent_history = {} - self.sent_history_pos = {} - self.typing_new = {} - self.orig_msg = {} - - # alignment before notebook (to control top padding for when showing tabs) - self.alignment = self.xml.get_widget('alignment') - - # notebook customizations - self.notebook = self.xml.get_widget('chat_notebook') - # Remove the page that is in glade - self.notebook.remove_page(0) - pref_pos = gajim.config.get('tabs_position') - if pref_pos != 'top': - if pref_pos == 'bottom': - nb_pos = gtk.POS_BOTTOM - elif pref_pos == 'left': - nb_pos = gtk.POS_LEFT - elif pref_pos == 'right': - nb_pos = gtk.POS_RIGHT - else: - nb_pos = gtk.POS_TOP - else: - nb_pos = gtk.POS_TOP - self.notebook.set_tab_pos(nb_pos) - if gajim.config.get('tabs_always_visible'): - self.notebook.set_show_tabs(True) - self.alignment.set_property('top-padding', 2) - else: - self.notebook.set_show_tabs(False) - self.notebook.set_show_border(gajim.config.get('tabs_border')) - - if gajim.config.get('useemoticons'): - self.emoticons_menu = self.prepare_emoticons_menu() - - # muc attention states (when we are mentioned in a muc) - # if the room jid is in the list, the room has mentioned us - self.muc_attentions = [] - - def toggle_emoticons(self): - '''hide show emoticons_button and make sure emoticons_menu is always there - when needed''' - if gajim.config.get('useemoticons'): - self.emoticons_menu = self.prepare_emoticons_menu() - - for jid in self.xmls: - emoticons_button = self.xmls[jid].get_widget('emoticons_button') - if gajim.config.get('useemoticons'): - emoticons_button.show() - emoticons_button.set_no_show_all(False) - else: - emoticons_button.hide() - emoticons_button.set_no_show_all(True) - - def update_font(self): - font = pango.FontDescription(gajim.config.get('conversation_font')) - for jid in self.xmls: - self.conversation_textviews[jid].modify_font(font) - msg_textview = self.message_textviews[jid] - msg_textview.modify_font(font) - - def update_tags(self): - for jid in self.conversation_textviews: - self.conversation_textviews[jid].update_tags() - - def update_print_time(self): - if gajim.config.get('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 print_time_timeout(self, jid): - if not jid in self.xmls.keys(): - return False - if gajim.config.get('print_time') == 'sometimes': - conv_textview = self.conversation_textviews[jid] - buffer = conv_textview.get_buffer() - end_iter = buffer.get_end_iter() - tim = time.localtime() - tim_format = time.strftime('%H:%M', tim) - buffer.insert_with_tags_by_name(end_iter, '\n' + tim_format, - 'time_sometimes') - # scroll to the end of the textview - if conv_textview.at_the_end(): - # we are at the end - conv_textview.scroll_to_end() - return True # loop again - if self.print_time_timeout_id.has_key(jid): - del self.print_time_timeout_id[jid] - return False - - def show_title(self, urgent = True): - '''redraw the window's title''' - unread = 0 - for jid in self.nb_unread: - unread += self.nb_unread[jid] - start = '' - if unread > 1: - start = '[' + unicode(unread) + '] ' - elif unread == 1: - start = '* ' - if len(self.xmls) > 1: # if more than one tab in the same window - if self.widget_name == 'tabbed_chat_window': - add = _('Chat') - elif self.widget_name == 'groupchat_window': - add = _('Group Chat') - elif len(self.xmls) == 1: # just one tab - if self.widget_name == 'tabbed_chat_window': - c = gajim.contacts.get_first_contact_from_jid(self.account, jid) - if c is None: - add = '' - else: - add = c.name - elif self.widget_name == 'groupchat_window': - name = gajim.get_nick_from_jid(jid) - add = name - - title = start + add - if len(gajim.connections) >= 2: # if we have 2 or more accounts - title += ' (' + _('account: ') + self.account + ')' - - self.window.set_title(title) - if urgent: - gtkgui_helpers.set_unset_urgency_hint(self.window, unread) - - def redraw_tab(self, jid, chatstate = None): - '''redraw the label of the tab - if chatstate is given that means we have HE SENT US a chatstate''' - # Update status images - self.set_state_image(jid) - - unread = '' - num_unread = 0 - child = self.childs[jid] - hb = self.notebook.get_tab_label(child).get_children()[0] - if self.widget_name == 'tabbed_chat_window': - nickname = hb.get_children()[1] - close_button = hb.get_children()[2] - - num_unread = self.nb_unread[jid] - if num_unread == 1 and not gajim.config.get('show_unread_tab_icon'): - unread = '*' - elif num_unread > 1: - unread = '[' + unicode(num_unread) + ']' - - # Draw tab label using chatstate - theme = gajim.config.get('roster_theme') - color = None - if chatstate is not None: - if chatstate == 'composing': - color = gajim.config.get_per('themes', theme, - 'state_composing_color') - elif chatstate == 'inactive': - color = gajim.config.get_per('themes', theme, - 'state_inactive_color') - elif chatstate == 'gone': - color = gajim.config.get_per('themes', theme, 'state_gone_color') - elif chatstate == 'paused': - color = gajim.config.get_per('themes', theme, 'state_paused_color') - else: - color = gajim.config.get_per('themes', theme, 'state_active_color') - if color: - color = gtk.gdk.colormap_get_system().alloc_color(color) - # We set the color for when it's the current tab or not - nickname.modify_fg(gtk.STATE_NORMAL, color) - if chatstate in ('inactive', 'gone'): - # Adjust color to be lighter against the darker inactive - # background - p = 0.4 - mask = 0 - color.red = int((color.red * p) + (mask * (1 - p))) - color.green = int((color.green * p) + (mask * (1 - p))) - color.blue = int((color.blue * p) + (mask * (1 - p))) - nickname.modify_fg(gtk.STATE_ACTIVE, color) - elif self.widget_name == 'groupchat_window': - nickname = hb.get_children()[1] - close_button = hb.get_children()[2] - - has_focus = self.window.get_property('has-toplevel-focus') - current_tab = (self.notebook.page_num(child) == self.notebook.get_current_page()) - color = None - theme = gajim.config.get('roster_theme') - if chatstate == 'attention' and (not has_focus or not current_tab): - if jid not in self.muc_attentions: - self.muc_attentions.append(jid) - color = gajim.config.get_per('themes', theme, 'state_muc_directed_msg') - elif chatstate: - if chatstate == 'active' or (current_tab and has_focus): - if jid in self.muc_attentions: - self.muc_attentions.remove(jid) - color = gajim.config.get_per('themes', theme, 'state_active_color') - elif chatstate == 'newmsg' and (not has_focus or not current_tab) and\ - jid not in self.muc_attentions: - color = gajim.config.get_per('themes', theme, 'state_muc_msg') - if color: - color = gtk.gdk.colormap_get_system().alloc_color(color) - # The widget state depend on whether this tab is the "current" tab - if current_tab: - nickname.modify_fg(gtk.STATE_NORMAL, color) - else: - nickname.modify_fg(gtk.STATE_ACTIVE, color) - - if gajim.config.get('tabs_close_button'): - close_button.show() - else: - close_button.hide() - - nickname.set_max_width_chars(10) - lbl = gtkgui_helpers.escape_for_pango_markup(self.names[jid]) - if num_unread: # if unread, text in the label becomes bold - lbl = '' + unread + lbl + '' - nickname.set_markup(lbl) - - def get_message_type(self, jid): - if self.widget_name == 'groupchat_window': - return 'gc' - if gajim.contacts.is_pm_from_jid(self.account, jid): - return 'pm' - return 'chat' - - def get_nth_jid(self, page_number = None): - notebook = self.notebook - if page_number == None: - page_number = notebook.get_current_page() - nth_child = notebook.get_nth_page(page_number) - nth_jid = '' - for jid in self.xmls: - if self.childs[jid] == nth_child: - nth_jid = jid - break - return nth_jid - - def move_to_next_unread_tab(self, forward): - ind = self.notebook.get_current_page() - current = ind - found = False - # loop until finding an unread tab or having done a complete cycle - while True: - if forward == True: # look for the first unread tab on the right - ind = ind + 1 - if ind >= self.notebook.get_n_pages(): - ind = 0 - else: # look for the first unread tab on the right - ind = ind - 1 - if ind < 0: - ind = self.notebook.get_n_pages() - 1 - if ind == current: - break # a complete cycle without finding an unread tab - jid = self.get_nth_jid(ind) - if self.nb_unread[jid] > 0: - found = True - break # found - if found: - self.notebook.set_current_page(ind) - else: # not found - if forward: # CTRL + TAB - if current < (self.notebook.get_n_pages() - 1): - self.notebook.next_page() - else: # traverse for ever (eg. don't stop at last tab) - self.notebook.set_current_page(0) - else: # CTRL + SHIFT + TAB - if current > 0: - self.notebook.prev_page() - else: # traverse for ever (eg. don't stop at first tab) - self.notebook.set_current_page( - self.notebook.get_n_pages() - 1) - - def on_window_destroy(self, widget, kind): #kind is 'chats' or 'gc' - '''clean gajim.interface.instances[self.account][kind]''' - for jid in self.xmls: - windows = gajim.interface.instances[self.account][kind] - if kind == 'chats': - # send 'gone' chatstate to every tabbed chat tab - windows[jid].send_chatstate('gone', jid) - gobject.source_remove(self.possible_paused_timeout_id[jid]) - gobject.source_remove(self.possible_inactive_timeout_id[jid]) - if gajim.interface.systray_enabled and self.nb_unread[jid] > 0: - gajim.interface.systray.remove_jid(jid, self.account, - self.get_message_type(jid)) - del windows[jid] - if self.print_time_timeout_id.has_key(jid): - gobject.source_remove(self.print_time_timeout_id[jid]) - if windows.has_key('tabbed'): - del windows['tabbed'] - - def get_active_jid(self): - return self.get_nth_jid() - - def on_close_button_clicked(self, button, jid): - '''When close button is pressed: close a tab''' - self.remove_tab(jid) - - def on_history_menuitem_clicked(self, widget = None, jid = None): - '''When history menuitem is pressed: call history window''' - if jid is None: # None when viewing room and tc history - jid = self.get_active_jid() - if gajim.interface.instances['logs'].has_key(jid): - gajim.interface.instances['logs'][jid].window.present() - else: - gajim.interface.instances['logs'][jid] = history_window.HistoryWindow( - jid, self.account) - - def on_chat_window_focus_in_event(self, widget, event): - '''When window gets focus''' - jid = self.get_active_jid() - - textview = self.conversation_textviews[jid] - if textview.at_the_end(): - #we are at the end - if self.nb_unread[jid] > 0: - self.nb_unread[jid] = 0 + self.get_specific_unread(jid) - self.show_title() - if gajim.interface.systray_enabled: - gajim.interface.systray.remove_jid(jid, self.account, - self.get_message_type(jid)) - - '''TC/GC window received focus, so if we had urgency REMOVE IT - NOTE: we do not have to read the message (it maybe in a bg tab) - to remove urgency hint so this functions does that''' - if gtk.gtk_version >= (2, 8, 0) and gtk.pygtk_version >= (2, 8, 0): - if widget.props.urgency_hint: - widget.props.urgency_hint = False - # Undo "unread" state display, etc. - if self.widget_name == 'groupchat_window': - self.redraw_tab(jid, 'active') - else: - # NOTE: we do not send any chatstate to preserve inactive, gone, etc. - self.redraw_tab(jid) - - def on_compact_view_menuitem_activate(self, widget): - isactive = widget.get_active() - self.set_compact_view(isactive) - - def on_actions_button_clicked(self, widget): - '''popup action menu''' - #FIXME: BUG http://bugs.gnome.org/show_bug.cgi?id=316786 - self.button_clicked = widget - - menu = self.prepare_context_menu() - menu.show_all() - menu.popup(None, None, self.position_menu_under_button, 1, 0) - - def on_emoticons_button_clicked(self, widget): - '''popup emoticons menu''' - #FIXME: BUG http://bugs.gnome.org/show_bug.cgi?id=316786 - self.button_clicked = widget - self.emoticons_menu.popup(None, None, self.position_menu_under_button, 1, 0) - - def position_menu_under_button(self, menu): - #FIXME: BUG http://bugs.gnome.org/show_bug.cgi?id=316786 - # pass btn instance when this bug is over - button = self.button_clicked - # here I get the coordinates of the button relative to - # window (self.window) - button_x, button_y = button.allocation.x, button.allocation.y - - # now convert them to X11-relative - window_x, window_y = self.window.window.get_origin() - x = window_x + button_x - y = window_y + button_y - - menu_width, menu_height = menu.size_request() - - ## should we pop down or up? - if (y + button.allocation.height + menu_height - < gtk.gdk.screen_height()): - # now move the menu below the button - y += button.allocation.height - else: - # now move the menu above the button - y -= menu_height - - - # push_in is True so all the menuitems are always inside screen - push_in = True - return (x, y, push_in) - - def remove_possible_switch_to_menuitems(self, menu): - ''' remove duplicate 'Switch to' if they exist and return clean menu''' - childs = menu.get_children() - - if self.widget_name == 'tabbed_chat_window': - jid = self.get_active_jid() - c = self.contacts[jid] - if _('not in the roster') in c.groups: # for add_to_roster_menuitem - childs[5].show() - childs[5].set_no_show_all(False) - else: - childs[5].hide() - childs[5].set_no_show_all(True) - - start_removing_from = 6 # this is from the seperator and after - - else: - start_removing_from = 7 # # this is from the seperator and after - - for child in childs[start_removing_from:]: - menu.remove(child) - - return menu - - def prepare_context_menu(self): - '''sets compact view menuitem active state - sets active and sensitivity state for toggle_gpg_menuitem - and remove possible 'Switch to' menuitems''' - if self.widget_name == 'groupchat_window': - menu = self.gc_popup_menu - childs = menu.get_children() - # compact_view_menuitem - childs[5].set_active(self.compact_view_current_state) - elif self.widget_name == 'tabbed_chat_window': - menu = self.tabbed_chat_popup_menu - childs = menu.get_children() - # check if gpg capabitlies or else make gpg toggle insensitive - jid = self.get_active_jid() - gpg_btn = self.xmls[jid].get_widget('gpg_togglebutton') - isactive = gpg_btn.get_active() - issensitive = gpg_btn.get_property('sensitive') - childs[3].set_active(isactive) - childs[3].set_property('sensitive', issensitive) - # If we don't have resource, we can't do file transfert - c = self.contacts[jid] - if not c.resource: - childs[2].set_sensitive(False) - else: - childs[2].set_sensitive(True) - # compact_view_menuitem - childs[4].set_active(self.compact_view_current_state) - menu = self.remove_possible_switch_to_menuitems(menu) - - return menu - - def prepare_emoticons_menu(self): - menu = gtk.Menu() - - def append_emoticon(w, d): - jid = self.get_active_jid() - message_textview = self.message_textviews[jid] - buffer = message_textview.get_buffer() - if buffer.get_char_count(): - buffer.insert_at_cursor(' %s ' % d) - else: # we are the beginning of buffer - buffer.insert_at_cursor('%s ' % d) - message_textview.grab_focus() - - counter = 0 - # Calculate the side lenght of the popup to make it a square - size = int(round(math.sqrt(len(gajim.interface.emoticons_images)))) - for image in gajim.interface.emoticons_images: - item = gtk.MenuItem() - img = gtk.Image() - if type(image[1]) == gtk.gdk.PixbufAnimation: - img.set_from_animation(image[1]) - else: - img.set_from_pixbuf(image[1]) - item.add(img) - item.connect('activate', append_emoticon, image[0]) - #FIXME: add tooltip with ascii - menu.attach(item, - counter % size, counter % size + 1, - counter / size, counter / size + 1) - counter += 1 - menu.show_all() - return menu - - def popup_menu(self, event): - menu = self.prepare_context_menu() - # common menuitems (tab switches) - if len(self.xmls) > 1: # if there is more than one tab - menu.append(gtk.SeparatorMenuItem()) # seperator - for jid in self.xmls: - if jid != self.get_active_jid(): - item = gtk.ImageMenuItem(_('Switch to %s') % self.names[jid]) - img = gtk.image_new_from_stock(gtk.STOCK_JUMP_TO, - gtk.ICON_SIZE_MENU) - item.set_image(img) - item.connect('activate', lambda obj, jid:self.set_active_tab( - jid), jid) - menu.append(item) - - # show the menu - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - - def on_banner_eventbox_button_press_event(self, widget, event): - '''If right-clicked, show popup''' - if event.button == 3: # right click - self.popup_menu(event) - - def on_chat_notebook_switch_page(self, notebook, page, page_num): - # get the index of the page and then the page that we're leaving - old_no = notebook.get_current_page() - old_child = notebook.get_nth_page(old_no) - - new_child = notebook.get_nth_page(page_num) - - old_jid = '' - new_jid = '' - for jid in self.xmls: - if self.childs[jid] == new_child: - new_jid = jid - elif self.childs[jid] == old_child: - old_jid = jid - - if old_jid != '' and new_jid != '': # we found both jids - break # so stop looping - - if self.widget_name == 'tabbed_chat_window': - # send chatstate inactive to the one we're leaving - # and active to the one we visit - if old_jid != '': - self.send_chatstate('inactive', old_jid) - self.send_chatstate('active', new_jid) - - conv_textview = self.conversation_textviews[new_jid] - if conv_textview.at_the_end(): - #we are at the end - if self.nb_unread[new_jid] > 0: - self.nb_unread[new_jid] = 0 + self.get_specific_unread(new_jid) - self.redraw_tab(new_jid) - self.show_title() - if gajim.interface.systray_enabled: - gajim.interface.systray.remove_jid(new_jid, self.account, - self.get_message_type(new_jid)) - - conv_textview.grab_focus() - - def set_active_tab(self, jid): - self.notebook.set_current_page(self.notebook.page_num(self.childs[jid])) - - def remove_tab(self, jid, kind): #kind is 'chats' or 'gc' - if len(self.xmls) == 1: # only one tab when we asked to remove - # so destroy window - - # we check and possibly save positions here, because Ctrl+W, Escape - # etc.. call remove_tab so similar code in delete_event callbacks - # is not enough - if gajim.config.get('saveposition'): - if kind == 'chats': - x, y = self.window.get_position() - gajim.config.set('chat-x-position', x) - gajim.config.set('chat-y-position', y) - width, height = self.window.get_size() - gajim.config.set('chat-width', width) - gajim.config.set('chat-height', height) - elif kind == 'gc': - gajim.config.set('gc-hpaned-position', self.hpaned_position) - x, y = self.window.get_position() - gajim.config.set('gc-x-position', x) - gajim.config.set('gc-y-position', y) - width, height = self.window.get_size() - gajim.config.set('gc-width', width) - gajim.config.set('gc-height', height) - - self.window.destroy() - else: - if self.nb_unread[jid] > 0: - self.nb_unread[jid] = 0 - if gajim.interface.systray_enabled: - gajim.interface.systray.remove_jid(jid, self.account, - self.get_message_type(jid)) - 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.notebook.remove_page(self.notebook.page_num(self.childs[jid])) - - if gajim.interface.instances[self.account][kind].has_key(jid): - del gajim.interface.instances[self.account][kind][jid] - del self.nb_unread[jid] - del gajim.last_message_time[self.account][jid] - del self.xmls[jid] - del self.childs[jid] - del self.sent_history[jid] - del self.sent_history_pos[jid] - del self.typing_new[jid] - del self.orig_msg[jid] - - if len(self.xmls) == 1: # we now have only one tab - show_tabs_if_one_tab = gajim.config.get('tabs_always_visible') - self.notebook.set_show_tabs(show_tabs_if_one_tab) - - if not show_tabs_if_one_tab: - self.alignment.set_property('top-padding', 0) - - self.show_title() - - def bring_scroll_to_end(self, textview, diff_y = 0): - ''' scrolls to the end of textview if end is not visible ''' - buffer = textview.get_buffer() - end_iter = buffer.get_end_iter() - end_rect = textview.get_iter_location(end_iter) - visible_rect = textview.get_visible_rect() - # scroll only if expected end is not visible - if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y): - gobject.idle_add(self.scroll_to_end_iter, textview) - - def scroll_to_end_iter(self, textview): - buffer = textview.get_buffer() - end_iter = buffer.get_end_iter() - textview.scroll_to_iter(end_iter, 0, False, 1, 1) - return False - - def size_request(self, msg_textview , requisition, xml_top): - ''' When message_textview changes its size. If the new height - will enlarge the window, enable the scrollbar automatic policy - Also enable scrollbar automatic policy for horizontal scrollbar - if message we have in message_textview is too big''' - if msg_textview.window is None: - return - message_scrolledwindow = xml_top.get_widget('message_scrolledwindow') - - conversation_scrolledwindow = xml_top.get_widget( - 'conversation_scrolledwindow') - conv_textview = conversation_scrolledwindow.get_children()[0] - - min_height = conversation_scrolledwindow.get_property('height-request') - conversation_height = conv_textview.window.get_size()[1] - message_height = msg_textview.window.get_size()[1] - message_width = msg_textview.window.get_size()[0] - # new tab is not exposed yet - if conversation_height < 2: - return - - if conversation_height < min_height: - min_height = conversation_height - - # we don't want to always resize in height the message_textview - # so we have minimum on conversation_textview's scrolled window - # but we also want to avoid window resizing so if we reach that - # minimum for conversation_textview and maximum for message_textview - # we set to automatic the scrollbar policy - diff_y = message_height - requisition.height - if diff_y != 0: - if conversation_height + diff_y < min_height: - if message_height + conversation_height - min_height > min_height: - message_scrolledwindow.set_property('vscrollbar-policy', - gtk.POLICY_AUTOMATIC) - message_scrolledwindow.set_property('height-request', - message_height + conversation_height - min_height) - self.bring_scroll_to_end(msg_textview) - else: - message_scrolledwindow.set_property('vscrollbar-policy', - gtk.POLICY_NEVER) - message_scrolledwindow.set_property('height-request', -1) - - conv_textview.bring_scroll_to_end(diff_y - 18) - - # enable scrollbar automatic policy for horizontal scrollbar - # if message we have in message_textview is too big - if requisition.width > message_width: - message_scrolledwindow.set_property('hscrollbar-policy', - gtk.POLICY_AUTOMATIC) - else: - message_scrolledwindow.set_property('hscrollbar-policy', - gtk.POLICY_NEVER) - - return True - - def on_tab_eventbox_button_press_event(self, widget, event, child): - if event.button == 3: - n = self.notebook.page_num(child) - self.notebook.set_current_page(n) - self.popup_menu(event) - - def new_tab(self, jid): - #FIXME: text formating buttons will be hidden in 0.8 release - for w in ('bold_togglebutton', 'italic_togglebutton', - 'underline_togglebutton'): - self.xmls[jid].get_widget(w).set_no_show_all(True) - - # add ConversationTextView to UI and connect signals - conv_textview = self.conversation_textviews[jid] = \ - conversation_textview.ConversationTextview(self.account) - conv_textview.show_all() - conversation_scrolledwindow = self.xmls[jid].get_widget( - 'conversation_scrolledwindow') - conversation_scrolledwindow.add(conv_textview) - conv_textview.connect('key_press_event', self.on_conversation_textview_key_press_event) - - # add MessageTextView to UI and connect signals - message_scrolledwindow = self.xmls[jid].get_widget( - 'message_scrolledwindow') - msg_textview = self.message_textviews[jid] = \ - message_textview.MessageTextView() - msg_textview.connect('mykeypress', - self.on_message_textview_mykeypress_event) - message_scrolledwindow.add(msg_textview) - msg_textview.connect('key_press_event', - self.on_message_textview_key_press_event) - - self.set_compact_view(self.always_compact_view) - self.nb_unread[jid] = 0 - gajim.last_message_time[self.account][jid] = 0 - font = pango.FontDescription(gajim.config.get('conversation_font')) - - if gajim.config.get('use_speller') and HAS_GTK_SPELL: - try: - gtkspell.Spell(msg_textview) - except gobject.GError, msg: - #FIXME: add a ui for this use spell.set_language() - dialogs.ErrorDialog(unicode(msg), _('If that is not your language for which you want to highlight misspelled words, then please set your $LANG as appropriate. Eg. for French do export LANG=fr_FR or export LANG=fr_FR.UTF-8 in ~/.bash_profile or to make it global in /etc/profile.\n\nHighlighting misspelled words feature will not be used')).get_response() - gajim.config.set('use_speller', False) - - emoticons_button = self.xmls[jid].get_widget('emoticons_button') - # set image no matter if user wants at this time emoticons or not - # (so toggle works ok) - img = self.xmls[jid].get_widget('emoticons_button_image') - img.set_from_file(os.path.join(gajim.DATA_DIR, 'emoticons', 'smile.png')) - if gajim.config.get('useemoticons'): - emoticons_button.show() - emoticons_button.set_no_show_all(False) - else: - emoticons_button.hide() - emoticons_button.set_no_show_all(True) - - conv_textview.modify_font(font) - conv_buffer = conv_textview.get_buffer() - end_iter = conv_buffer.get_end_iter() - - self.xmls[jid].signal_autoconnect(self) - conversation_scrolledwindow.get_vadjustment().connect('value-changed', - self.on_conversation_vadjustment_value_changed) - - if len(self.xmls) > 1: - self.notebook.set_show_tabs(True) - self.alignment.set_property('top-padding', 2) - - if self.widget_name == 'tabbed_chat_window': - xm = gtk.glade.XML(GTKGUI_GLADE, 'chats_eventbox', APP) - tab_hbox = xm.get_widget('chats_eventbox') - elif self.widget_name == 'groupchat_window': - xm = gtk.glade.XML(GTKGUI_GLADE, 'gc_eventbox', APP) - tab_hbox = xm.get_widget('gc_eventbox') - - child = self.childs[jid] - - xm.signal_connect('on_close_button_clicked', - self.on_close_button_clicked, jid) - xm.signal_connect('on_tab_eventbox_button_press_event', - self.on_tab_eventbox_button_press_event, child) - - self.notebook.append_page(child, tab_hbox) - - msg_textview.modify_font(font) - msg_textview.connect('size-request', self.size_request, self.xmls[jid]) - # init new sent history for this conversation - self.sent_history[jid] = [] - self.sent_history_pos[jid] = 0 - self.typing_new[jid] = True - self.orig_msg[jid] = '' - - self.show_title() - - def on_message_textview_key_press_event(self, widget, event): - jid = self.get_active_jid() - conv_textview = self.conversation_textviews[jid] - - if self.widget_name == 'groupchat_window': - if event.keyval not in (gtk.keysyms.ISO_Left_Tab, gtk.keysyms.Tab): - room_jid = self.get_active_jid() - self.last_key_tabs[room_jid] = False - - if event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN - if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN - self.notebook.emit('key_press_event', event) - elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN - conv_textview.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP - if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP - self.notebook.emit('key_press_event', event) - elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP - conv_textview.emit('key_press_event', event) - - def on_conversation_textview_key_press_event(self, widget, event): - '''Do not block these events and send them to the notebook''' - if event.state & gtk.gdk.CONTROL_MASK: - if event.keyval == gtk.keysyms.Tab: # CTRL + TAB - self.notebook.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB - self.notebook.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN - self.notebook.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP - self.notebook.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.l or \ - event.keyval == gtk.keysyms.L: # CTRL + L - jid = self.get_active_jid() - conv_textview = self.conversation_textviews[jid] - conv_textview.get_buffer().set_text('') - elif event.keyval == gtk.keysyms.v: # CTRL + V - jid = self.get_active_jid() - msg_textview = self.message_textviews[jid] - if not msg_textview.is_focus(): - msg_textview.grab_focus() - msg_textview.emit('key_press_event', event) - - def on_chat_notebook_key_press_event(self, widget, event): - st = '1234567890' # alt+1 means the first tab (tab 0) - jid = self.get_active_jid() - if event.keyval == gtk.keysyms.Escape: # ESCAPE - if self.widget_name == 'tabbed_chat_window': - self.remove_tab(jid) - elif event.keyval == gtk.keysyms.F4 and \ - (event.state & gtk.gdk.CONTROL_MASK): # CTRL + F4 - self.remove_tab(jid) - elif event.keyval == gtk.keysyms.w and \ - (event.state & gtk.gdk.CONTROL_MASK): # CTRL + W - self.remove_tab(jid) - elif event.string and event.string in st and \ - (event.state & gtk.gdk.MOD1_MASK): # alt + 1,2,3.. - self.notebook.set_current_page(st.index(event.string)) - elif event.keyval == gtk.keysyms.c and \ - (event.state & gtk.gdk.MOD1_MASK): # alt + C toggles compact view - self.set_compact_view(not self.compact_view_current_state) - elif event.keyval == gtk.keysyms.e and \ - (event.state & gtk.gdk.MOD1_MASK): # alt + E opens emoticons menu - if gajim.config.get('useemoticons'): - msg_tv = self.message_textviews[jid] - def set_emoticons_menu_position(w, msg_tv = msg_tv): - window = msg_tv.get_window(gtk.TEXT_WINDOW_WIDGET) - # get the window position - origin = window.get_origin() - size = window.get_size() - buf = msg_tv.get_buffer() - # get the cursor position - cursor = msg_tv.get_iter_location(buf.get_iter_at_mark( - buf.get_insert())) - cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, - cursor.x, cursor.y) - x = origin[0] + cursor[0] - y = origin[1] + size[1] - menu_width, menu_height = self.emoticons_menu.size_request() - #FIXME: get_line_count is not so good - #get the iter of cursor, then tv.get_line_yrange - # so we know in which y we are typing (not how many lines we have - # then go show just above the current cursor line for up - # or just below the current cursor line for down - #TEST with having 3 lines and writing in the 2nd - if y + menu_height > gtk.gdk.screen_height(): - # move menu just above cursor - y -= menu_height + (msg_tv.allocation.height / buf.get_line_count()) - #else: # move menu just below cursor - # y -= (msg_tv.allocation.height / buf.get_line_count()) - return (x, y, True) # push_in True - self.emoticons_menu.popup(None, None, set_emoticons_menu_position, 1, 0) - elif event.keyval == gtk.keysyms.Page_Down: - if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN - conv_textview = self.conversation_textviews[jid] - rect = conv_textview.get_visible_rect() - iter = conv_textview.get_iter_at_location(rect.x, - rect.y + rect.height) - conv_textview.scroll_to_iter(iter, 0.1, True, 0, 0) - elif event.keyval == gtk.keysyms.Page_Up: - if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP - conv_textview = self.conversation_textviews[jid] - rect = conv_textview.get_visible_rect() - iter = conv_textview.get_iter_at_location(rect.x, rect.y) - conv_textview.scroll_to_iter(iter, 0.1, True, 0, 1) - elif event.keyval == gtk.keysyms.Up: - if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + UP - conversation_scrolledwindow = self.xml.get_widget('conversation_scrolledwindow') - conversation_scrolledwindow.emit('scroll-child', - gtk.SCROLL_PAGE_BACKWARD, False) - elif event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB - if event.state & gtk.gdk.CONTROL_MASK: # CTRL + SHIFT + TAB - self.move_to_next_unread_tab(False) - elif event.keyval == gtk.keysyms.Tab: # TAB - if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB - self.move_to_next_unread_tab(True) - elif (event.keyval == gtk.keysyms.l or event.keyval == gtk.keysyms.L) \ - and event.state & gtk.gdk.CONTROL_MASK: # CTRL + L - conv_textview = self.conversation_textviews[jid] - conv_textview.get_buffer().set_text('') - elif event.keyval == gtk.keysyms.v and event.state & gtk.gdk.CONTROL_MASK: - # CTRL + V - msg_textview = self.message_textviews[jid] - if not msg_textview.is_focus(): - msg_textview.grab_focus() - msg_textview.emit('key_press_event', event) - 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 (copy text) and - # others do their default work - pass - else: # it's a normal key press make sure message_textview has focus - msg_textview = self.message_textviews[jid] - if msg_textview.get_property('sensitive'): - if not msg_textview.is_focus(): - msg_textview.grab_focus() - msg_textview.emit('key_press_event', event) - - def on_conversation_vadjustment_value_changed(self, widget): - jid = self.get_active_jid() - if not self.nb_unread[jid]: - return - conv_textview = self.conversation_textviews[jid] - if conv_textview.at_the_end() and self.window.is_active(): - #we are at the end - self.nb_unread[jid] = self.get_specific_unread(jid) - self.redraw_tab(jid) - self.show_title() - if gajim.interface.systray_enabled: - gajim.interface.systray.remove_jid(jid, self.account, - self.get_message_type(jid)) - - def clear(self, tv): - buffer = tv.get_buffer() - start, end = buffer.get_bounds() - buffer.delete(start, end) - - def print_conversation_line(self, text, jid, kind, name, tim, - other_tags_for_name = [], other_tags_for_time = [], - other_tags_for_text = [], count_as_new = True, subject = None): - '''prints 'chat' type messages''' - textview = self.conversation_textviews[jid] - end = False - if textview.at_the_end() or kind == 'outgoing': - end = True - textview.print_conversation_line(text, jid, kind, name, tim, - other_tags_for_name, other_tags_for_time, other_tags_for_text, subject) - - if not count_as_new: - return - if kind == 'incoming_queue': - gajim.last_message_time[self.account][jid] = time.time() - urgent = True - if (jid != self.get_active_jid() or \ - not self.window.is_active() or \ - not end) and kind in ('incoming', 'incoming_queue'): - if self.widget_name == 'groupchat_window': - if not self.needs_visual_notification(text, self.nicks[jid]): - # Do not notify us for gc messages that are not for us - urgent = False - if not gajim.config.get('notify_on_all_muc_messages'): - return - self.nb_unread[jid] += 1 - if gajim.interface.systray_enabled and gajim.config.get( - 'trayicon_notification_on_new_messages'): - gajim.interface.systray.add_jid(jid, self.account, self.get_message_type(jid)) - self.redraw_tab(jid) - self.show_title(urgent) - - def save_sent_message(self, jid, message): - #save the message, so user can scroll though the list with key up/down - size = len(self.sent_history[jid]) - #we don't want size of the buffer to grow indefinately - max_size = gajim.config.get('key_up_lines') - if size >= max_size: - for i in xrange(0, size - 1): - self.sent_history[jid][i] = self.sent_history[jid][i+1] - self.sent_history[jid][max_size - 1] = message - else: - self.sent_history[jid].append(message) - self.sent_history_pos[jid] = size + 1 - - self.typing_new[jid] = True - self.orig_msg[jid] = '' - - def sent_messages_scroll(self, jid, direction, conv_buf): - size = len(self.sent_history[jid]) - if self.typing_new[jid]: - #user was typing something and then went into history, so save - #whatever is already typed - start_iter = conv_buf.get_start_iter() - end_iter = conv_buf.get_end_iter() - self.orig_msg[jid] = conv_buf.get_text(start_iter, end_iter, 0).decode('utf-8') - self.typing_new[jid] = False - if direction == 'up': - if self.sent_history_pos[jid] == 0: - return - self.sent_history_pos[jid] = self.sent_history_pos[jid] - 1 - conv_buf.set_text(self.sent_history[jid][self.sent_history_pos[jid]]) - - elif direction == 'down': - if self.sent_history_pos[jid] >= size - 1: - conv_buf.set_text(self.orig_msg[jid]); - self.typing_new[jid] = True - self.sent_history_pos[jid] = size - return - - self.sent_history_pos[jid] = self.sent_history_pos[jid] + 1 - conv_buf.set_text(self.sent_history[jid][self.sent_history_pos[jid]]) - - def paint_banner(self, jid): - theme = gajim.config.get('roster_theme') - bgcolor = gajim.config.get_per('themes', theme, 'bannerbgcolor') - textcolor = gajim.config.get_per('themes', theme, 'bannertextcolor') - # the backgrounds are colored by using an eventbox by - # setting the bg color of the eventbox and the fg of the name_label - banner_eventbox = self.xmls[jid].get_widget('banner_eventbox') - banner_name_label = self.xmls[jid].get_widget('banner_name_label') - if bgcolor: - banner_eventbox.modify_bg(gtk.STATE_NORMAL, - gtk.gdk.color_parse(bgcolor)) - else: - banner_eventbox.modify_bg(gtk.STATE_NORMAL, None) - if textcolor: - banner_name_label.modify_fg(gtk.STATE_NORMAL, - gtk.gdk.color_parse(textcolor)) - else: - banner_name_label.modify_fg(gtk.STATE_NORMAL, None) - - def repaint_colored_widgets(self): - '''Repaint widgets (banner) in the window/tab with theme color''' - # iterate through tabs/windows and repaint - for jid in self.xmls: - self.paint_banner(jid) - - def set_compact_view(self, state): - '''Toggle compact view. state is bool''' - - for jid in self.xmls: - if self.widget_name == 'tabbed_chat_window': - widgets = [ - self.xmls[jid].get_widget('banner_eventbox'), - self.xmls[jid].get_widget('actions_hbox'), - ] - elif self.widget_name == 'groupchat_window': - widgets = [self.xmls[jid].get_widget('banner_eventbox'), - self.xmls[jid].get_widget('gc_actions_hbox'), - self.xmls[jid].get_widget('list_scrolledwindow'), - ] - - for widget in widgets: - if state: - widget.set_no_show_all(True) - widget.hide() - else: - widget.set_no_show_all(False) - widget.show_all() - # make the last message visible, when changing to "full view" - if not state: - conv_textview = self.conversation_textviews[jid] - gobject.idle_add(conv_textview.scroll_to_end_iter) diff --git a/src/groupchat_window.py b/src/groupchat_window.py deleted file mode 100644 index 80bd4c81f..000000000 --- a/src/groupchat_window.py +++ /dev/null @@ -1,1696 +0,0 @@ -## groupchat_window.py -## -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## - Travis Shirk -## -## Copyright (C) 2003-2004 Yann Le Boulanger -## Vincent Hanquez -## Copyright (C) 2005 Yann Le Boulanger -## Vincent Hanquez -## Nikos Kouremenos -## Dimitur Kirov -## Travis Shirk -## Norman Rasmussen -## -## 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 os - -import dialogs -import vcard -import chat -import cell_renderer_image -import gtkgui_helpers -import history_window -import tooltips - -from common import gajim -from common import helpers -from gettext import ngettext -from common import i18n - -_ = i18n._ -Q_ = i18n.Q_ -APP = i18n.APP -gtk.glade.bindtextdomain(APP, i18n.DIR) -gtk.glade.textdomain(APP) - -#(status_image, type, nick, shown_nick) -( -C_IMG, # image to show state (online, new message etc) -C_TYPE, # type of the row ('contact' or 'group') -C_NICK, # contact nickame or group name -C_TEXT, # text shown in the cellrenderer -) = range(4) - -GTKGUI_GLADE = 'gtkgui.glade' - -class GroupchatWindow(chat.Chat): - '''Class for Groupchat window''' - def __init__(self, room_jid, nick, account): - # we check that on opening new windows - self.always_compact_view = gajim.config.get('always_compact_view_gc') - chat.Chat.__init__(self, account, 'groupchat_window') - - # alphanum sorted - self.muc_cmds = ['ban', 'chat', 'query', 'clear', 'close', 'compact', 'help', 'invite', - 'join', 'kick', 'leave', 'me', 'msg', 'nick', 'part', 'say', 'topic'] - - self.nicks = {} # our nick for each groupchat we are in - self.list_treeview = {} - self.subjects = {} - self.name_labels = {} - self.subject_tooltip = {} - self.room_creation = {} - self.nick_hits = {} # possible candidates for nick completion - self.cmd_hits = {} # possible candidates for command completion - self.last_key_tabs = {} - self.hpaneds = {} # used for auto positioning - # holds the iter's offset which points to the end of --- line per jid - self.focus_out_end_iter_offset = {} - self.allow_focus_out_line = {} - self.hpaned_position = gajim.config.get('gc-hpaned-position') - self.gc_refer_to_nick_char = gajim.config.get('gc_refer_to_nick_char') - self.new_room(room_jid, nick) - self.show_title() - self.tooltip = tooltips.GCTooltip() - - - # NOTE: if it not a window event, connect in new_room function - signal_dict = { -'on_groupchat_window_destroy': self.on_groupchat_window_destroy, -'on_groupchat_window_delete_event': self.on_groupchat_window_delete_event, -'on_groupchat_window_focus_in_event': self.on_groupchat_window_focus_in_event, -'on_chat_notebook_key_press_event': self.on_chat_notebook_key_press_event, -'on_chat_notebook_switch_page': self.on_chat_notebook_switch_page, - } - - self.xml.signal_autoconnect(signal_dict) - - # get size and position from config - if gajim.config.get('saveposition') and \ - not gtkgui_helpers.one_window_opened('gc'): - gtkgui_helpers.move_window(self.window, - gajim.config.get('gc-x-position'), - gajim.config.get('gc-y-position')) - gtkgui_helpers.resize_window(self.window, - gajim.config.get('gc-width'), - gajim.config.get('gc-height')) - self.window.show_all() - - def save_var(self, room_jid): - if not room_jid in self.nicks: - return {} - return { - 'nick': self.nicks[room_jid], - 'model': self.list_treeview[room_jid].get_model(), - 'subject': self.subjects[room_jid], - } - - def load_var(self, room_jid, var): - if not self.xmls.has_key(room_jid): - return - self.list_treeview[room_jid].set_model(var['model']) - self.list_treeview[room_jid].expand_all() - self.set_subject(room_jid, var['subject']) - self.subjects[room_jid] = var['subject'] - if gajim.gc_connected[self.account][room_jid]: - self.got_connected(room_jid) - - def confirm_close(self, room_jid = None): - '''Returns True if we confirm we want to close - Returns False if we don't want to close anymore - if room_jid is given, ask only for it else ask for all opened rooms''' - # whether to ask for comfirmation before closing muc - if gajim.config.get('confirm_close_muc'): - names = [] - if not room_jid: - for r_jid in self.xmls: - if gajim.gc_connected[self.account][r_jid]: - names.append(gajim.get_nick_from_jid(r_jid)) - else: - names = [room_jid] - - rooms_no = len(names) - if rooms_no >= 2: # if we are in many rooms - pritext = _('Are you sure you want to leave rooms "%s"?') % ', '.join(names) - sectext = _('If you close this window, you will be disconnected from these rooms.') - - elif rooms_no == 1: # just in one room - pritext = _('Are you sure you want to leave room "%s"?') % names[0] - sectext = _('If you close this window, you will be disconnected from this room.') - - if rooms_no > 0: - dialog = dialogs.ConfirmationDialogCheck(pritext, sectext, - _('Do _not ask me again')) - - if dialog.is_checked(): - gajim.config.set('confirm_close_muc', False) - dialog.destroy() - - if dialog.get_response() != gtk.RESPONSE_OK: - return False - return True - - def on_groupchat_window_delete_event(self, widget, event): - '''close window''' - if not self.confirm_close(): - return True # stop propagation of the delete event - for room_jid in self.xmls: - if gajim.gc_connected[self.account][room_jid]: - gajim.connections[self.account].send_gc_status(self.nicks[room_jid], - room_jid, 'offline', 'offline') - - if gajim.config.get('saveposition'): - # save window position and size - gajim.config.set('gc-hpaned-position', self.hpaned_position) - x, y = self.window.get_position() - gajim.config.set('gc-x-position', x) - gajim.config.set('gc-y-position', y) - width, height = self.window.get_size() - gajim.config.set('gc-width', width) - gajim.config.set('gc-height', height) - - def on_groupchat_window_destroy(self, widget): - chat.Chat.on_window_destroy(self, widget, 'gc') - for room_jid in self.xmls: - gajim.contacts.remove_room(self.account, room_jid) - del gajim.gc_connected[self.account][room_jid] - - def on_groupchat_window_focus_in_event(self, widget, event): - '''When window gets focus''' - room_jid = self.get_active_jid() - self.allow_focus_out_line[room_jid] = True - chat.Chat.on_chat_window_focus_in_event(self, widget, event) - - def check_and_possibly_add_focus_out_line(self, room_jid): - '''checks and possibly adds focus out line for room_jid if it needs it - and does not already have it as last event. If it goes to add this line - it removes previous line first''' - - if room_jid == self.get_active_jid() and self.window.get_property('has-toplevel-focus'): - # it's the current room and it's the focused window. - # we have full focus (we are reading it!) - return - - if not self.allow_focus_out_line[room_jid]: - # if room did not receive focus-in from the last time we added - # --- line then do not readd - return - - print_focus_out_line = False - textview = self.conversation_textviews[room_jid] - buffer = textview.get_buffer() - - if self.focus_out_end_iter_offset[room_jid] is None: - # this happens only first time we focus out on this room - print_focus_out_line = True - - else: - if self.focus_out_end_iter_offset[room_jid] != buffer.get_end_iter().get_offset(): - # this means after last-focus something was printed - # (else end_iter's offset is the same as before) - # only then print ---- line (eg. we avoid printing many following - # ---- lines) - print_focus_out_line = True - - if print_focus_out_line and buffer.get_char_count() > 0: - buffer.begin_user_action() - - # remove previous focus out line if such focus out line exists - if self.focus_out_end_iter_offset[room_jid] is not None: - end_iter_for_previous_line = buffer.get_iter_at_offset( - self.focus_out_end_iter_offset[room_jid]) - begin_iter_for_previous_line = end_iter_for_previous_line.copy() - begin_iter_for_previous_line.backward_chars(2) # img_char+1 (the '\n') - - # remove focus out line - buffer.delete(begin_iter_for_previous_line, - end_iter_for_previous_line) - - # add the new focus out line - path_to_file = os.path.join(gajim.DATA_DIR, 'pixmaps', 'muc_separator.png') - focus_out_line_pixbuf = gtk.gdk.pixbuf_new_from_file(path_to_file) - end_iter = buffer.get_end_iter() - buffer.insert(end_iter, '\n') - buffer.insert_pixbuf(end_iter, focus_out_line_pixbuf) - - end_iter = buffer.get_end_iter() - before_img_iter = end_iter.copy() - before_img_iter.backward_char() # one char back (an image also takes one char) - buffer.apply_tag_by_name('focus-out-line', before_img_iter, end_iter) - #FIXME: remove this workaround when bug is fixed - # c http://bugzilla.gnome.org/show_bug.cgi?id=318569 - - self.allow_focus_out_line[room_jid] = False - - # update the iter we hold to make comparison the next time - self.focus_out_end_iter_offset[room_jid] = buffer.get_end_iter( - ).get_offset() - - buffer.end_user_action() - - # scroll to the end (via idle in case the scrollbar has appeared) - gobject.idle_add(textview.scroll_to_end) - - def on_chat_notebook_key_press_event(self, widget, event): - chat.Chat.on_chat_notebook_key_press_event(self, widget, event) - - def on_chat_notebook_switch_page(self, notebook, page, page_num): - old_child = notebook.get_nth_page(notebook.get_current_page()) - new_child = notebook.get_nth_page(page_num) - old_jid = '' - new_jid = '' - for room_jid in self.xmls: - if self.childs[room_jid] == new_child: - new_jid = room_jid - self.redraw_tab(new_jid, 'active') - elif self.childs[room_jid] == old_child: - old_jid = room_jid - self.redraw_tab(old_jid, 'active') - if old_jid != '' and new_jid != '': # we found both jids - break # so stop looping - - subject = self.subjects[new_jid] - subject_escaped = gtkgui_helpers.escape_for_pango_markup(subject) - new_jid_escaped = gtkgui_helpers.escape_for_pango_markup(new_jid) - - name_label = self.name_labels[new_jid] - name_label.set_markup('%s\n%s'\ - % (new_jid_escaped, subject_escaped)) - event_box = name_label.get_parent() - if subject == '': - subject = _('This room has no subject') - self.subject_tooltip[new_jid].set_tip(event_box, subject) - - if len(self.xmls) > 1 and old_jid != '': # if we have more than one tab - # and we have the old_jid (eg we did NOT close the tab - # but we just switched) - # then add the focus-out line to the tab we are leaving - self.check_and_possibly_add_focus_out_line(old_jid) - chat.Chat.on_chat_notebook_switch_page(self, notebook, page, page_num) - - 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[iter][C_NICK].decode('utf-8') - if role == role_name: - return iter - iter = model.iter_next(iter) - if not iter: - fin = True - return None - - def get_contact_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[user_iter][C_NICK].decode('utf-8'): - 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 remove_contact(self, room_jid, nick): - '''Remove a user from the contacts_list''' - model = self.list_treeview[room_jid].get_model() - iter = self.get_contact_iter(room_jid, nick) - if not iter: - return - gc_contact = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - if gc_contact: - gajim.contacts.remove_gc_contact(self.account, gc_contact) - parent_iter = model.iter_parent(iter) - model.remove(iter) - if model.iter_n_children(parent_iter) == 0: - model.remove(parent_iter) - - def add_contact_to_roster(self, room_jid, nick, show, role, affiliation, status, jid = ''): - model = self.list_treeview[room_jid].get_model() - role_name = helpers.get_uf_role(role, plural = True) - - resource = '' - if jid: - jids = jid.split('/', 1) - j = jids[0] - if len(jids) > 1: - resource = jids[1] - else: - j = '' - - name = nick - - role_iter = self.get_role_iter(room_jid, role) - if not role_iter: - role_iter = model.append(None, - (gajim.interface.roster.jabber_state_images['16']['closed'], 'role', role, - '%s' % role_name)) - iter = model.append(role_iter, (None, 'contact', nick, name)) - if not nick in gajim.contacts.get_nick_list(self.account, room_jid): - gc_contact = gajim.contacts.create_gc_contact(room_jid = room_jid, - name = nick, show = show, status = status, role = role, - affiliation = affiliation, jid = j, resource = resource) - gajim.contacts.add_gc_contact(self.account, gc_contact) - self.draw_contact(room_jid, nick) - if nick == self.nicks[room_jid]: # we became online - self.got_connected(room_jid) - self.list_treeview[room_jid].expand_row((model.get_path(role_iter)), - False) - return iter - - def draw_contact(self, room_jid, nick, selected=False, focus=False): - iter = self.get_contact_iter(room_jid, nick) - if not iter: - return - model = self.list_treeview[room_jid].get_model() - gc_contact = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - state_images = gajim.interface.roster.jabber_state_images['16'] - if gajim.awaiting_events[self.account].has_key(room_jid + '/' + nick): - image = state_images['message'] - else: - image = state_images[gc_contact.show] - - name = gtkgui_helpers.escape_for_pango_markup(gc_contact.name) - status = gc_contact.status - # add status msg, if not empty, under contact name in the treeview - if status and gajim.config.get('show_status_msgs_in_roster'): - status = status.strip() - if status != '': - status = gtkgui_helpers.reduce_chars_newlines(status, max_lines = 1) - # escape markup entities and make them small italic and fg color - color = gtkgui_helpers._get_fade_color(self.list_treeview[room_jid], - selected, focus) - colorstring = "#%04x%04x%04x" % (color.red, color.green, color.blue) - name += '\n' '%s'\ - % (colorstring, gtkgui_helpers.escape_for_pango_markup(status)) - - model[iter][C_IMG] = image - model[iter][C_TEXT] = name - - def draw_roster(self, room_jid): - model = self.list_treeview[room_jid].get_model() - model.clear() - for nick in gajim.contacts.get_nick_list(self.account, room_jid): - gc_contact = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - self.add_contact_to_roster(room_jid, nick, gc_contact.show, - gc_contact.role, gc_contact.affiliation, gc_contact.status, - gc_contact.jid) - - def get_role(self, room_jid, nick): - gc_contact = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - if gc_contact: - return gc_contact.role - else: - return 'visitor' - - def draw_all_roster(self): - for room_jid in self.list_treeview: - self.draw_roster(room_jid) - - def chg_contact_status(self, room_jid, nick, show, status, role, affiliation, - jid, reason, actor, statusCode, new_nick, account): - '''When an occupant changes his or her status''' - if show == 'invisible': - return - if not role: - role = 'visitor' - if not affiliation: - affiliation = 'none' - if show in ('offline', 'error'): - if statusCode == '307': - if actor is None: # do not print 'kicked by None' - s = _('%(nick)s has been kicked: %(reason)s') % { - 'nick': nick, - 'reason': reason } - else: - s = _('%(nick)s has been kicked by %(who)s: %(reason)s') % { - 'nick': nick, - 'who': actor, - 'reason': reason } - self.print_conversation(s, room_jid) - elif statusCode == '301': - if actor is None: # do not print 'banned by None' - s = _('%(nick)s has been banned: %(reason)s') % { - 'nick': nick, - 'reason': reason } - else: - s = _('%(nick)s has been banned by %(who)s: %(reason)s') % { - 'nick': nick, - 'who': actor, - 'reason': reason } - self.print_conversation(s, room_jid) - elif statusCode == '303': # Someone changed his or her nick - if nick == self.nicks[room_jid]: # We changed our nick - self.nicks[room_jid] = new_nick - s = _('You are now known as %s') % new_nick - else: - s = _('%s is now known as %s') % (nick, new_nick) - self.print_conversation(s, room_jid) - - if not gajim.awaiting_events[self.account].has_key( - room_jid + '/' + nick): - self.remove_contact(room_jid, nick) - else: - c = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - c.show = show - c.status = status - if nick == self.nicks[room_jid] and statusCode != '303': # We became offline - self.got_disconnected(room_jid) - else: - iter = self.get_contact_iter(room_jid, nick) - if not iter: - iter = self.add_contact_to_roster(room_jid, nick, show, role, - affiliation, status, jid) - if statusCode == '201': # We just created the room - gajim.connections[self.account].request_gc_config(room_jid) - - else: - actual_role = self.get_role(room_jid, nick) - if role != actual_role: - self.remove_contact(room_jid, nick) - self.add_contact_to_roster(room_jid, nick, show, role, - affiliation, status, jid) - else: - c = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - if c.show == show and c.status == status and \ - c.affiliation == affiliation: #no change - return - c.show = show - c.affiliation = affiliation - c.status = status - self.draw_contact(room_jid, nick) - if (time.time() - self.room_creation[room_jid]) > 30 and \ - nick != self.nicks[room_jid] and statusCode != '303': - if show == 'offline': - st = _('%s has left') % nick - else: - st = _('%s is now %s') % (nick, helpers.get_uf_show(show)) - if status: - st += ' (' + status + ')' - self.print_conversation(st, room_jid) - - def set_subject(self, room_jid, subject): - self.subjects[room_jid] = subject - name_label = self.name_labels[room_jid] - full_subject = None - - subject = gtkgui_helpers.reduce_chars_newlines(subject, 0, 2) - subject = gtkgui_helpers.escape_for_pango_markup(subject) - name_label.set_markup( - '%s\n%s' % (room_jid, subject)) - event_box = name_label.get_parent() - if subject == '': - subject = _('This room has no subject') - - if full_subject is not None: - subject = full_subject # tooltip must always hold ALL the subject - self.subject_tooltip[room_jid].set_tip(event_box, subject) - - def get_specific_unread(self, room_jid): - # returns the number of the number of unread msgs - # for room_jid & number of unread private msgs with each contact - # that we have - nb = 0 - for nick in gajim.contacts.get_nick_list(self.account, room_jid): - fjid = room_jid + '/' + nick - if gajim.awaiting_events[self.account].has_key(fjid): - # gc can only have messages as event - nb += len(gajim.awaiting_events[self.account][fjid]) - return nb - - def on_change_subject_menuitem_activate(self, widget): - room_jid = self.get_active_jid() - subject = self.subjects[room_jid] - instance = dialogs.InputDialog(_('Changing Subject'), - _('Please specify the new subject:'), subject) - response = instance.get_response() - if response == gtk.RESPONSE_OK: - subject = instance.input_entry.get_text().decode('utf-8') - gajim.connections[self.account].send_gc_subject(room_jid, subject) - - def on_change_nick_menuitem_activate(self, widget): - room_jid = self.get_active_jid() - nick = self.nicks[room_jid] - title = _('Changing Nickname') - prompt = _('Please specify the new nickname you want to use:') - self.show_change_nick_input_dialog(title, prompt, nick, room_jid) - - def show_change_nick_input_dialog(self, title, prompt, proposed_nick = None, - room_jid = None): - '''asks user for new nick and on ok it sets it on room''' - instance = dialogs.InputDialog(title, prompt, proposed_nick) - response = instance.get_response() - if response == gtk.RESPONSE_OK: - nick = instance.input_entry.get_text().decode('utf-8') - self.nicks[room_jid] = nick - gajim.connections[self.account].change_gc_nick(room_jid, nick) - - def on_configure_room_menuitem_activate(self, widget): - room_jid = self.get_active_jid() - gajim.connections[self.account].request_gc_config(room_jid) - - def on_bookmark_room_menuitem_activate(self, widget): - room_jid = self.get_active_jid() - bm = { - 'name': room_jid, - 'jid': room_jid, - 'autojoin': '0', - 'password': '', - 'nick': self.nicks[room_jid] - } - - for bookmark in gajim.connections[self.account].bookmarks: - if bookmark['jid'] == bm['jid']: - dialogs.ErrorDialog( - _('Bookmark already set'), - _('Room "%s" is already in your bookmarks.') %bm['jid']).\ - get_response() - return - - gajim.connections[self.account].bookmarks.append(bm) - gajim.connections[self.account].store_bookmarks() - - gajim.interface.roster.make_menu() - - dialogs.InformationDialog( - _('Bookmark has been added successfully'), - _('You can manage your bookmarks via Actions menu in your roster.')) - - def on_message_textview_key_press_event(self, widget, event): - '''we use this cb instead of my_key_press to catch everything else - that is not registered as binding (see message_textview.py)''' - chat.Chat.on_message_textview_key_press_event(self, widget, event) - - def on_message_textview_mykeypress_event(self, widget, event_keyval, - event_keymod): - '''When a key is pressed: - if enter is pressed without the shift key, message (if not empty) is sent - and printed in the conversation. Tab does autocomplete in nicknames''' - # NOTE: handles mykeypress which is custom signal connected to this - # CB in new_room(). for this singal see message_textview.py - room_jid = self.get_active_jid() - message_buffer = widget.get_buffer() - start_iter, end_iter = message_buffer.get_bounds() - message = message_buffer.get_text(start_iter, end_iter, - False).decode('utf-8') - - # construct event instance from binding - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 # assign current time - - if event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB - if (event.state & gtk.gdk.CONTROL_MASK): # CTRL + SHIFT + TAB - self.notebook.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.Tab: # TAB - if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB - self.notebook.emit('key_press_event', event) - else: # just TAB - 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, - False).decode('utf-8') - if not text or text.endswith(' '): - # if we are nick cycling, last char will always be space - if not self.last_key_tabs[room_jid]: - return False - - splitted_text = text.split() - # command completion - if text.startswith('/') and len(splitted_text) == 1: - text = splitted_text[0] - if len(text) == 1: # user wants to cycle all commands - self.cmd_hits[room_jid] = self.muc_cmds - else: - # cycle possible commands depending on what the user typed - if self.last_key_tabs[room_jid] and \ - self.cmd_hits[room_jid][0].startswith(text.lstrip('/')): - self.cmd_hits[room_jid].append(self.cmd_hits[room_jid][0]) - self.cmd_hits[room_jid].pop(0) - else: # find possible commands - self.cmd_hits[room_jid] = [] - for cmd in self.muc_cmds: - if cmd.startswith(text.lstrip('/')): - self.cmd_hits[room_jid].append(cmd) - if len(self.cmd_hits[room_jid]): - message_buffer.delete(start_iter, end_iter) - message_buffer.insert_at_cursor('/' + \ - self.cmd_hits[room_jid][0] + ' ') - self.last_key_tabs[room_jid] = True - return True - - # nick completion - # check if tab is pressed with empty message - if len(splitted_text): # if there are any words - begin = splitted_text[-1] # last word we typed - - if len(self.nick_hits[room_jid]) and \ - self.nick_hits[room_jid][0].startswith(begin.replace( - self.gc_refer_to_nick_char, '')) and \ - self.last_key_tabs[room_jid]: # we should cycle - self.nick_hits[room_jid].append(self.nick_hits[room_jid][0]) - self.nick_hits[room_jid].pop(0) - else: - self.nick_hits[room_jid] = [] # clear the hit list - list_nick = gajim.contacts.get_nick_list(self.account, - room_jid) - for nick in list_nick: - if nick.lower().startswith(begin.lower()): # the word is the begining of a nick - self.nick_hits[room_jid].append(nick) - if len(self.nick_hits[room_jid]): - if len(splitted_text) == 1: # This is the 1st word of the line - add = self.gc_refer_to_nick_char + ' ' - else: - add = ' ' - start_iter = end_iter.copy() - if self.last_key_tabs[room_jid] and begin.endswith(', '): - # have to accomodate for the added space from last completion - start_iter.backward_chars(len(begin) + 2) - elif self.last_key_tabs[room_jid]: - # have to accomodate for the added space from last completion - start_iter.backward_chars(len(begin) + 1) - else: - start_iter.backward_chars(len(begin)) - - message_buffer.delete(start_iter, end_iter) - message_buffer.insert_at_cursor(self.nick_hits[room_jid][0] \ - + add) - self.last_key_tabs[room_jid] = True - return True - self.last_key_tabs[room_jid] = False - return False - elif event.keyval == gtk.keysyms.Return or \ - event.keyval == gtk.keysyms.KP_Enter: # ENTER - if (event.state & gtk.gdk.SHIFT_MASK): - self.last_key_tabs[room_jid] = False - return False - if gajim.config.get('send_on_ctrl_enter'): - # here, we emulate GTK default action on ENTER (add new line) - # normally I would add in keypress but it gets way to complex - # to get instant result on changing this advanced setting - if event.state == 0: # no ctrl, no shift just ENTER add newline - end_iter = message_buffer.get_end_iter() - message_buffer.insert_at_cursor('\n') - send_message = False - elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER - send_message = True - else: # send on Enter, do newline on Ctrl Enter - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER - end_iter = message_buffer.get_end_iter() - message_buffer.insert_at_cursor('\n') - send_message = False - else: # ENTER - send_message = True - - if gajim.connections[self.account].connected < 2: - # we are not connected - dialogs.ErrorDialog(_('A connection is not available'), - _('Your message can not be sent until you are connected.')).get_response() - send_message = False - - if send_message: - self.send_gc_message(message) # send the message - return True - elif event.keyval == gtk.keysyms.Up: - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP - self.sent_messages_scroll(room_jid, 'up', widget.get_buffer()) - return True # override the default gtk+ thing for ctrl+up - elif event.keyval == gtk.keysyms.Down: - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down - self.sent_messages_scroll(room_jid, 'down', widget.get_buffer()) - return True # override the default gtk+ thing for ctrl+down - - def on_send_button_clicked(self, widget): - '''When send button is pressed: send the current message''' - room_jid = self.get_active_jid() - message_textview = self.message_textviews[room_jid] - message_buffer = message_textview.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).decode('utf-8') - - # send the message - self.send_gc_message(message) - - def send_gc_message(self, message): - '''call this function to send our message''' - if not message: - return - room_jid = self.get_active_jid() - message_textview = self.message_textviews[room_jid] - message_buffer = message_textview.get_buffer() - conv_textview = self.conversation_textviews[room_jid] - if message != '' or message != '\n': - self.save_sent_message(room_jid, message) - - if message.startswith('/') and not message.startswith('/me'): - message = message[1:] - message_array = message.split(' ', 1) - command = message_array.pop(0).lower() - if message_array == ['']: - message_array = [] - if command == 'clear': - # clear the groupchat window - conv_textview.clear() - self.clear(message_textview) - elif command == 'compact': - # set compact mode - self.set_compact_view(not self.compact_view_current_state) - self.clear(message_textview) - elif command == 'nick': - # example: /nick foo - if len(message_array): - nick = message_array[0] - gajim.connections[self.account].change_gc_nick(room_jid, - nick) - self.clear(message_textview) - else: - self.get_command_help(command) - elif command == 'query' or command == 'chat': - # Open a chat window to the specified nick - # example: /query foo - if len(message_array): - nick = message_array.pop(0) - nicks = gajim.contacts.get_nick_list(self.account, room_jid) - if nick in nicks: - self.on_send_pm(nick = nick) - self.clear(message_textview) - else: - self.print_conversation(_('Nickname not found: %s') % nick, - room_jid) - else: - self.get_command_help(command) - elif command == 'msg': - # Send a message to a nick. Also opens a private message window. - # example: /msg foo Hey, what's up? - if len(message_array): - message_array = message_array[0].split() - nick = message_array.pop(0) - room_nicks = gajim.contacts.get_nick_list(self.account, - room_jid) - if nick in room_nicks: - privmsg = ' '.join(message_array) - self.on_send_pm(nick=nick, msg=privmsg) - self.clear(message_textview) - else: - self.print_conversation(_('Nickname not found: %s') % nick, - room_jid) - else: - self.get_command_help(command) - elif command == 'topic': - # display or change the room topic - # example: /topic : print topic - # /topic foo : change topic to foo - if len(message_array): - new_topic = message_array.pop(0) - gajim.connections[self.account].send_gc_subject(room_jid, - new_topic) - else: - self.print_conversation(self.subjects[room_jid], room_jid) - self.clear(message_textview) - elif command == 'invite': - # invite a user to a room for a reason - # example: /invite user@example.com reason - if len(message_array): - message_array = message_array[0].split() - invitee = message_array.pop(0) - if invitee.find('@') >= 0: - reason = ' '.join(message_array) - gajim.connections[self.account].send_invite(room_jid, - invitee, reason) - s = _('Invited %(contact_jid)s to %(room_jid)s.') % { - 'contact_jid': invitee, - 'room_jid': room_jid} - self.print_conversation(s, room_jid) - self.clear(message_textview) - else: - #%s is something the user wrote but it is not a jid so we inform - s = _('%s does not appear to be a valid JID') % invitee - self.print_conversation(s, room_jid) - else: - self.get_command_help(command) - elif command == 'join': - # example: /join room@conference.example.com/nick - if len(message_array): - message_array = message_array[0] - if message_array.find('@') >= 0: - room, servernick = message_array.split('@') - if servernick.find('/') >= 0: - server, nick = servernick.split('/', 1) - else: - server = servernick - nick = '' - #join_gc window is needed in order to provide for password entry. - if gajim.interface.instances[self.account].has_key('join_gc'): - gajim.interface.instances[self.account]['join_gc'].\ - window.present() - else: - try: - gajim.interface.instances[self.account]['join_gc'] =\ - dialogs.JoinGroupchatWindow(self.account, - server = server, room = room, nick = nick) - except RuntimeError: - pass - self.clear(message_textview) - else: - #%s is something the user wrote but it is not a jid so we inform - s = _('%s does not appear to be a valid JID') % message_array - self.print_conversation(s, room_jid) - else: - self.get_command_help(command) - elif command == 'leave' or command == 'part' or command == 'close': - # Leave the room and close the tab or window - # FIXME: Sometimes this doesn't actually leave the room. Why? - reason = 'offline' - if len(message_array): - reason = message_array.pop(0) - self.remove_tab(room_jid, reason) - elif command == 'ban': - if len(message_array): - message_array = message_array[0].split() - nick = message_array.pop(0) - room_nicks = gajim.contacts.get_nick_list(self.account, - room_jid) - reason = ' '.join(message_array) - if nick in room_nicks: - ban_jid = gajim.construct_fjid(room_jid, nick) - gajim.connections[self.account].gc_set_affiliation(room_jid, - ban_jid, 'outcast', reason) - self.clear(message_textview) - elif nick.find('@') >= 0: - gajim.connections[self.account].gc_set_affiliation(room_jid, - nick, 'outcast', reason) - self.clear(message_textview) - else: - self.print_conversation(_('Nickname not found: %s') % nick, - room_jid) - else: - self.get_command_help(command) - elif command == 'kick': - if len(message_array): - message_array = message_array[0].split() - nick = message_array.pop(0) - room_nicks = gajim.contacts.get_nick_list(self.account, - room_jid) - reason = ' '.join(message_array) - if nick in room_nicks: - gajim.connections[self.account].gc_set_role(room_jid, nick, - 'none', reason) - self.clear(message_textview) - else: - self.print_conversation(_('Nickname not found: %s') % nick, - room_jid) - else: - self.get_command_help(command) - elif command == 'help': - if len(message_array): - subcommand = message_array.pop(0) - self.get_command_help(subcommand) - else: - self.get_command_help(command) - self.clear(message_textview) - elif command == 'say': - if len(message_array): - gajim.connections[self.account].send_gc_message(room_jid, - message[4:]) - self.clear(message_textview) - else: - self.get_command_help(command) - else: - self.print_conversation( - _('No such command: /%s (if you want to send this, prefix it with /say)') % command, - room_jid) - return # don't print the command - - gajim.connections[self.account].send_gc_message(room_jid, message) - message_buffer.set_text('') - message_textview.grab_focus() - - def get_command_help(self, command): - room_jid = self.get_active_jid() - if command == 'help': - self.print_conversation(_('Commands: %s') % self.muc_cmds, room_jid) - elif command == 'ban': - s = _('Usage: /%s [reason], bans the JID from the room.' - ' The nickname of an occupant may be substituted, but not if it contains "@".' - ' If the JID is currently in the room, he/she/it will also be kicked.' - ' Does NOT support spaces in nickname.') % command - self.print_conversation(s, room_jid) - elif command == 'chat' or command == 'query': - self.print_conversation(_('Usage: /%s , opens a private chat window \ -to the specified occupant.') % command, room_jid) - elif command == 'clear': - self.print_conversation(_('Usage: /%s, clears the text window.') % command, - room_jid) - elif command == 'close' or command == 'leave' or command == 'part': - self.print_conversation(_('Usage: /%s [reason], closes the current window \ -or tab, displaying reason if specified.') % command, room_jid) - elif command == 'compact': - self.print_conversation(_('Usage: /%s, sets the groupchat window to \ -compact mode.') % command, room_jid) - elif command == 'invite': - self.print_conversation(_('Usage: /%s [reason], invites JID to the \ -current room, optionally providing a reason.') % command, room_jid) - elif command == 'join': - self.print_conversation(_('Usage: /%s @[/nickname], offers to \ -join room@server optionally using specified nickname.') % command, room_jid) - elif command == 'kick': - self.print_conversation(_('Usage: /%s [reason], removes the \ -occupant specified by nickname from the room and optionally displays a \ -reason. Does NOT support spaces in nickname.') % command, room_jid) - elif command == 'me': - self.print_conversation(_('Usage: /%s , sends action to the \ -current room. Use third person. (e.g. /%s explodes.)') % - (command, command), room_jid) - elif command == 'msg': - s = _('Usage: /%s [message], opens a private message window and sends message to the occupant specified by nickname.') % command - self.print_conversation(s, room_jid) - elif command == 'nick': - s = _('Usage: /%s , changes your nickname in current room.') % command - self.print_conversation(s, room_jid) - elif command == 'topic': - self.print_conversation(_('Usage: /%s [topic], displays or updates the \ -current room topic.') % command, room_jid) - elif command == 'say': - self.print_conversation(_('Usage: /%s , sends a message without looking for other commands.') % command, room_jid) - else: - self.print_conversation(_('No help info for /%s') % command, room_jid) - - 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 or help''' - if isinstance(text, str): - text = unicode(text, 'utf-8') - other_tags_for_name = [] - other_tags_for_text = [] - if contact: - if contact == self.nicks[room_jid]: # it's us - kind = 'outgoing' - else: - kind = 'incoming' - # muc-specific chatstate - self.redraw_tab(room_jid, 'newmsg') - else: - kind = 'status' - - - nick = self.nicks[room_jid] - - if kind == 'incoming': # it's a message NOT from us - # highlighting and sounds - (highlight, sound) = self.highlighting_for_message(text, nick, tim) - if highlight: - self.redraw_tab(room_jid, 'attention') # muc-specific chatstate - other_tags_for_name.append('bold') - other_tags_for_text.append('marked') - if sound == 'received': - helpers.play_sound('muc_message_received') - elif sound == 'highlight': - helpers.play_sound('muc_message_highlight') - - self.check_and_possibly_add_focus_out_line(room_jid) - - chat.Chat.print_conversation_line(self, text, room_jid, kind, contact, - tim, other_tags_for_name, [], other_tags_for_text) - - def highlighting_for_message(self, text, nick, tim): - '''Returns a 2-Tuple. The first says whether or not to highlight the - text, the second, what sound to play.''' - highlight, sound = (None, None) - - # Do we play a sound on every muc message? - if gajim.config.get_per('soundevents', 'muc_message_received', 'enabled'): - if gajim.config.get('notify_on_all_muc_messages'): - sound = 'received' - - # Are any of the defined highlighting words in the text? - if self.needs_visual_notification(text, nick): - highlight = True - if gajim.config.get_per('soundevents', 'muc_message_highlight', - 'enabled'): - sound = 'highlight' - - # Is it a history message? Don't want sound-floods when we join. - if tim != time.localtime(): - sound = None - - return (highlight, sound) - - def needs_visual_notification(self, text, nick): - '''checks text to see whether any of the words in (muc_highlight_words - and nick) appear.''' - - special_words = gajim.config.get('muc_highlight_words').split(';') - special_words.append(nick) - # Strip empties: ''.split(';') == [''] and would highlight everything. - # Also lowercase everything for case insensitive compare. - special_words = [word.lower() for word in special_words if word] - text = text.lower() - - text_splitted = text.split() - for word in text_splitted: # get each word of the text - for special_word in special_words: - if word.startswith(special_word): - return True - - return False - - def kick(self, widget, room_jid, nick): - '''kick a user''' - # ask for reason - instance = dialogs.InputDialog(_('Kicking %s') % nick, - _('You may specify a reason below:')) - response = instance.get_response() - if response == gtk.RESPONSE_OK: - reason = instance.input_entry.get_text().decode('utf-8') - else: - return # stop kicking procedure - gajim.connections[self.account].gc_set_role(room_jid, nick, 'none', - reason) - - def grant_voice(self, widget, room_jid, nick): - '''grant voice privilege to a user''' - gajim.connections[self.account].gc_set_role(room_jid, nick, 'participant') - - def revoke_voice(self, widget, room_jid, nick): - '''revoke voice privilege to a user''' - gajim.connections[self.account].gc_set_role(room_jid, nick, 'visitor') - - def grant_moderator(self, widget, room_jid, nick): - '''grant moderator privilege to a user''' - gajim.connections[self.account].gc_set_role(room_jid, nick, 'moderator') - - def revoke_moderator(self, widget, room_jid, nick): - '''revoke moderator privilege to a user''' - gajim.connections[self.account].gc_set_role(room_jid, nick, 'participant') - - def ban(self, widget, room_jid, jid): - '''ban a user''' - # to ban we know the real jid. so jid is not fakejid - nick = gajim.get_nick_from_jid(jid) - # ask for reason - instance = dialogs.InputDialog(_('Banning %s') % nick, - _('You may specify a reason below:')) - response = instance.get_response() - if response == gtk.RESPONSE_OK: - reason = instance.input_entry.get_text().decode('utf-8') - else: - return # stop banning procedure - gajim.connections[self.account].gc_set_affiliation(room_jid, jid, - 'outcast', reason) - - def grant_membership(self, widget, room_jid, jid): - '''grant membership privilege to a user''' - gajim.connections[self.account].gc_set_affiliation(room_jid, jid, - 'member') - - def revoke_membership(self, widget, room_jid, jid): - '''revoke membership privilege to a user''' - gajim.connections[self.account].gc_set_affiliation(room_jid, jid, - 'none') - - def grant_admin(self, widget, room_jid, jid): - '''grant administrative privilege to a user''' - gajim.connections[self.account].gc_set_affiliation(room_jid, jid, 'admin') - - def revoke_admin(self, widget, room_jid, jid): - '''revoke administrative privilege to a user''' - gajim.connections[self.account].gc_set_affiliation(room_jid, jid, - 'member') - - def grant_owner(self, widget, room_jid, jid): - '''grant owner privilege to a user''' - gajim.connections[self.account].gc_set_affiliation(room_jid, jid, 'owner') - - def revoke_owner(self, widget, room_jid, jid): - '''revoke owner privilege to a user''' - gajim.connections[self.account].gc_set_affiliation(room_jid, jid, 'admin') - - def on_info(self, widget, room_jid, nick): - '''Call vcard_information_window class to display user's information''' - c = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - # we create a Contact instance - c2 = gajim.contacts.contact_from_gc_contact(c) - if gajim.interface.instances[self.account]['infos'].has_key(c2.jid): - gajim.interface.instances[self.account]['infos'][c2.jid].window.present() - else: - gajim.interface.instances[self.account]['infos'][c2.jid] = \ - vcard.VcardWindow(c2, self.account, False) - - def on_history(self, widget, room_jid, nick): - jid = gajim.construct_fjid(room_jid, nick) - self.on_history_menuitem_clicked(jid = jid) - - def on_add_to_roster(self, widget, jid): - dialogs.AddNewContactWindow(self.account, jid) - - def on_send_pm(self, widget=None, model=None, iter=None, nick=None, msg=None): - '''opens a chat window and msg is not None sends private message to a - contact in a room''' - if nick is None: - nick = model[iter][C_NICK].decode('utf-8') - room_jid = self.get_active_jid() - fjid = gajim.construct_fjid(room_jid, nick) # 'fake' jid - # FIXME - if not gajim.interface.instances[self.account]['chats'].has_key(fjid): - gc_c = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - c = gajim.contacts.contact_from_gc_contact(gc_c) - gajim.interface.roster.new_chat(c, self.account) - - #make active here in case we need to send a message - gajim.interface.instances[self.account]['chats'][fjid].set_active_tab(fjid) - - if msg: - gajim.interface.instances[self.account]['chats'][fjid].send_message(msg) - gajim.interface.instances[self.account]['chats'][fjid].window.present() - - def on_voice_checkmenuitem_activate(self, widget, room_jid, nick): - if widget.get_active(): - self.grant_voice(widget, room_jid, nick) - else: - self.revoke_voice(widget, room_jid, nick) - - def on_moderator_checkmenuitem_activate(self, widget, room_jid, nick): - if widget.get_active(): - self.grant_moderator(widget, room_jid, nick) - else: - self.revoke_moderator(widget, room_jid, nick) - - def on_member_checkmenuitem_activate(self, widget, room_jid, jid): - if widget.get_active(): - self.grant_membership(widget, room_jid, jid) - else: - self.revoke_membership(widget, room_jid, jid) - - def on_admin_checkmenuitem_activate(self, widget, room_jid, jid): - if widget.get_active(): - self.grant_admin(widget, room_jid, jid) - else: - self.revoke_admin(widget, room_jid, jid) - - def on_owner_checkmenuitem_activate(self, widget, room_jid, jid): - if widget.get_active(): - self.grant_owner(widget, room_jid, jid) - else: - self.revoke_owner(widget, room_jid, jid) - - def mk_menu(self, room_jid, event, iter): - '''Make contact's popup menu''' - model = self.list_treeview[room_jid].get_model() - nick = model[iter][C_NICK].decode('utf-8') - c = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - jid = c.jid - target_affiliation = c.affiliation - target_role = c.role - - # looking for user's affiliation and role - user_nick = self.nicks[room_jid] - user_affiliation = gajim.contacts.get_gc_contact(self.account, room_jid, - user_nick).affiliation - user_role = self.get_role(room_jid, user_nick) - - # making menu from glade - xml = gtk.glade.XML(GTKGUI_GLADE, 'gc_occupants_menu', APP) - - # these conditions were taken from JEP 0045 - item = xml.get_widget('kick_menuitem') - item.connect('activate', self.kick, room_jid, nick) - if user_role != 'moderator' or \ - (user_affiliation == 'admin' and target_affiliation == 'owner') or \ - (user_affiliation == 'member' and target_affiliation in ('admin', 'owner')) or \ - (user_affiliation == 'none' and target_affiliation != 'none'): - item.set_sensitive(False) - - item = xml.get_widget('voice_checkmenuitem') - item.set_active(target_role != 'visitor') - if user_role != 'moderator' or \ - user_affiliation == 'none' or \ - (user_affiliation=='member' and target_affiliation!='none') or \ - target_affiliation in ('admin', 'owner'): - item.set_sensitive(False) - item.connect('activate', self.on_voice_checkmenuitem_activate, room_jid, - nick) - - item = xml.get_widget('moderator_checkmenuitem') - item.set_active(target_role == 'moderator') - if not user_affiliation in ('admin', 'owner') or \ - target_affiliation in ('admin', 'owner'): - item.set_sensitive(False) - item.connect('activate', self.on_moderator_checkmenuitem_activate, - room_jid, nick) - - item = xml.get_widget('ban_menuitem') - if not user_affiliation in ('admin', 'owner') or \ - (target_affiliation in ('admin', 'owner') and\ - user_affiliation != 'owner'): - item.set_sensitive(False) - item.connect('activate', self.ban, room_jid, jid) - - item = xml.get_widget('member_checkmenuitem') - item.set_active(target_affiliation != 'none') - if not user_affiliation in ('admin', 'owner') or \ - (user_affiliation != 'owner' and target_affiliation in ('admin','owner')): - item.set_sensitive(False) - item.connect('activate', self.on_member_checkmenuitem_activate, - room_jid, jid) - - item = xml.get_widget('admin_checkmenuitem') - item.set_active(target_affiliation in ('admin', 'owner')) - if not user_affiliation == 'owner': - item.set_sensitive(False) - item.connect('activate', self.on_admin_checkmenuitem_activate, - room_jid, jid) - - item = xml.get_widget('owner_checkmenuitem') - item.set_active(target_affiliation == 'owner') - if not user_affiliation == 'owner': - item.set_sensitive(False) - item.connect('activate', self.on_owner_checkmenuitem_activate, - room_jid, jid) - - item = xml.get_widget('information_menuitem') - item.connect('activate', self.on_info, room_jid, nick) - - item = xml.get_widget('history_menuitem') - item.connect('activate', self.on_history, room_jid, nick) - - item = xml.get_widget('add_to_roster_menuitem') - if not jid: - item.set_sensitive(False) - item.connect('activate', self.on_add_to_roster, jid) - - item = xml.get_widget('send_private_message_menuitem') - item.connect('activate', self.on_send_pm, model, iter) - - # show the popup now! - menu = xml.get_widget('gc_occupants_menu') - menu.popup(None, None, None, event.button, event.time) - menu.show_all() - - def remove_tab(self, room_jid, reason = 'offline'): - if not self.confirm_close(room_jid): - return - - chat.Chat.remove_tab(self, room_jid, 'gc') - if len(self.xmls) > 0: - gajim.connections[self.account].send_gc_status(self.nicks[room_jid], - room_jid, show='offline', status=reason) - del self.nicks[room_jid] - # They can already be removed by the destroy function - if room_jid in gajim.contacts.get_gc_list(self.account): - gajim.contacts.remove_room(self.account, room_jid) - del gajim.gc_connected[self.account][room_jid] - del self.list_treeview[room_jid] - del self.subjects[room_jid] - del self.name_labels[room_jid] - del self.hpaneds[room_jid] - - def got_connected(self, room_jid): - gajim.gc_connected[self.account][room_jid] = True - message_textview = self.message_textviews[room_jid] - message_textview.set_sensitive(True) - self.xmls[room_jid].get_widget('send_button').set_sensitive(True) - - def got_disconnected(self, room_jid): - model = self.list_treeview[room_jid].get_model() - model.clear() - nick_list = gajim.contacts.get_nick_list(self.account, room_jid) - for nick in nick_list: - gc_contact = gajim.contacts.get_gc_contact(self.account, room_jid, - nick) - gajim.contacts.remove_gc_contact(self.account, gc_contact) - gajim.gc_connected[self.account][room_jid] = False - message_textview = self.message_textviews[room_jid] - message_textview.set_sensitive(False) - self.xmls[room_jid].get_widget('send_button').set_sensitive(False) - - def iter_contact_rows(self, room_jid): - '''iterate over all contact rows in the tree model''' - model = self.list_treeview[room_jid].get_model() - role_iter = model.get_iter_root() - while role_iter: - contact_iter = model.iter_children(role_iter) - while contact_iter: - yield model[contact_iter] - contact_iter = model.iter_next(contact_iter) - role_iter = model.iter_next(role_iter) - - def on_list_treeview_style_set(self, treeview, style): - '''When style (theme) changes, redraw all contacts''' - # Get the room_jid from treeview - for room_jid in self.list_treeview: - if self.list_treeview[room_jid] == treeview: - break - for contact in self.iter_contact_rows(room_jid): - nick = contact[C_NICK].decode('utf-8') - self.draw_contact(room_jid, nick) - - def on_list_treeview_selection_changed(self, selection): - model, selected_iter = selection.get_selected() - # get room_jid from model - for room_jid in self.list_treeview: - mod = self.list_treeview[room_jid].get_model() - if model == mod: - break - if self._last_selected_contact is not None: - # update unselected row - room_jid, nick = self._last_selected_contact - # last selected can be in a closed room, so prevent KeyError - if room_jid in self.list_treeview.keys(): - self.draw_contact(room_jid, nick) - if selected_iter is None: - self._last_selected_contact = None - return - contact = model[selected_iter] - nick = contact[C_NICK].decode('utf-8') - self._last_selected_contact = (room_jid, nick) - if contact[C_TYPE] != 'contact': - return - self.draw_contact(room_jid, nick, selected=True, focus=True) - - def new_room(self, room_jid, nick): - self.names[room_jid] = room_jid.split('@')[0] - self.xmls[room_jid] = gtk.glade.XML(GTKGUI_GLADE, 'gc_vbox', APP) - self.childs[room_jid] = self.xmls[room_jid].get_widget('gc_vbox') - self.nicks[room_jid] = nick - self.subjects[room_jid] = '' - self.room_creation[room_jid] = time.time() - self.nick_hits[room_jid] = [] - self.cmd_hits[room_jid] = [] - self.focus_out_end_iter_offset[room_jid] = None - self.allow_focus_out_line[room_jid] = True - self.last_key_tabs[room_jid] = False - self.hpaneds[room_jid] = self.xmls[room_jid].get_widget('hpaned') - list_treeview = self.list_treeview[room_jid] = self.xmls[room_jid].get_widget( - 'list_treeview') - list_treeview.get_selection().connect('changed', - self.on_list_treeview_selection_changed) - list_treeview.connect('style-set', self.on_list_treeview_style_set) - self._last_selected_contact = None # None or holds jid, account tupple - - self.subject_tooltip[room_jid] = gtk.Tooltips() - - chat.Chat.new_tab(self, room_jid) - - # we want to know when the the widget resizes, because that is - # an indication that the hpaned has moved... - # FIXME: Find a better indicator that the hpaned has moved. - self.list_treeview[room_jid].connect('size-allocate', - self.on_treeview_size_allocate) - conv_textview = self.conversation_textviews[room_jid] - self.name_labels[room_jid] = self.xmls[room_jid].get_widget( - 'banner_name_label') - self.paint_banner(room_jid) - - # connect the menuitems to their respective functions - xm = gtk.glade.XML(GTKGUI_GLADE, 'gc_popup_menu', APP) - xm.signal_autoconnect(self) - self.gc_popup_menu = xm.get_widget('gc_popup_menu') - - #status_image, type, nickname, shown_nick - store = gtk.TreeStore(gtk.Image, str, str, str) - store.set_sort_column_id(C_TEXT, gtk.SORT_ASCENDING) - column = gtk.TreeViewColumn('contacts') - renderer_image = cell_renderer_image.CellRendererImage(0, 0) - renderer_image.set_property('width', 20) - column.pack_start(renderer_image, expand = False) - column.add_attribute(renderer_image, 'image', 0) - renderer_text = gtk.CellRendererText() - column.pack_start(renderer_text, expand = True) - column.set_attributes(renderer_text, markup = C_TEXT) - column.set_cell_data_func(renderer_image, self.tree_cell_data_func, None) - column.set_cell_data_func(renderer_text, self.tree_cell_data_func, None) - - self.list_treeview[room_jid].append_column(column) - self.list_treeview[room_jid].set_model(store) - - # workaround to avoid gtk arrows to be shown - column = gtk.TreeViewColumn() # 2nd COLUMN - renderer = gtk.CellRendererPixbuf() - column.pack_start(renderer, expand = False) - self.list_treeview[room_jid].append_column(column) - column.set_visible(False) - self.list_treeview[room_jid].set_expander_column(column) - - # set the position of the current hpaned - self.hpaneds[room_jid] = self.xmls[room_jid].get_widget('hpaned') - self.hpaneds[room_jid].set_position(self.hpaned_position) - - self.redraw_tab(room_jid) - self.show_title() - # set an empty subject to show the room_jid - self.set_subject(room_jid, '') - self.got_disconnected(room_jid) #init some variables - conv_textview.grab_focus() - self.childs[room_jid].show_all() - - def on_message(self, room_jid, nick, msg, tim): - if not nick: - # message from server - self.print_conversation(msg, room_jid, tim = tim) - else: - # message from someone - self.print_conversation(msg, room_jid, nick, tim) - - def on_private_message(self, room_jid, nick, msg, tim): - # Do we have a queue? - fjid = room_jid + '/' + nick - qs = gajim.awaiting_events[self.account] - no_queue = True - if qs.has_key(fjid): - no_queue = False - - # We print if window is opened - # FIXME - if gajim.interface.instances[self.account]['chats'].has_key(fjid): - chat_win = gajim.interface.instances[self.account]['chats'][fjid] - chat_win.print_conversation(msg, fjid, tim = tim) - return - - if no_queue: - qs[fjid] = [] - qs[fjid].append(('chat', (msg, '', 'incoming', tim, False, ''))) - self.nb_unread[room_jid] += 1 - - autopopup = gajim.config.get('autopopup') - autopopupaway = gajim.config.get('autopopupaway') - iter = self.get_contact_iter(room_jid, nick) - path = self.list_treeview[room_jid].get_model().get_path(iter) - if not autopopup or ( not autopopupaway and \ - gajim.connections[self.account].connected > 2): - if no_queue: # We didn't have a queue: we change icons - model = self.list_treeview[room_jid].get_model() - state_images = gajim.interface.roster.get_appropriate_state_images(room_jid) - image = state_images['message'] - model[iter][C_IMG] = image - if gajim.interface.systray_enabled: - gajim.interface.systray.add_jid(fjid, self.account, 'pm') - self.show_title() - else: - gc_c = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - c = gajim.contacts.contact_from_gc_contact(gc_c) - gajim.interface.roster.new_chat(c, self.account) - # Scroll to line - self.list_treeview[room_jid].expand_row(path[0:1], False) - self.list_treeview[room_jid].scroll_to_cell(path) - self.list_treeview[room_jid].set_cursor(path) - - - def set_state_image(self, jid): - # FIXME: Tab notifications? - pass - - def on_list_treeview_motion_notify_event(self, widget, event): - model = widget.get_model() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id != props[0]: - self.tooltip.hide_tooltip() - if props: - [row, col, x, y] = props - iter = None - try: - iter = model.get_iter(row) - except: - self.tooltip.hide_tooltip() - return - room_jid = self.get_active_jid() - typ = model[iter][C_TYPE].decode('utf-8') - if typ == 'contact': - account = self.account - - if self.tooltip.timeout == 0 or self.tooltip.id != props[0]: - self.tooltip.id = row - nick = model[iter][C_NICK].decode('utf-8') - self.tooltip.timeout = gobject.timeout_add(500, - self.show_tooltip, gajim.contacts.get_gc_contact(account, - room_jid, nick)) - - def on_list_treeview_leave_notify_event(self, widget, event): - model = widget.get_model() - props = widget.get_path_at_pos(int(event.x), int(event.y)) - if self.tooltip.timeout > 0: - if not props or self.tooltip.id == props[0]: - self.tooltip.hide_tooltip() - - def show_tooltip(self, contact): - room_jid = self.get_active_jid() - pointer = self.list_treeview[room_jid].get_pointer() - props = self.list_treeview[room_jid].get_path_at_pos(pointer[0], pointer[1]) - if props and self.tooltip.id == props[0]: - # check if the current pointer is at the same path - # as it was before setting the timeout - rect = self.list_treeview[room_jid].get_cell_area(props[0],props[1]) - position = self.list_treeview[room_jid].window.get_origin() - pointer = self.window.get_pointer() - self.tooltip.show_tooltip(contact, (0, rect.height), - (self.window.get_screen().get_display().get_pointer()[1], - position[1] + rect.y)) - else: - self.tooltip.hide_tooltip() - - def on_treeview_size_allocate(self, widget, allocation): - '''The MUC treeview has resized. Move the hpaneds in all tabs to match''' - thisroom_jid = self.get_active_jid() - self.hpaned_position = self.hpaneds[thisroom_jid].get_position() - for room_jid in self.xmls: - self.hpaneds[room_jid].set_position(self.hpaned_position) - - def tree_cell_data_func(self, column, renderer, model, iter, data=None): - theme = gajim.config.get('roster_theme') - if model.iter_parent(iter): - bgcolor = gajim.config.get_per('themes', theme, 'contactbgcolor') - else: # it is root (eg. group) - bgcolor = gajim.config.get_per('themes', theme, 'groupbgcolor') - if bgcolor: - renderer.set_property('cell-background', bgcolor) - else: - renderer.set_property('cell-background', None) - - def on_list_treeview_button_press_event(self, widget, event): - '''popup user's group's or agent menu''' - room_jid = self.get_active_jid() - if event.button == 3: # right click - try: - path, column, x, y = widget.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - widget.get_selection().unselect_all() - return - widget.get_selection().select_path(path) - model = widget.get_model() - iter = model.get_iter(path) - if len(path) == 2: - self.mk_menu(room_jid, event, iter) - return True - - elif event.button == 2: # middle click - try: - path, column, x, y = widget.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - widget.get_selection().unselect_all() - return - widget.get_selection().select_path(path) - model = widget.get_model() - iter = model.get_iter(path) - if len(path) == 2: - nick = model[iter][C_NICK].decode('utf-8') - fjid = gajim.construct_fjid(room_jid, nick) - # FIXME - if not gajim.interface.instances[self.account]['chats'].has_key(fjid): - gc_c = gajim.contacts.get_gc_contact(self.account, room_jid, - nick) - c = gajim.contacts.contact_from_gc_contact(gc_c) - gajim.interface.roster.new_chat(c, self.account) - gajim.interface.instances[self.account]['chats'][fjid].set_active_tab(fjid) - gajim.interface.instances[self.account]['chats'][fjid].window.present() - return True - - elif event.button == 1: # left click - try: - path, column, x, y = widget.get_path_at_pos(int(event.x), - int(event.y)) - except TypeError: - widget.get_selection().unselect_all() - return - - model = widget.get_model() - iter = model.get_iter(path) - nick = model[iter][C_NICK].decode('utf-8') - if not nick in gajim.contacts.get_nick_list(self.account, room_jid): - #it's a group - if x < 20: # first cell in 1st column (the arrow SINGLE clicked) - if (widget.row_expanded(path)): - widget.collapse_row(path) - else: - widget.expand_row(path, False) - - def on_list_treeview_key_press_event(self, widget, event): - if event.keyval == gtk.keysyms.Escape: - widget.get_selection().unselect_all() - - def on_list_treeview_row_activated(self, widget, path, col = 0): - '''When an iter is double clicked: open the chat window''' - model = widget.get_model() - iter = model.get_iter(path) - if len(path) == 1: # It's a group - if (widget.row_expanded(path)): - widget.collapse_row(path) - else: - widget.expand_row(path, False) - else: # We want to send a private message - room_jid = self.get_active_jid() - nick = model[iter][C_NICK].decode('utf-8') - gc_c = gajim.contacts.get_gc_contact(self.account, room_jid, nick) - c = gajim.contacts.contact_from_gc_contact(gc_c) - if not gajim.interface.instances[self.account]['chats'].has_key(c.jid): - gajim.interface.roster.new_chat(c, self.account) - gajim.interface.instances[self.account]['chats'][c.jid].set_active_tab( - c.jid) - gajim.interface.instances[self.account]['chats'][c.jid].window.present() - - 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() - image = gajim.interface.roster.jabber_state_images['16']['opened'] - model[iter][C_IMG] = image - - 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() - image = gajim.interface.roster.jabber_state_images['16']['closed'] - model[iter][C_IMG] = image diff --git a/src/gtkgui.glade b/src/gtkgui.glade index a7cac5e31..4cc7702db 100644 --- a/src/gtkgui.glade +++ b/src/gtkgui.glade @@ -9369,695 +9369,6 @@ Custom - - Group Chat - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE - False - 600 - 400 - True - False - True - False - False - GDK_WINDOW_TYPE_HINT_NORMAL - GDK_GRAVITY_NORTH_WEST - True - - - - - - - True - 0.5 - 0.5 - 1 - 1 - 0 - 0 - 0 - 0 - - - - True - False - False - GTK_POS_TOP - True - False - - - - - - 3 - False - 0 - - - - True - 0.5 - 0.5 - 1 - 1 - 0 - 1 - 3 - 3 - - - - - - - 0 - False - False - - - - - - 3 - True - True - 495 - - - - 150 - True - False - 6 - - - - True - False - 6 - - - - 200 - 60 - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - GTK_CORNER_TOP_LEFT - - - - - - - 0 - True - True - - - - - - True - True - GTK_POLICY_NEVER - GTK_POLICY_NEVER - GTK_SHADOW_IN - GTK_CORNER_TOP_LEFT - - - - - - - 0 - False - True - - - - - 0 - True - True - - - - - False - False - - - - - - 100 - True - GTK_POLICY_NEVER - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - GTK_CORNER_TOP_LEFT - - - - 1 - True - True - False - False - False - True - False - False - False - - - - - - - - - - - - False - False - - - - - 0 - True - True - - - - - - 3 - True - False - 0 - - - - False - 0 - - - - - - - - Bold - True - GTK_RELIEF_NONE - False - False - False - - - - True - gtk-bold - 3 - 0.5 - 0.5 - 0 - 0 - - - - - 0 - False - False - - - - - - Italic - True - GTK_RELIEF_NONE - False - False - False - - - - True - gtk-italic - 3 - 0.5 - 0.5 - 0 - 0 - - - - - 0 - False - False - - - - - - Underline - True - GTK_RELIEF_NONE - False - False - False - - - - True - gtk-underline - 3 - 0.5 - 0.5 - 0 - 0 - - - - - 0 - False - False - - - - - 0 - True - True - - - - - - True - False - 6 - - - - True - Click to insert an emoticon (Alt+E) - True - GTK_RELIEF_NORMAL - True - - - - - True - False - 0 - - - - True - 0.5 - 0.5 - 2 - 0 - - - 0 - True - True - - - - - - True - GTK_ARROW_DOWN - GTK_SHADOW_OUT - 0.5 - 0.5 - 0 - 0 - - - 0 - True - True - - - - - - - 0 - False - False - - - - - - True - True - GTK_RELIEF_NORMAL - True - - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - - True - False - 2 - - - - True - gtk-preferences - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - True - _Actions - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - True - GTK_ARROW_DOWN - GTK_SHADOW_OUT - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - - - - 0 - False - False - - - - - - True - - - 2 - False - True - - - - - - True - True - GTK_RELIEF_NORMAL - True - - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - - True - False - 2 - - - - True - gtk-jump-to - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - True - _Send - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - - - - 0 - False - False - - - - - 0 - False - True - - - - - 0 - False - True - - - - - False - True - - - - - - True - False - False - - - - - 2 - True - False - 4 - - - - True - gtk-no - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - True - - False - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - 20 - 20 - True - True - GTK_RELIEF_NONE - True - - - - - True - gtk-close - 1 - 0.5 - 0.5 - 0 - 6 - - - - - 0 - False - False - - - - - - - tab - - - - - - - - 6 Join Group Chat @@ -10935,743 +10246,6 @@ topic - - True - Chat - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE - False - 460 - 430 - True - False - True - False - False - GDK_WINDOW_TYPE_HINT_NORMAL - GDK_GRAVITY_NORTH_WEST - True - - - - - - - - True - 0.5 - 0.5 - 1 - 1 - 0 - 0 - 0 - 0 - - - - True - False - False - GTK_POS_TOP - True - False - - - - - - 3 - True - False - 1 - - - - True - 0.5 - 0.5 - 1 - 1 - 0 - 0 - 3 - 3 - - - - - - - 0 - False - False - - - - - - True - False - 0 - - - - 3 - 60 - True - True - GTK_POLICY_AUTOMATIC - GTK_POLICY_AUTOMATIC - GTK_SHADOW_IN - GTK_CORNER_TOP_LEFT - - - - - - - 0 - True - True - - - - - - 3 - True - True - GTK_POLICY_NEVER - GTK_POLICY_NEVER - GTK_SHADOW_IN - GTK_CORNER_TOP_LEFT - - - - - - - 0 - False - True - - - - - 0 - True - True - - - - - - True - False - 0 - - - - True - False - 1 - - - - True - OpenPGP Encryption - True - False - - - - True - GTK_RELIEF_NONE - False - False - False - - - - True - gtk-dialog-authentication - 4 - 0.5 - 0.5 - 0 - 0 - - - - - - - 0 - False - True - - - - - - True - - - 0 - False - True - - - - - - Bold - True - GTK_RELIEF_NONE - False - False - False - - - - True - gtk-bold - 3 - 0.5 - 0.5 - 0 - 0 - - - - - 0 - False - False - - - - - - Italic - True - GTK_RELIEF_NONE - False - False - False - - - - True - gtk-italic - 3 - 0.5 - 0.5 - 0 - 0 - - - - - 0 - False - False - - - - - - Underline - True - GTK_RELIEF_NONE - False - False - False - - - - True - gtk-underline - 3 - 0.5 - 0.5 - 0 - 0 - - - - - 0 - False - False - - - - - 0 - True - True - - - - - - 3 - True - False - 6 - - - - True - Click to insert an emoticon (Alt+E) - True - GTK_RELIEF_NORMAL - True - - - - - True - False - 0 - - - - True - 0.5 - 0.5 - 2 - 0 - - - 0 - True - True - - - - - - True - GTK_ARROW_DOWN - GTK_SHADOW_OUT - 0.5 - 0.5 - 0 - 0 - - - 0 - True - True - - - - - - - 0 - False - False - - - - - - True - True - GTK_RELIEF_NORMAL - True - - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - - True - False - 2 - - - - True - gtk-preferences - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - True - _Actions - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - True - GTK_ARROW_DOWN - GTK_SHADOW_OUT - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - - - - 0 - False - False - - - - - - True - - - 0 - False - False - - - - - - True - True - GTK_RELIEF_NORMAL - True - - - - - True - 0.5 - 0.5 - 0 - 0 - 0 - 0 - 0 - 0 - - - - True - False - 2 - - - - True - gtk-jump-to - 4 - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - True - _Send - True - False - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - - - - 0 - False - False - - - - - 0 - False - True - - - - - 0 - False - True - - - - - False - True - - - - - - True - False - False - - - - - True - False - 4 - - - - True - 0.5 - 0.5 - 0 - 0 - - - 0 - False - False - - - - - - True - - False - True - GTK_JUSTIFY_LEFT - False - False - 0.5 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_NONE - -1 - False - 0 - - - 0 - False - False - - - - - - 20 - 20 - True - True - GTK_RELIEF_NONE - True - - - - - True - gtk-close - 1 - 0.5 - 0.5 - 0 - 6 - - - - - 0 - False - False - - - - - - - tab - - - - - - - - 6 Edit Groups diff --git a/src/tabbed_chat_window.py b/src/tabbed_chat_window.py deleted file mode 100644 index 800f61cd8..000000000 --- a/src/tabbed_chat_window.py +++ /dev/null @@ -1,965 +0,0 @@ -## tabbed_chat_window.py -## -## Contributors for this file: -## - Yann Le Boulanger -## - Nikos Kouremenos -## - Travis Shirk -## -## Copyright (C) 2003-2004 Yann Le Boulanger -## Vincent Hanquez -## Copyright (C) 2005 Yann Le Boulanger -## Vincent Hanquez -## Nikos Kouremenos -## Dimitur Kirov -## Travis Shirk -## Norman Rasmussen -## -## 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 os - -import dialogs -import chat -import gtkgui_helpers - -from common import gajim -from common import helpers -from common.logger import Constants -constants = Constants() - -from common import i18n - -_ = i18n._ -APP = i18n.APP -gtk.glade.bindtextdomain(APP, i18n.DIR) -gtk.glade.textdomain(APP) - -GTKGUI_GLADE = 'gtkgui.glade' - -class TabbedChatWindow(chat.Chat): - '''Class for tabbed chat window''' - def __init__(self, contact, account): - # we check that on opening new windows - self.always_compact_view = gajim.config.get('always_compact_view_chat') - chat.Chat.__init__(self, account, 'tabbed_chat_window') - self.contacts = {} - # keep check for possible paused timeouts per jid - self.possible_paused_timeout_id = {} - # keep check for possible inactive timeouts per jid - self.possible_inactive_timeout_id = {} - - # keep timeout id and window obj for possible big avatar - # it is on enter-notify and leave-notify so no need to be per jid - self.show_bigger_avatar_timeout_id = None - self.bigger_avatar_window = None - - self.TARGET_TYPE_URI_LIST = 80 - self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ) ] - self.new_tab(contact) - self.show_title() - - # NOTE: if it not a window event, do not connect here (new_tab() autoconnects) - signal_dict = { -'on_tabbed_chat_window_destroy': self.on_tabbed_chat_window_destroy, -'on_tabbed_chat_window_delete_event': self.on_tabbed_chat_window_delete_event, -'on_tabbed_chat_window_focus_in_event': self.on_tabbed_chat_window_focus_in_event, -'on_chat_notebook_key_press_event': self.on_chat_notebook_key_press_event, -'on_chat_notebook_switch_page': self.on_chat_notebook_switch_page, # in chat.py -'on_tabbed_chat_window_motion_notify_event': self.on_tabbed_chat_window_motion_notify_event, - } - - self.xml.signal_autoconnect(signal_dict) - - - if gajim.config.get('saveposition'): - if gtkgui_helpers.one_window_opened('chats'): - # get window position and size from config - gtkgui_helpers.move_window(self.window, gajim.config.get('chat-x-position'), - gajim.config.get('chat-y-position')) - # Even if one is already opened we can use the saved size - gtkgui_helpers.resize_window(self.window, gajim.config.get('chat-width'), - gajim.config.get('chat-height')) - - # gtk+ doesn't make use of the motion notify on gtkwindow by default - # so this line adds that - self.window.set_events(gtk.gdk.POINTER_MOTION_MASK) - - self.window.show_all() - - def save_var(self, jid): - '''return the specific variable of a jid, like gpg_enabled - the return value have to be compatible with wthe one given to load_var''' - gpg_enabled = self.xmls[jid].get_widget('gpg_togglebutton').get_active() - return {'gpg_enabled': gpg_enabled} - - def load_var(self, jid, var): - if not self.xmls.has_key(jid): - return - self.xmls[jid].get_widget('gpg_togglebutton').set_active( - var['gpg_enabled']) - - def on_tabbed_chat_window_motion_notify_event(self, widget, event): - '''it gets called no matter if it is the active window or not''' - if widget.get_property('has-toplevel-focus'): - # change chatstate only if window is the active one - self.mouse_over_in_last_5_secs = True - self.mouse_over_in_last_30_secs = True - - def on_drag_data_received(self, widget, context, x, y, selection, - target_type, timestamp, contact): - # If not resource, we can't send file - if not contact.resource: - return - if target_type == self.TARGET_TYPE_URI_LIST: - uri = selection.data.strip() - uri_splitted = uri.split() # we may have more than one file dropped - for uri in uri_splitted: - path = helpers.get_file_path_from_dnd_dropped_uri(uri) - if os.path.isfile(path): # is it file? - gajim.interface.instances['file_transfers'].send_file(self.account, - contact, path) - - def on_avatar_eventbox_enter_notify_event(self, widget, event): - '''we enter the eventbox area so we under conditions add a timeout - to show a bigger avatar after 0.5 sec''' - jid = self.get_active_jid() - real_jid = gajim.get_real_jid_from_fjid(self.account, jid) - if not real_jid: # this can happend if we're in a moderate room - return - avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(real_jid) - if avatar_pixbuf in ('ask', None): - return - avatar_w = avatar_pixbuf.get_width() - avatar_h = avatar_pixbuf.get_height() - - scaled_buf = self.xmls[jid].get_widget('avatar_image').get_pixbuf() - scaled_buf_w = scaled_buf.get_width() - scaled_buf_h = scaled_buf.get_height() - - # do we have something bigger to show? - if avatar_w > scaled_buf_w or avatar_h > scaled_buf_h: - # wait for 0.5 sec in case we leave earlier - self.show_bigger_avatar_timeout_id = gobject.timeout_add(500, - self.show_bigger_avatar, widget) - - def on_avatar_eventbox_leave_notify_event(self, widget, event): - '''we left the eventbox area that holds the avatar img''' - # did we add a timeout? if yes remove it - if self.show_bigger_avatar_timeout_id is not None: - gobject.source_remove(self.show_bigger_avatar_timeout_id) - - def show_bigger_avatar(self, small_avatar): - '''resizes the avatar, if needed, so it has at max half the screen size - and shows it''' - jid = self.get_active_jid() - real_jid = gajim.get_real_jid_from_fjid(self.account, jid) - if not real_jid: # this can happend if we're in a moderate room - return - avatar_pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(real_jid) - screen_w = gtk.gdk.screen_width() - screen_h = gtk.gdk.screen_height() - avatar_w = avatar_pixbuf.get_width() - avatar_h = avatar_pixbuf.get_height() - half_scr_w = screen_w / 2 - half_scr_h = screen_h / 2 - if avatar_w > half_scr_w: - avatar_w = half_scr_w - if avatar_h > half_scr_h: - avatar_h = half_scr_h - window = gtk.Window(gtk.WINDOW_POPUP) - self.bigger_avatar_window = window - pixmap, mask = avatar_pixbuf.render_pixmap_and_mask() - window.set_size_request(avatar_w, avatar_h) - # we should make the cursor visible - # gtk+ doesn't make use of the motion notify on gtkwindow by default - # so this line adds that - window.set_events(gtk.gdk.POINTER_MOTION_MASK) - window.set_app_paintable(True) - - window.realize() - window.window.set_back_pixmap(pixmap, False) # make it transparent - window.window.shape_combine_mask(mask, 0, 0) - - # make the bigger avatar window show up centered - x0, y0 = small_avatar.window.get_origin() - x0 += small_avatar.allocation.x - y0 += small_avatar.allocation.y - center_x= x0 + (small_avatar.allocation.width / 2) - center_y = y0 + (small_avatar.allocation.height / 2) - pos_x, pos_y = center_x - (avatar_w / 2), center_y - (avatar_h / 2) - window.move(pos_x, pos_y) - # make the cursor invisible so we can see the image - invisible_cursor = gtkgui_helpers.get_invisible_cursor() - window.window.set_cursor(invisible_cursor) - - # we should hide the window - window.connect('leave_notify_event', - self.on_window_avatar_leave_notify_event) - window.connect('motion-notify-event', - self.on_window_motion_notify_event) - - window.show_all() - - def on_window_avatar_leave_notify_event(self, widget, event): - '''we just left the popup window that holds avatar''' - self.bigger_avatar_window.destroy() - - def on_window_motion_notify_event(self, widget, event): - '''we just moved the mouse so show the cursor''' - cursor = gtk.gdk.Cursor(gtk.gdk.LEFT_PTR) - self.bigger_avatar_window.window.set_cursor(cursor) - - def draw_widgets(self, contact): - '''draw the widgets in a tab (f.e. gpg togglebutton) - according to the the information in the contact variable''' - jid = contact.jid - self.set_state_image(jid) - tb = self.xmls[jid].get_widget('gpg_togglebutton') - if contact.keyID: # we can do gpg - tb.set_sensitive(True) - tt = _('OpenPGP Encryption') - else: - tb.set_sensitive(False) - #we talk about a contact here - tt = _('%s has not broadcast an OpenPGP key, nor has one neen assigned') %\ - contact.name - tip = gtk.Tooltips() - tip.set_tip(self.xmls[jid].get_widget('gpg_eventbox'), tt) - - # add the fat line at the top - self.draw_name_banner(contact) - - def draw_name_banner(self, contact, chatstate = None): - '''Draw the fat line at the top of the window that - houses the status icon, name, jid, and avatar''' - # this is the text for the big brown bar - # some chars need to be escaped.. - jid = contact.jid - banner_name_label = self.xmls[jid].get_widget('banner_name_label') - - name = gtkgui_helpers.escape_for_pango_markup(contact.name) - - status = contact.status - - if status is not None: - banner_name_label.set_ellipsize(pango.ELLIPSIZE_END) - status = gtkgui_helpers.reduce_chars_newlines(status, 0, 2) - - status = gtkgui_helpers.escape_for_pango_markup(status) - - #FIXME: uncomment me when we support sending messages to specific resource - # composing full jid - #fulljid = jid - #if self.contacts[jid].resource: - # fulljid += '/' + self.contacts[jid].resource - #label_text = '%s\n%s' \ - # % (name, fulljid) - - - st = gajim.config.get('chat_state_notifications') - if contact.chatstate and st in ('composing_only', 'all'): - if contact.show == 'offline': - chatstate = '' - elif st == 'all': - chatstate = helpers.get_uf_chatstate(contact.chatstate) - else: # 'composing_only' - if chatstate in ('composing', 'paused'): - # only print composing, paused - chatstate = helpers.get_uf_chatstate(contact.chatstate) - else: - chatstate = '' - label_text = \ - '%s %s' % (name, chatstate) - else: - label_text = '%s' % name - - if status is not None: - label_text += '\n%s' % status - - # setup the label that holds name and jid - banner_name_label.set_markup(label_text) - self.paint_banner(jid) - - def get_specific_unread(self, jid): - # return the number of unread (private) msgs with contacts in the room - # when gc, and that is 0 in tc - # FIXME: maybe refactor so this func is not called at all if TC? - return 0 - - def show_avatar(self, jid, resource): - # Get the XML instance - if not gajim.config.get('show_avatar_in_chat'): - return - jid_with_resource = jid - if resource: - jid_with_resource += '/' + resource - - xml = None - if self.xmls.has_key(jid): - xml = self.xmls[jid] - else: - # it can be xmls[jid/resource] if it's a vcard from pm - if self.xmls.has_key(jid_with_resource): - xml = self.xmls[jid_with_resource] - if not xml: - return - - # we assume contact has no avatar - scaled_pixbuf = None - - real_jid = gajim.get_real_jid_from_fjid(self.account, jid) - pixbuf = None - if real_jid: - pixbuf = gtkgui_helpers.get_avatar_pixbuf_from_cache(real_jid) - if not real_jid or pixbuf == 'ask': - # we don't have the vcard or it's pm and we don't have the real jid - gajim.connections[self.account].request_vcard(jid_with_resource) - return - if pixbuf is not None: - scaled_pixbuf = gtkgui_helpers.get_scaled_pixbuf(pixbuf, 'chat') - - - image = xml.get_widget('avatar_image') - image.set_from_pixbuf(scaled_pixbuf) - image.show_all() - - def set_state_image(self, jid): - prio = 0 - contact_list = gajim.contacts.get_contact(self.account, jid) - if not contact_list: - contact_list = [self.contacts[jid]] - - contact = contact_list[0] - show = contact.show - jid = contact.jid - keyID = contact.keyID - - for u in contact_list: - if u.priority > prio: - prio = u.priority - show = u.show - keyID = u.keyID - child = self.childs[jid] - hb = self.notebook.get_tab_label(child).get_children()[0] - status_image = hb.get_children()[0] - - state_images_32 = gajim.interface.roster.get_appropriate_state_images(jid, - size = '32') - state_images_16 = gajim.interface.roster.get_appropriate_state_images(jid) - - # Set banner image - if state_images_32.has_key(show) and state_images_32[show].get_pixbuf(): - # we have 32x32! use it! - banner_image = state_images_32[show] - use_size_32 = True - else: - banner_image = state_images_16[show] - use_size_32 = False - - banner_status_image = self.xmls[jid].get_widget('banner_status_image') - if banner_image.get_storage_type() == gtk.IMAGE_ANIMATION: - banner_status_image.set_from_animation(banner_image.get_animation()) - else: - pix = banner_image.get_pixbuf() - if use_size_32: - banner_status_image.set_from_pixbuf(pix) - else: # we need to scale 16x16 to 32x32 - scaled_pix = pix.scale_simple(32, 32, gtk.gdk.INTERP_BILINEAR) - banner_status_image.set_from_pixbuf(scaled_pix) - - # Set tab image (always 16x16); unread messages show the 'message' image - if self.nb_unread[jid] and gajim.config.get('show_unread_tab_icon'): - tab_image = state_images_16['message'] - else: - tab_image = state_images_16[show] - if tab_image.get_storage_type() == gtk.IMAGE_ANIMATION: - status_image.set_from_animation(tab_image.get_animation()) - else: - status_image.set_from_pixbuf(tab_image.get_pixbuf()) - - if keyID: - self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(True) - else: - self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(False) - - def on_tabbed_chat_window_delete_event(self, widget, event): - '''close window''' - for jid in self.contacts: - if time.time() - gajim.last_message_time[self.account][jid] < 2: - # 2 seconds - dialog = dialogs.ConfirmationDialog( - #%s is being replaced in the code with JID - _('You just received a new message from "%s"' % jid), - _('If you close this tab and you have history disabled, this message will be lost.')) - if dialog.get_response() != gtk.RESPONSE_OK: - return True #stop the propagation of the event - - if gajim.config.get('saveposition'): - # save the window size and position - x, y = self.window.get_position() - gajim.config.set('chat-x-position', x) - gajim.config.set('chat-y-position', y) - width, height = self.window.get_size() - gajim.config.set('chat-width', width) - gajim.config.set('chat-height', height) - - def on_tabbed_chat_window_destroy(self, widget): - # Reset contact chatstates to all open tabs - for jid in self.xmls: - self.send_chatstate('gone', jid) - self.contacts[jid].chatstate = None - self.contacts[jid].our_chatstate = None - #clean gajim.interface.instances[self.account]['chats'] - chat.Chat.on_window_destroy(self, widget, 'chats') - - def on_tabbed_chat_window_focus_in_event(self, widget, event): - chat.Chat.on_chat_window_focus_in_event(self, widget, event) - # on focus in, send 'active' chatstate to current tab - self.send_chatstate('active') - - def on_chat_notebook_key_press_event(self, widget, event): - chat.Chat.on_chat_notebook_key_press_event(self, widget, event) - - def on_send_file_menuitem_activate(self, widget): - jid = self.get_active_jid() - contact = gajim.contacts.get_first_contact_from_jid(self.account, jid) - gajim.interface.instances['file_transfers'].show_file_send_request( - self.account, contact) - - def on_add_to_roster_menuitem_activate(self, widget): - jid = self.get_active_jid() - dialogs.AddNewContactWindow(self.account, jid) - - def on_send_button_clicked(self, widget): - '''When send button is pressed: send the current message''' - jid = self.get_active_jid() - message_textview = self.message_textviews[jid] - message_buffer = message_textview.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).decode('utf-8') - - # send the message - self.send_message(message) - - def on_toggle_gpg_menuitem_activate(self, widget): - jid = self.get_active_jid() - tb = self.xmls[jid].get_widget('gpg_togglebutton') - tb.set_active(not tb.get_active()) - - def remove_tab(self, jid): - if time.time() - gajim.last_message_time[self.account][jid] < 2: - dialog = dialogs.ConfirmationDialog( - _('You just received a new message from "%s"' % jid), - _('If you close this tab and you have history disabled, the message will be lost.')) - if dialog.get_response() != gtk.RESPONSE_OK: - return - - # chatstates - tab is destroyed, send gone and reset - self.send_chatstate('gone', jid) - self.contacts[jid].chatstate = None - self.contacts[jid].our_chatstate = None - - chat.Chat.remove_tab(self, jid, 'chats') - del self.contacts[jid] - - def new_tab(self, contact): - '''when new tab is created''' - self.names[contact.jid] = contact.name - self.xmls[contact.jid] = gtk.glade.XML(GTKGUI_GLADE, 'chats_vbox', APP) - self.childs[contact.jid] = self.xmls[contact.jid].get_widget('chats_vbox') - self.contacts[contact.jid] = contact - - self.show_avatar(contact.jid, contact.resource) - - self.childs[contact.jid].connect('drag_data_received', - self.on_drag_data_received, contact) - self.childs[contact.jid].drag_dest_set( gtk.DEST_DEFAULT_MOTION | - gtk.DEST_DEFAULT_HIGHLIGHT | gtk.DEST_DEFAULT_DROP, - self.dnd_list, gtk.gdk.ACTION_COPY) - - chat.Chat.new_tab(self, contact.jid) - - msg_textview = self.message_textviews[contact.jid] - message_tv_buffer = msg_textview.get_buffer() - message_tv_buffer.connect('changed', - self.on_message_tv_buffer_changed, contact) - - if contact.jid in gajim.encrypted_chats[self.account]: - self.xmls[contact.jid].get_widget('gpg_togglebutton').set_active(True) - - xm = gtk.glade.XML(GTKGUI_GLADE, 'chat_control_popup_menu', APP) - xm.signal_autoconnect(self) - self.tabbed_chat_popup_menu = xm.get_widget('chat_control_popup_menu') - - self.redraw_tab(contact.jid) - self.draw_widgets(contact) - - # restore previous conversation - self.restore_conversation(contact.jid) - - if gajim.awaiting_events[self.account].has_key(contact.jid): - self.read_queue(contact.jid) - - self.childs[contact.jid].show_all() - - # chatstates - self.reset_kbd_mouse_timeout_vars() - - self.possible_paused_timeout_id[contact.jid] = gobject.timeout_add( - 5000, self.check_for_possible_paused_chatstate, contact.jid) - self.possible_inactive_timeout_id[contact.jid] = gobject.timeout_add( - 30000, self.check_for_possible_inactive_chatstate, contact.jid) - - def handle_incoming_chatstate(self, account, contact): - ''' handle incoming chatstate that jid SENT TO us ''' - self.draw_name_banner(contact, contact.chatstate) - # update chatstate in tab for this chat - self.redraw_tab(contact.jid, contact.chatstate) - - def check_for_possible_paused_chatstate(self, jid): - ''' did we move mouse of that window or write something in message - textview - in the last 5 seconds? - if yes we go active for mouse, composing for kbd - if no we go paused if we were previously composing ''' - contact = gajim.contacts.get_first_contact_from_jid(self.account, jid) - if jid not in self.xmls or contact is None: - # the tab with jid is no longer open or contact left - # stop timer - return False # stop looping - - current_state = contact.our_chatstate - if current_state is False: # jid doesn't support chatstates - return False # stop looping - - message_textview = self.message_textviews[jid] - message_buffer = message_textview.get_buffer() - if self.kbd_activity_in_last_5_secs and message_buffer.get_char_count(): - # Only composing if the keyboard activity was in text entry - self.send_chatstate('composing', jid) - elif self.mouse_over_in_last_5_secs and jid == self.get_active_jid(): - self.send_chatstate('active', jid) - else: - if current_state == 'composing': - self.send_chatstate('paused', jid) # pause composing - - # assume no activity and let the motion-notify or 'insert-text' make them True - # refresh 30 seconds vars too or else it's 30 - 5 = 25 seconds! - self.reset_kbd_mouse_timeout_vars() - return True # loop forever - - def check_for_possible_inactive_chatstate(self, jid): - ''' did we move mouse over that window or wrote something in message - textview - in the last 30 seconds? - if yes we go active - if no we go inactive ''' - contact = gajim.contacts.get_first_contact_from_jid(self.account, jid) - if jid not in self.xmls or contact is None: - # the tab with jid is no longer open or contact left - return False # stop looping - - current_state = contact.our_chatstate - if current_state is False: # jid doesn't support chatstates - return False # stop looping - - if self.mouse_over_in_last_5_secs or self.kbd_activity_in_last_5_secs: - return True # loop forever - - if not (self.mouse_over_in_last_30_secs or\ - self.kbd_activity_in_last_30_secs): - self.send_chatstate('inactive', jid) - - # assume no activity and let the motion-notify or 'insert-text' make them True - # refresh 30 seconds too or else it's 30 - 5 = 25 seconds! - self.reset_kbd_mouse_timeout_vars() - - return True # loop forever - - def on_message_tv_buffer_changed(self, textbuffer, contact): - self.kbd_activity_in_last_5_secs = True - self.kbd_activity_in_last_30_secs = True - if textbuffer.get_char_count(): - self.send_chatstate('composing', contact.jid) - else: - self.send_chatstate('active', contact.jid) - - def reset_kbd_mouse_timeout_vars(self): - self.kbd_activity_in_last_5_secs = False - self.mouse_over_in_last_5_secs = False - self.mouse_over_in_last_30_secs = False - self.kbd_activity_in_last_30_secs = False - - def on_message_textview_mykeypress_event(self, widget, event_keyval, - event_keymod): - '''When a key is pressed: - if enter is pressed without the shift key, message (if not empty) is sent - and printed in the conversation''' - # NOTE: handles mykeypress which is custom signal connected to this - # CB in new_tab(). for this singal see message_textview.py - jid = self.get_active_jid() - conv_textview = self.conversation_textviews[jid] - message_textview = widget - message_buffer = message_textview.get_buffer() - start_iter, end_iter = message_buffer.get_bounds() - message = message_buffer.get_text(start_iter, end_iter, False).decode( - 'utf-8') - - # construct event instance from binding - event = gtk.gdk.Event(gtk.gdk.KEY_PRESS) # it's always a key-press here - event.keyval = event_keyval - event.state = event_keymod - event.time = 0 # assign current time - - if event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB - if event.state & gtk.gdk.CONTROL_MASK: # CTRL + SHIFT + TAB - self.notebook.emit('key_press_event', event) - if event.keyval == gtk.keysyms.Tab: - if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB - self.notebook.emit('key_press_event', event) - elif event.keyval == gtk.keysyms.Up: - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+UP - self.sent_messages_scroll(jid, 'up', widget.get_buffer()) - return - elif event.keyval == gtk.keysyms.Down: - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl+Down - self.sent_messages_scroll(jid, 'down', widget.get_buffer()) - return - elif event.keyval == gtk.keysyms.Return or \ - event.keyval == gtk.keysyms.KP_Enter: # ENTER - # NOTE: SHIFT + ENTER is not needed to be emulated as it is not - # binding at all (textview's default action is newline) - - if gajim.config.get('send_on_ctrl_enter'): - # here, we emulate GTK default action on ENTER (add new line) - # normally I would add in keypress but it gets way to complex - # to get instant result on changing this advanced setting - if event.state == 0: # no ctrl, no shift just ENTER add newline - end_iter = message_buffer.get_end_iter() - message_buffer.insert_at_cursor('\n') - send_message = False - elif event.state & gtk.gdk.CONTROL_MASK: # CTRL + ENTER - send_message = True - else: # send on Enter, do newline on Ctrl Enter - if event.state & gtk.gdk.CONTROL_MASK: # Ctrl + ENTER - end_iter = message_buffer.get_end_iter() - message_buffer.insert_at_cursor('\n') - send_message = False - else: # ENTER - send_message = True - - if gajim.connections[self.account].connected < 2: # we are not connected - dialogs.ErrorDialog(_('A connection is not available'), - _('Your message can not be sent until you are connected.')).get_response() - send_message = False - - if send_message: - self.send_message(message) # send the message - - def send_chatstate(self, state, jid = None): - ''' sends OUR chatstate as STANDLONE chat state message (eg. no body) - to jid only if new chatstate is different - from the previous one - if jid is not specified, send to active tab''' - # JEP 85 does not allow resending the same chatstate - # this function checks for that and just returns so it's safe to call it - # with same state. - - # This functions also checks for violation in state transitions - # and raises RuntimeException with appropriate message - # more on that http://www.jabber.org/jeps/jep-0085.html#statechart - - # do not send nothing if we have chat state notifications disabled - # that means we won't reply to the from other peer - # so we do not broadcast jep85 capabalities - chatstate_setting = gajim.config.get('chat_state_notifications') - if chatstate_setting == 'disabled': - return - elif chatstate_setting == 'composing_only' and state != 'active' and state != 'composing': - return - - if jid is None: - jid = self.get_active_jid() - - contact = gajim.contacts.get_first_contact_from_jid(self.account, jid) - - if contact is None: - # contact was from pm in MUC, and left the room so contact is None - # so we cannot send chatstate anymore - return - - # Don't send chatstates to offline contacts - if contact.show == 'offline': - return - - if contact.our_chatstate is False: # jid cannot do jep85 - return - - # if the new state we wanna send (state) equals - # the current state (contact.our_chatstate) then return - if contact.our_chatstate == state: - return - - if contact.our_chatstate is None: - # we don't know anything about jid, so return - # NOTE: - # send 'active', set current state to 'ask' and return is done - # in self.send_message() because we need REAL message (with ) - # for that procedure so return to make sure we send only once - # 'active' until we know peer supports jep85 - return - - if contact.our_chatstate == 'ask': - return - - # prevent going paused if we we were not composing (JEP violation) - if state == 'paused' and not contact.our_chatstate == 'composing': - gajim.connections[self.account].send_message(jid, None, None, - chatstate = 'active') # go active before - contact.our_chatstate = 'active' - self.reset_kbd_mouse_timeout_vars() - - # if we're inactive prevent composing (JEP violation) - if contact.our_chatstate == 'inactive' and state == 'composing': - gajim.connections[self.account].send_message(jid, None, None, - chatstate = 'active') # go active before - contact.our_chatstate = 'active' - self.reset_kbd_mouse_timeout_vars() - - gajim.connections[self.account].send_message(jid, None, None, - chatstate = state) - contact.our_chatstate = state - if contact.our_chatstate == 'active': - self.reset_kbd_mouse_timeout_vars() - - def send_message(self, message): - '''Send the given message to the active tab''' - if not message: - return - - jid = self.get_active_jid() - contact = gajim.contacts.get_first_contact_from_jid(self.account, jid) - if contact is None: - # contact was from pm in MUC - room, nick = gajim.get_room_and_nick_from_fjid(jid) - gc_contact = gajim.contacts.get_gc_contact(self.account, room, nick) - if not gc_contact: - # contact left the room, or we left the room - dialogs.ErrorDialog(_('Sending private message failed'), - #in second %s code replaces with nickname - _('You are no longer in room "%s" or "%s" has left.') % \ - (room, nick)).get_response() - return - - conv_textview = self.conversation_textviews[jid] - message_textview = self.message_textviews[jid] - message_buffer = message_textview.get_buffer() - - if message != '' or message != '\n': - self.save_sent_message(jid, message) - if message == '/clear': - conv_textview.clear() # clear conversation - self.clear(message_textview) # clear message textview too - return True - elif message == '/compact': - self.set_compact_view(not self.compact_view_current_state) - self.clear(message_textview) - return True - keyID = '' - encrypted = False - if self.xmls[jid].get_widget('gpg_togglebutton').get_active(): - keyID = self.contacts[jid].keyID - encrypted = True - - chatstates_on = gajim.config.get( - 'chat_state_notifications') != 'disabled' - - chatstate_to_send = None - - if chatstates_on and contact is not None: - if contact.our_chatstate is None: - # no info about peer - # send active to discover chat state capabilities - # this is here (and not in send_chatstate) - # because we want it sent with REAL message - # (not standlone) eg. one that has body - chatstate_to_send = 'active' - contact.our_chatstate = 'ask' # pseudo state - - # if peer supports jep85 and we are not 'ask', send 'active' - # NOTE: first active and 'ask' is set in gajim.py - elif contact.our_chatstate not in (False, 'ask'): - #send active chatstate on every message (as JEP says) - chatstate_to_send = 'active' - contact.our_chatstate = 'active' - - # refresh timers - # avoid sending composing in less than 5 seconds - # if we just send a message - gobject.source_remove(self.possible_paused_timeout_id[jid]) - gobject.source_remove(self.possible_inactive_timeout_id[jid]) - self.possible_paused_timeout_id[jid] =\ - gobject.timeout_add(5000, self.check_for_possible_paused_chatstate, - jid) - self.possible_inactive_timeout_id[jid] =\ - gobject.timeout_add(30000, self.check_for_possible_inactive_chatstate, - jid) - self.reset_kbd_mouse_timeout_vars() - - gajim.connections[self.account].send_message(jid, message, keyID, - chatstate = chatstate_to_send) - - message_buffer.set_text('') # clear message buffer (and tv of course) - self.print_conversation(message, jid, jid, encrypted = encrypted) - - def on_contact_information_menuitem_clicked(self, widget): - jid = self.get_active_jid() - contact = self.contacts[jid] - gajim.interface.roster.on_info(widget, contact, self.account) - - def read_queue(self, jid): - '''read queue and print messages containted in it''' - l = gajim.awaiting_events[self.account][jid] - contact = self.contacts[jid] - # Is it a pm ? - is_pm = False - room_jid, nick = gajim.get_room_and_nick_from_fjid(jid) - gcs = gajim.interface.instances[self.account]['gc'] - if gcs.has_key(room_jid): - is_pm = True - events_to_keep = [] - for event in l: - typ = event[0] - if typ != 'chat': - events_to_keep.append(event) - continue - data = event[1] - kind = data[2] - if kind == 'error': - kind = 'status' - else: - kind = 'print_queue' - self.print_conversation(data[0], jid, kind, tim = data[3], - encrypted = data[4], subject = data[1]) - - # remove from gc nb_unread if it's pm or from roster - if is_pm: - gcs[room_jid].nb_unread[room_jid] -= 1 - else: - gajim.interface.roster.nb_unread -= 1 - - if is_pm: - gcs[room_jid].show_title() - else: - gajim.interface.roster.show_title() - # Keep only non-messages events - if len(events_to_keep): - gajim.awaiting_events[self.account][jid] = events_to_keep - else: - del gajim.awaiting_events[self.account][jid] - typ = 'chat' # Is it a normal chat or a pm ? - # reset to status image in gc if it is a pm - gcs = gajim.interface.instances[self.account]['gc'] - if gcs.has_key(room_jid): - gcs[room_jid].draw_all_roster() - typ = 'pm' - - gajim.interface.roster.draw_contact(jid, self.account) - if gajim.interface.systray_enabled: - gajim.interface.systray.remove_jid(jid, self.account, typ) - if (contact.show == 'offline' or contact.show == 'error'): - showOffline = gajim.config.get('showoffline') - if not showOffline and typ == 'chat' and \ - len(gajim.contacts.get_contact(self.account, jid)) == 1: - gajim.interface.roster.really_remove_contact(contact, self.account) - elif typ == 'pm': - gcs[room_jid].remove_contact(room_jid, nick) - - def print_conversation(self, text, jid, frm = '', tim = None, - encrypted = False, subject = 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 set to print_queue: it is incomming from queue - if contact is not set: it's an incomming message''' - contact = self.contacts[jid] - if frm == 'status': - kind = 'status' - name = '' - else: - ec = gajim.encrypted_chats[self.account] - if encrypted and jid not in ec: - msg = _('Encryption enabled') - chat.Chat.print_conversation_line(self, msg, jid, - 'status', '', tim) - ec.append(jid) - if not encrypted and jid in ec: - msg = _('Encryption disabled') - chat.Chat.print_conversation_line(self, msg, jid, - 'status', '', tim) - ec.remove(jid) - self.xmls[jid].get_widget('gpg_togglebutton').set_active(encrypted) - if not frm: - kind = 'incoming' - name = contact.name - elif frm == 'print_queue': # incoming message, but do not update time - kind = 'incoming_queue' - name = contact.name - else: - kind = 'outgoing' - name = gajim.nicks[self.account] - chat.Chat.print_conversation_line(self, text, jid, kind, name, tim, - subject = subject) - - def restore_conversation(self, jid): - # don't restore lines if it's a transport - if gajim.jid_is_transport(jid): - return - - # How many lines to restore and when to time them out - restore_how_many = gajim.config.get('restore_lines') - timeout = gajim.config.get('restore_timeout') # in minutes - # number of messages that are in queue and are already logged - pending_how_many = 0 # we want to avoid duplication - - if gajim.awaiting_events[self.account].has_key(jid): - events = gajim.awaiting_events[self.account][jid] - for event in events: - if event[0] == 'chat': - pending_how_many += 1 - - rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many, - pending_how_many, timeout) - - for row in rows: # row[0] time, row[1] has kind, row[2] the message - if not row[2]: # message is empty, we don't print it - continue - if row[1] in (constants.KIND_CHAT_MSG_SENT, constants.KIND_SINGLE_MSG_SENT): - kind = 'outgoing' - name = gajim.nicks[self.account] - elif row[1] in (constants.KIND_SINGLE_MSG_RECV, constants.KIND_CHAT_MSG_RECV): - kind = 'incoming' - name = self.contacts[jid].name - - tim = time.localtime(float(row[0])) - - chat.Chat.print_conversation_line(self, row[2], jid, kind, name, tim, - ['small'], ['small', 'restored_message'], ['small', 'restored_message'], False) - - if len(rows): - conv_textview = self.conversation_textviews[jid] - conv_textview.print_empty_line()