| 1 | ## plugins/tabbed_chat_window.py |
|---|
| 2 | ## |
|---|
| 3 | ## Gajim Team: |
|---|
| 4 | ## - Yann Le Boulanger <asterix@lagaule.org> |
|---|
| 5 | ## - Vincent Hanquez <tab@snarc.org> |
|---|
| 6 | ## - Nikos Kouremenos <kourem@gmail.com> |
|---|
| 7 | ## |
|---|
| 8 | ## Copyright (C) 2003-2005 Gajim Team |
|---|
| 9 | ## |
|---|
| 10 | ## This program is free software; you can redistribute it and/or modify |
|---|
| 11 | ## it under the terms of the GNU General Public License as published |
|---|
| 12 | ## by the Free Software Foundation; version 2 only. |
|---|
| 13 | ## |
|---|
| 14 | ## This program is distributed in the hope that it will be useful, |
|---|
| 15 | ## but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 16 | ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 17 | ## GNU General Public License for more details. |
|---|
| 18 | ## |
|---|
| 19 | |
|---|
| 20 | import gtk |
|---|
| 21 | import gtk.glade |
|---|
| 22 | import pango |
|---|
| 23 | import gobject |
|---|
| 24 | import time |
|---|
| 25 | |
|---|
| 26 | import dialogs |
|---|
| 27 | import history_window |
|---|
| 28 | import chat |
|---|
| 29 | |
|---|
| 30 | from common import gajim |
|---|
| 31 | from common import helpers |
|---|
| 32 | from common import i18n |
|---|
| 33 | |
|---|
| 34 | _ = i18n._ |
|---|
| 35 | APP = i18n.APP |
|---|
| 36 | gtk.glade.bindtextdomain(APP, i18n.DIR) |
|---|
| 37 | gtk.glade.textdomain(APP) |
|---|
| 38 | |
|---|
| 39 | GTKGUI_GLADE = 'gtkgui.glade' |
|---|
| 40 | |
|---|
| 41 | class Tabbed_chat_window(chat.Chat): |
|---|
| 42 | """Class for tabbed chat window""" |
|---|
| 43 | def __init__(self, user, plugin, account): |
|---|
| 44 | chat.Chat.__init__(self, plugin, account, 'tabbed_chat_window') |
|---|
| 45 | self.users = {} |
|---|
| 46 | self.new_user(user) |
|---|
| 47 | self.show_title() |
|---|
| 48 | self.xml.signal_connect('on_tabbed_chat_window_destroy', |
|---|
| 49 | self.on_tabbed_chat_window_destroy) |
|---|
| 50 | self.xml.signal_connect('on_tabbed_chat_window_delete_event', |
|---|
| 51 | self.on_tabbed_chat_window_delete_event) |
|---|
| 52 | self.xml.signal_connect('on_tabbed_chat_window_focus_in_event', |
|---|
| 53 | self.on_tabbed_chat_window_focus_in_event) |
|---|
| 54 | self.xml.signal_connect('on_chat_notebook_key_press_event', |
|---|
| 55 | self.on_chat_notebook_key_press_event) |
|---|
| 56 | self.xml.signal_connect('on_chat_notebook_switch_page', |
|---|
| 57 | self.on_chat_notebook_switch_page) |
|---|
| 58 | self.window.show_all() |
|---|
| 59 | |
|---|
| 60 | def save_var(self, jid): |
|---|
| 61 | '''return the specific variable of a jid, like gpg_enabled |
|---|
| 62 | the return value have to be compatible with wthe one given to load_var''' |
|---|
| 63 | gpg_enabled = self.xmls[jid].get_widget('gpg_togglebutton').get_active() |
|---|
| 64 | return {'gpg_enabled': gpg_enabled} |
|---|
| 65 | |
|---|
| 66 | def load_var(self, jid, var): |
|---|
| 67 | if not self.xmls.has_key(jid): |
|---|
| 68 | return |
|---|
| 69 | self.xmls[jid].get_widget('gpg_togglebutton').set_active( |
|---|
| 70 | var['gpg_enabled']) |
|---|
| 71 | |
|---|
| 72 | def draw_widgets(self, user): |
|---|
| 73 | """draw the widgets in a tab (status_image, contact_button ...) |
|---|
| 74 | according to the the information in the user variable""" |
|---|
| 75 | jid = user.jid |
|---|
| 76 | self.set_state_image(jid) |
|---|
| 77 | contact_button = self.xmls[jid].get_widget('contact_button') |
|---|
| 78 | contact_button.set_use_underline(False) |
|---|
| 79 | contact_button.set_label(user.name + ' <' + jid + '>') |
|---|
| 80 | if not user.keyID: |
|---|
| 81 | self.xmls[jid].get_widget('gpg_togglebutton').set_sensitive(False) |
|---|
| 82 | |
|---|
| 83 | nontabbed_status_image = self.xmls[jid].get_widget( |
|---|
| 84 | 'nontabbed_status_image') |
|---|
| 85 | if len(self.xmls) > 1: |
|---|
| 86 | nontabbed_status_image.hide() |
|---|
| 87 | else: |
|---|
| 88 | nontabbed_status_image.show() |
|---|
| 89 | |
|---|
| 90 | def set_state_image(self, jid): |
|---|
| 91 | prio = 0 |
|---|
| 92 | list_users = self.plugin.roster.contacts[self.account][jid] |
|---|
| 93 | user = list_users[0] |
|---|
| 94 | show = user.show |
|---|
| 95 | jid = user.jid |
|---|
| 96 | for u in list_users: |
|---|
| 97 | if u.priority > prio: |
|---|
| 98 | prio = u.priority |
|---|
| 99 | show = u.show |
|---|
| 100 | child = self.childs[jid] |
|---|
| 101 | status_image = self.notebook.get_tab_label(child).get_children()[0] |
|---|
| 102 | state_images = self.plugin.roster.get_appropriate_state_images(jid) |
|---|
| 103 | image = state_images[show] |
|---|
| 104 | non_tabbed_status_image = self.xmls[jid].get_widget( |
|---|
| 105 | 'nontabbed_status_image') |
|---|
| 106 | if image.get_storage_type() == gtk.IMAGE_ANIMATION: |
|---|
| 107 | non_tabbed_status_image.set_from_animation(image.get_animation()) |
|---|
| 108 | status_image.set_from_animation(image.get_animation()) |
|---|
| 109 | elif image.get_storage_type() == gtk.IMAGE_PIXBUF: |
|---|
| 110 | non_tabbed_status_image.set_from_pixbuf(image.get_pixbuf()) |
|---|
| 111 | status_image.set_from_pixbuf(image.get_pixbuf()) |
|---|
| 112 | |
|---|
| 113 | def on_tabbed_chat_window_delete_event(self, widget, event): |
|---|
| 114 | """close window""" |
|---|
| 115 | for jid in self.users: |
|---|
| 116 | if time.time() - self.last_message_time[jid] < 2: # 2 seconds |
|---|
| 117 | dialog = dialogs.Confirmation_dialog(_('You received a message from %s in the last two seconds.\nDo you still want to close this window?') % jid) |
|---|
| 118 | if dialog.get_response() != gtk.RESPONSE_YES: |
|---|
| 119 | return True #stop the propagation of the event |
|---|
| 120 | |
|---|
| 121 | def on_tabbed_chat_window_destroy(self, widget): |
|---|
| 122 | #clean self.plugin.windows[self.account]['chats'] |
|---|
| 123 | chat.Chat.on_window_destroy(self, widget, 'chats') |
|---|
| 124 | |
|---|
| 125 | def on_tabbed_chat_window_focus_in_event(self, widget, event): |
|---|
| 126 | chat.Chat.on_chat_window_focus_in_event(self, widget, event) |
|---|
| 127 | |
|---|
| 128 | def on_chat_notebook_key_press_event(self, widget, event): |
|---|
| 129 | chat.Chat.on_chat_notebook_key_press_event(self, widget, event) |
|---|
| 130 | |
|---|
| 131 | def on_clear_button_clicked(self, widget): |
|---|
| 132 | """When clear button is pressed: clear the conversation""" |
|---|
| 133 | jid = self.get_active_jid() |
|---|
| 134 | textview = self.xmls[jid].get_widget('conversation_textview') |
|---|
| 135 | self.on_clear(None, textview) |
|---|
| 136 | |
|---|
| 137 | def on_history_button_clicked(self, widget): |
|---|
| 138 | """When history button is pressed: call history window""" |
|---|
| 139 | jid = self.get_active_jid() |
|---|
| 140 | if self.plugin.windows['logs'].has_key(jid): |
|---|
| 141 | self.plugin.windows['logs'][jid].window.present() |
|---|
| 142 | else: |
|---|
| 143 | self.plugin.windows['logs'][jid] = history_window.\ |
|---|
| 144 | History_window(self.plugin, jid, self.account) |
|---|
| 145 | |
|---|
| 146 | def remove_tab(self, jid): |
|---|
| 147 | if time.time() - self.last_message_time[jid] < 2: |
|---|
| 148 | dialog = dialogs.Confirmation_dialog(_('You received a message from %s in the last two seconds.\nDo you still want to close this tab?') % jid) |
|---|
| 149 | if dialog.get_response() != gtk.RESPONSE_YES: |
|---|
| 150 | return |
|---|
| 151 | |
|---|
| 152 | chat.Chat.remove_tab(self, jid, 'chats') |
|---|
| 153 | if len(self.xmls) > 0: |
|---|
| 154 | del self.users[jid] |
|---|
| 155 | |
|---|
| 156 | jid = self.get_active_jid() # get the new active jid |
|---|
| 157 | if jid != '': |
|---|
| 158 | nontabbed_status_image = self.xmls[jid].get_widget( |
|---|
| 159 | 'nontabbed_status_image') |
|---|
| 160 | if len(self.xmls) > 1: |
|---|
| 161 | nontabbed_status_image.hide() |
|---|
| 162 | else: |
|---|
| 163 | nontabbed_status_image.show() |
|---|
| 164 | |
|---|
| 165 | def new_user(self, user): |
|---|
| 166 | self.names[user.jid] = user.name |
|---|
| 167 | self.xmls[user.jid] = gtk.glade.XML(GTKGUI_GLADE, 'chats_vbox', APP) |
|---|
| 168 | self.childs[user.jid] = self.xmls[user.jid].get_widget('chats_vbox') |
|---|
| 169 | chat.Chat.new_tab(self, user.jid) |
|---|
| 170 | self.users[user.jid] = user |
|---|
| 171 | |
|---|
| 172 | self.redraw_tab(user.jid) |
|---|
| 173 | self.draw_widgets(user) |
|---|
| 174 | uf_show = helpers.get_uf_show(user.show) |
|---|
| 175 | s = _('%s is %s') % (user.name, uf_show) |
|---|
| 176 | if user.status: |
|---|
| 177 | s += ' (' + user.status + ')' |
|---|
| 178 | self.print_conversation(s, user.jid, 'status') |
|---|
| 179 | |
|---|
| 180 | #print queued messages |
|---|
| 181 | if self.plugin.queues[self.account].has_key(user.jid): |
|---|
| 182 | self.read_queue(user.jid) |
|---|
| 183 | |
|---|
| 184 | if gajim.config.get('print_time') == 'sometimes': |
|---|
| 185 | self.print_time_timeout(user.jid) |
|---|
| 186 | self.print_time_timeout_id[user.jid] = gobject.timeout_add(300000, |
|---|
| 187 | self.print_time_timeout, user.jid) |
|---|
| 188 | |
|---|
| 189 | def on_message_textview_key_press_event(self, widget, event): |
|---|
| 190 | """When a key is pressed: |
|---|
| 191 | if enter is pressed without the shit key, message (if not empty) is sent |
|---|
| 192 | and printed in the conversation""" |
|---|
| 193 | jid = self.get_active_jid() |
|---|
| 194 | conversation_textview = self.xmls[jid].get_widget('conversation_textview') |
|---|
| 195 | if event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB |
|---|
| 196 | if event.state & gtk.gdk.CONTROL_MASK: # CTRL + SHIFT + TAB |
|---|
| 197 | self.notebook.emit('key_press_event', event) |
|---|
| 198 | if event.keyval == gtk.keysyms.Tab: |
|---|
| 199 | if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB |
|---|
| 200 | self.notebook.emit('key_press_event', event) |
|---|
| 201 | elif event.keyval == gtk.keysyms.Page_Down: # PAGE DOWN |
|---|
| 202 | if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN |
|---|
| 203 | self.notebook.emit('key_press_event', event) |
|---|
| 204 | elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN |
|---|
| 205 | conversation_textview.emit('key_press_event', event) |
|---|
| 206 | elif event.keyval == gtk.keysyms.Page_Up: # PAGE UP |
|---|
| 207 | if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP |
|---|
| 208 | self.notebook.emit('key_press_event', event) |
|---|
| 209 | elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP |
|---|
| 210 | conversation_textview.emit('key_press_event', event) |
|---|
| 211 | elif event.keyval == gtk.keysyms.Return or \ |
|---|
| 212 | event.keyval == gtk.keysyms.KP_Enter: # ENTER |
|---|
| 213 | if (event.state & gtk.gdk.SHIFT_MASK): |
|---|
| 214 | return False |
|---|
| 215 | if gajim.connections[self.account].connected < 2: #we are not connected |
|---|
| 216 | dialogs.Error_dialog(_('You are not connected, so you cannot send a message')) |
|---|
| 217 | return True |
|---|
| 218 | message_buffer = widget.get_buffer() |
|---|
| 219 | start_iter = message_buffer.get_start_iter() |
|---|
| 220 | end_iter = message_buffer.get_end_iter() |
|---|
| 221 | message = message_buffer.get_text(start_iter, end_iter, 0) |
|---|
| 222 | if message != '': |
|---|
| 223 | if message == '/clear': |
|---|
| 224 | self.on_clear(None, conversation_textview) # clear conversation |
|---|
| 225 | self.on_clear(None, widget) # clear message textview too |
|---|
| 226 | return True |
|---|
| 227 | keyID = '' |
|---|
| 228 | if self.xmls[jid].get_widget('gpg_togglebutton').get_active(): |
|---|
| 229 | keyID = self.users[jid].keyID |
|---|
| 230 | gajim.connections[self.account].send_message(jid, message, keyID) |
|---|
| 231 | message_buffer.set_text('', -1) |
|---|
| 232 | self.print_conversation(message, jid, jid) |
|---|
| 233 | return True |
|---|
| 234 | |
|---|
| 235 | def on_contact_button_clicked(self, widget): |
|---|
| 236 | jid = self.get_active_jid() |
|---|
| 237 | user = self.users[jid] |
|---|
| 238 | self.plugin.roster.on_info(widget, user, self.account) |
|---|
| 239 | |
|---|
| 240 | def read_queue(self, jid): |
|---|
| 241 | """read queue and print messages containted in it""" |
|---|
| 242 | q = self.plugin.queues[self.account][jid] |
|---|
| 243 | user = self.users[jid] |
|---|
| 244 | while not q.empty(): |
|---|
| 245 | event = q.get() |
|---|
| 246 | self.print_conversation(event[0], jid, tim = event[1]) |
|---|
| 247 | self.plugin.roster.nb_unread -= 1 |
|---|
| 248 | self.plugin.roster.show_title() |
|---|
| 249 | del self.plugin.queues[self.account][jid] |
|---|
| 250 | self.plugin.roster.draw_contact(jid, self.account) |
|---|
| 251 | if self.plugin.systray_enabled: |
|---|
| 252 | self.plugin.systray.remove_jid(jid, self.account) |
|---|
| 253 | showOffline = gajim.config.get('showoffline') |
|---|
| 254 | if (user.show == 'offline' or user.show == 'error') and \ |
|---|
| 255 | not showOffline: |
|---|
| 256 | if len(self.plugin.roster.contacts[self.account][jid]) == 1: |
|---|
| 257 | self.plugin.roster.really_remove_user(user, self.account) |
|---|
| 258 | |
|---|
| 259 | def print_conversation(self, text, jid, contact = '', tim = None): |
|---|
| 260 | """Print a line in the conversation: |
|---|
| 261 | if contact is set to status: it's a status message |
|---|
| 262 | if contact is set to another value: it's an outgoing message |
|---|
| 263 | if contact is not set: it's an incomming message""" |
|---|
| 264 | user = self.users[jid] |
|---|
| 265 | if contact == 'status': |
|---|
| 266 | kind = 'status' |
|---|
| 267 | name = '' |
|---|
| 268 | else: |
|---|
| 269 | if contact: |
|---|
| 270 | kind = 'outgoing' |
|---|
| 271 | name = self.plugin.nicks[self.account] |
|---|
| 272 | else: |
|---|
| 273 | kind = 'incoming' |
|---|
| 274 | name = user.name |
|---|
| 275 | |
|---|
| 276 | chat.Chat.print_conversation_line(self, text, jid, kind, name, tim) |
|---|