| 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 | from common import gajim |
|---|
| 27 | from common import i18n |
|---|
| 28 | |
|---|
| 29 | _ = i18n._ |
|---|
| 30 | APP = i18n.APP |
|---|
| 31 | gtk.glade.bindtextdomain(APP, i18n.DIR) |
|---|
| 32 | gtk.glade.textdomain(APP) |
|---|
| 33 | |
|---|
| 34 | GTKGUI_GLADE = 'gtkgui.glade' |
|---|
| 35 | |
|---|
| 36 | class Chat: |
|---|
| 37 | """Class for chat/groupchat windows""" |
|---|
| 38 | def __init__(self, plugin, account, widget_name): |
|---|
| 39 | self.xml = gtk.glade.XML(GTKGUI_GLADE, widget_name, APP) |
|---|
| 40 | self.window = self.xml.get_widget(widget_name) |
|---|
| 41 | self.widget_name = widget_name |
|---|
| 42 | self.notebook = self.xml.get_widget('chat_notebook') |
|---|
| 43 | self.notebook.remove_page(0) |
|---|
| 44 | self.plugin = plugin |
|---|
| 45 | self.account = account |
|---|
| 46 | self.change_cursor = None |
|---|
| 47 | self.xmls = {} |
|---|
| 48 | self.tagIn = {} |
|---|
| 49 | self.tagOut = {} |
|---|
| 50 | self.tagStatus = {} |
|---|
| 51 | self.nb_unread = {} |
|---|
| 52 | self.last_message_time = {} |
|---|
| 53 | self.print_time_timeout_id = {} |
|---|
| 54 | self.names = {} # what is printed in the tab (eg. user.name) |
|---|
| 55 | self.childs = {} |
|---|
| 56 | |
|---|
| 57 | def update_tags(self): |
|---|
| 58 | for jid in self.tagIn: |
|---|
| 59 | self.tagIn[jid].set_property('foreground', |
|---|
| 60 | gajim.config.get('inmsgcolor')) |
|---|
| 61 | self.tagOut[jid].set_property('foreground', |
|---|
| 62 | gajim.config.get('outmsgcolor')) |
|---|
| 63 | self.tagStatus[jid].set_property('foreground', |
|---|
| 64 | gajim.config.get('statusmsgcolor')) |
|---|
| 65 | |
|---|
| 66 | def update_print_time(self): |
|---|
| 67 | if gajim.config.get('print_time') != 'sometimes': |
|---|
| 68 | list_jid = self.print_time_timeout_id.keys() |
|---|
| 69 | for jid in list_jid: |
|---|
| 70 | gobject.source_remove(self.print_time_timeout_id[jid]) |
|---|
| 71 | del self.print_time_timeout_id[jid] |
|---|
| 72 | else: |
|---|
| 73 | for jid in self.xmls: |
|---|
| 74 | if self.print_time_timeout_id.has_key(jid): |
|---|
| 75 | continue |
|---|
| 76 | self.print_time_timeout(jid) |
|---|
| 77 | self.print_time_timeout_id[jid] = \ |
|---|
| 78 | gobject.timeout_add(300000, |
|---|
| 79 | self.print_time_timeout, |
|---|
| 80 | jid) |
|---|
| 81 | |
|---|
| 82 | def show_title(self): |
|---|
| 83 | """redraw the window's title""" |
|---|
| 84 | unread = 0 |
|---|
| 85 | for jid in self.nb_unread: |
|---|
| 86 | unread += self.nb_unread[jid] |
|---|
| 87 | start = "" |
|---|
| 88 | if unread > 1: |
|---|
| 89 | start = '[' + str(unread) + '] ' |
|---|
| 90 | elif unread == 1: |
|---|
| 91 | start = '* ' |
|---|
| 92 | chat = self.names[jid] |
|---|
| 93 | if len(self.xmls) > 1: # if more than one tab in the same window |
|---|
| 94 | if self.widget_name == 'tabbed_chat_window': |
|---|
| 95 | chat = 'Chat' |
|---|
| 96 | elif self.widget_name == 'groupchat_window': |
|---|
| 97 | chat = 'Groupchat' |
|---|
| 98 | title = start + chat |
|---|
| 99 | if len(gajim.connections) >= 2: # if we have 2 or more accounts |
|---|
| 100 | title = title + _(' (account: ') + self.account + ')' |
|---|
| 101 | |
|---|
| 102 | self.window.set_title(title) |
|---|
| 103 | |
|---|
| 104 | def redraw_tab(self, jid): |
|---|
| 105 | """redraw the label of the tab""" |
|---|
| 106 | start = '' |
|---|
| 107 | if self.nb_unread[jid] > 1: |
|---|
| 108 | start = '[' + str(self.nb_unread[jid]) + '] ' |
|---|
| 109 | elif self.nb_unread[jid] == 1: |
|---|
| 110 | start = '* ' |
|---|
| 111 | |
|---|
| 112 | child = self.childs[jid] |
|---|
| 113 | if self.widget_name == 'tabbed_chat_window': |
|---|
| 114 | nickname = self.notebook.get_tab_label(child).get_children()[1] |
|---|
| 115 | elif self.widget_name == 'groupchat_window': |
|---|
| 116 | nickname = self.notebook.get_tab_label(child).get_children()[0] |
|---|
| 117 | |
|---|
| 118 | nickname.set_text(start + self.names[jid]) |
|---|
| 119 | |
|---|
| 120 | def on_window_destroy(self, widget, kind): #kind is 'chats' or 'gc' |
|---|
| 121 | #clean self.plugin.windows[self.account][kind] |
|---|
| 122 | for jid in self.xmls: |
|---|
| 123 | if self.plugin.systray_enabled and self.nb_unread[jid] > 0: |
|---|
| 124 | self.plugin.systray.remove_jid(jid, self.account) |
|---|
| 125 | del self.plugin.windows[self.account][kind][jid] |
|---|
| 126 | if self.print_time_timeout_id.has_key(jid): |
|---|
| 127 | gobject.source_remove(self.print_time_timeout_id[jid]) |
|---|
| 128 | if self.plugin.windows[self.account][kind].has_key('tabbed'): |
|---|
| 129 | del self.plugin.windows[self.account][kind]['tabbed'] |
|---|
| 130 | |
|---|
| 131 | def get_active_jid(self): |
|---|
| 132 | notebook = self.notebook |
|---|
| 133 | active_child = notebook.get_nth_page(notebook.get_current_page()) |
|---|
| 134 | active_jid = '' |
|---|
| 135 | for jid in self.xmls: |
|---|
| 136 | if self.childs[jid] == active_child: |
|---|
| 137 | active_jid = jid |
|---|
| 138 | break |
|---|
| 139 | return active_jid |
|---|
| 140 | |
|---|
| 141 | def on_close_button_clicked(self, button, jid): |
|---|
| 142 | """When close button is pressed: close a tab""" |
|---|
| 143 | self.remove_tab(jid) |
|---|
| 144 | |
|---|
| 145 | def on_chat_window_focus_in_event(self, widget, event): |
|---|
| 146 | """When window get focus""" |
|---|
| 147 | jid = self.get_active_jid() |
|---|
| 148 | textview = self.xmls[jid].get_widget('conversation_textview') |
|---|
| 149 | buffer = textview.get_buffer() |
|---|
| 150 | end_iter = buffer.get_end_iter() |
|---|
| 151 | end_rect = textview.get_iter_location(end_iter) |
|---|
| 152 | visible_rect = textview.get_visible_rect() |
|---|
| 153 | if end_rect.y <= (visible_rect.y + visible_rect.height): |
|---|
| 154 | #we are at the end |
|---|
| 155 | if self.nb_unread[jid] > 0: |
|---|
| 156 | self.nb_unread[jid] = 0 |
|---|
| 157 | self.redraw_tab(jid) |
|---|
| 158 | self.show_title() |
|---|
| 159 | if self.plugin.systray_enabled: |
|---|
| 160 | self.plugin.systray.remove_jid(jid, self.account) |
|---|
| 161 | |
|---|
| 162 | def on_chat_notebook_switch_page(self, notebook, page, page_num): |
|---|
| 163 | new_child = notebook.get_nth_page(page_num) |
|---|
| 164 | new_jid = '' |
|---|
| 165 | for jid in self.xmls: |
|---|
| 166 | if self.childs[jid] == new_child: |
|---|
| 167 | new_jid = jid |
|---|
| 168 | break |
|---|
| 169 | |
|---|
| 170 | conversation_textview = self.xmls[new_jid].get_widget( |
|---|
| 171 | 'conversation_textview') |
|---|
| 172 | conversation_buffer = conversation_textview.get_buffer() |
|---|
| 173 | end_iter = conversation_buffer.get_end_iter() |
|---|
| 174 | end_rect = conversation_textview.get_iter_location(end_iter) |
|---|
| 175 | visible_rect = conversation_textview.get_visible_rect() |
|---|
| 176 | if end_rect.y <= (visible_rect.y + visible_rect.height): |
|---|
| 177 | #we are at the end |
|---|
| 178 | if self.nb_unread[new_jid] > 0: |
|---|
| 179 | self.nb_unread[new_jid] = 0 |
|---|
| 180 | self.redraw_tab(new_jid) |
|---|
| 181 | self.show_title() |
|---|
| 182 | if self.plugin.systray_enabled: |
|---|
| 183 | self.plugin.systray.remove_jid(new_jid, self.account) |
|---|
| 184 | |
|---|
| 185 | if self.widget_name == 'tabbed_chat_window': |
|---|
| 186 | nontabbed_status_image = self.xmls[jid].get_widget( |
|---|
| 187 | 'nontabbed_status_image') |
|---|
| 188 | if len(self.xmls) > 1: |
|---|
| 189 | nontabbed_status_image.hide() |
|---|
| 190 | else: |
|---|
| 191 | nontabbed_status_image.show() |
|---|
| 192 | |
|---|
| 193 | conversation_textview.grab_focus() |
|---|
| 194 | |
|---|
| 195 | def set_active_tab(self, jid): |
|---|
| 196 | self.notebook.set_current_page(self.notebook.page_num(self.childs[jid])) |
|---|
| 197 | |
|---|
| 198 | def remove_tab(self, jid, kind): #kind is 'chats' or 'gc' |
|---|
| 199 | if len(self.xmls) == 1: |
|---|
| 200 | self.window.destroy() |
|---|
| 201 | return |
|---|
| 202 | if self.nb_unread[jid] > 0: |
|---|
| 203 | self.nb_unread[jid] = 0 |
|---|
| 204 | self.show_title() |
|---|
| 205 | if self.plugin.systray_enabled: |
|---|
| 206 | self.plugin.systray.remove_jid(jid, self.account) |
|---|
| 207 | if self.print_time_timeout_id.has_key(jid): |
|---|
| 208 | gobject.source_remove(self.print_time_timeout_id[jid]) |
|---|
| 209 | del self.print_time_timeout_id[jid] |
|---|
| 210 | self.notebook.remove_page(self.notebook.page_num(self.childs[jid])) |
|---|
| 211 | if len(self.xmls) == 2: |
|---|
| 212 | # one that remains and one that we'll remove, 1 tab remains |
|---|
| 213 | self.notebook.set_show_tabs(False) |
|---|
| 214 | |
|---|
| 215 | del self.plugin.windows[self.account][kind][jid] |
|---|
| 216 | del self.nb_unread[jid] |
|---|
| 217 | del self.last_message_time[jid] |
|---|
| 218 | del self.xmls[jid] |
|---|
| 219 | del self.tagIn[jid] |
|---|
| 220 | del self.tagOut[jid] |
|---|
| 221 | del self.tagStatus[jid] |
|---|
| 222 | self.show_title() |
|---|
| 223 | |
|---|
| 224 | def new_tab(self, jid): |
|---|
| 225 | self.nb_unread[jid] = 0 |
|---|
| 226 | self.last_message_time[jid] = 0 |
|---|
| 227 | |
|---|
| 228 | conversation_textview = self.xmls[jid].get_widget( |
|---|
| 229 | 'conversation_textview') |
|---|
| 230 | conversation_buffer = conversation_textview.get_buffer() |
|---|
| 231 | end_iter = conversation_buffer.get_end_iter() |
|---|
| 232 | |
|---|
| 233 | conversation_buffer.create_mark('end', end_iter, False) |
|---|
| 234 | |
|---|
| 235 | self.tagIn[jid] = conversation_buffer.create_tag('incoming') |
|---|
| 236 | color = gajim.config.get('inmsgcolor') |
|---|
| 237 | self.tagIn[jid].set_property('foreground', color) |
|---|
| 238 | self.tagOut[jid] = conversation_buffer.create_tag('outgoing') |
|---|
| 239 | color = gajim.config.get('outmsgcolor') |
|---|
| 240 | self.tagOut[jid].set_property('foreground', color) |
|---|
| 241 | self.tagStatus[jid] = conversation_buffer.create_tag('status') |
|---|
| 242 | color = gajim.config.get('statusmsgcolor') |
|---|
| 243 | self.tagStatus[jid].set_property('foreground', color) |
|---|
| 244 | |
|---|
| 245 | tag = conversation_buffer.create_tag('time_sometimes') |
|---|
| 246 | tag.set_property('foreground', '#9e9e9e') |
|---|
| 247 | tag.set_property('scale', pango.SCALE_SMALL) |
|---|
| 248 | tag.set_property('justification', gtk.JUSTIFY_CENTER) |
|---|
| 249 | |
|---|
| 250 | tag = conversation_buffer.create_tag('url') |
|---|
| 251 | tag.set_property('foreground', '#0000ff') |
|---|
| 252 | tag.set_property('underline', pango.UNDERLINE_SINGLE) |
|---|
| 253 | tag.connect('event', self.hyperlink_handler, 'url') |
|---|
| 254 | |
|---|
| 255 | tag = conversation_buffer.create_tag('mail') |
|---|
| 256 | tag.set_property('foreground', '#0000ff') |
|---|
| 257 | tag.set_property('underline', pango.UNDERLINE_SINGLE) |
|---|
| 258 | tag.connect('event', self.hyperlink_handler, 'mail') |
|---|
| 259 | |
|---|
| 260 | tag = conversation_buffer.create_tag('bold') |
|---|
| 261 | tag.set_property('weight', pango.WEIGHT_BOLD) |
|---|
| 262 | |
|---|
| 263 | tag = conversation_buffer.create_tag('italic') |
|---|
| 264 | tag.set_property('style', pango.STYLE_ITALIC) |
|---|
| 265 | |
|---|
| 266 | tag = conversation_buffer.create_tag('underline') |
|---|
| 267 | tag.set_property('underline', pango.UNDERLINE_SINGLE) |
|---|
| 268 | |
|---|
| 269 | self.xmls[jid].signal_autoconnect(self) |
|---|
| 270 | conversation_scrolledwindow = self.xmls[jid].get_widget( |
|---|
| 271 | 'conversation_scrolledwindow') |
|---|
| 272 | conversation_scrolledwindow.get_vadjustment().connect('value-changed', |
|---|
| 273 | self.on_conversation_vadjustment_value_changed) |
|---|
| 274 | |
|---|
| 275 | child = self.childs[jid] |
|---|
| 276 | self.notebook.append_page(child) |
|---|
| 277 | |
|---|
| 278 | |
|---|
| 279 | if len(self.xmls) > 1: |
|---|
| 280 | self.notebook.set_show_tabs(True) |
|---|
| 281 | |
|---|
| 282 | if self.widget_name == 'tabbed_chat_window': |
|---|
| 283 | xm = gtk.glade.XML(GTKGUI_GLADE, 'chat_tab_hbox', APP) |
|---|
| 284 | tab_hbox = xm.get_widget('chat_tab_hbox') |
|---|
| 285 | elif self.widget_name == 'groupchat_window': |
|---|
| 286 | xm = gtk.glade.XML(GTKGUI_GLADE, 'groupchat_tab_hbox', APP) |
|---|
| 287 | tab_hbox = xm.get_widget('groupchat_tab_hbox') |
|---|
| 288 | |
|---|
| 289 | xm.signal_connect('on_close_button_clicked', |
|---|
| 290 | self.on_close_button_clicked, jid) |
|---|
| 291 | self.notebook.set_tab_label(child, tab_hbox) |
|---|
| 292 | |
|---|
| 293 | self.show_title() |
|---|
| 294 | |
|---|
| 295 | def on_conversation_textview_key_press_event(self, widget, event): |
|---|
| 296 | """Do not block these events and send them to the notebook""" |
|---|
| 297 | if event.state & gtk.gdk.CONTROL_MASK: |
|---|
| 298 | if event.keyval == gtk.keysyms.Tab: # CTRL + TAB |
|---|
| 299 | self.notebook.emit('key_press_event', event) |
|---|
| 300 | elif event.keyval == gtk.keysyms.ISO_Left_Tab: # CTRL + SHIFT + TAB |
|---|
| 301 | self.notebook.emit('key_press_event', event) |
|---|
| 302 | elif event.keyval == gtk.keysyms.Page_Down: # CTRL + PAGE DOWN |
|---|
| 303 | self.notebook.emit('key_press_event', event) |
|---|
| 304 | elif event.keyval == gtk.keysyms.Page_Up: # CTRL + PAGE UP |
|---|
| 305 | self.notebook.emit('key_press_event', event) |
|---|
| 306 | elif event.keyval == gtk.keysyms.v: # CTRL + V |
|---|
| 307 | jid = self.get_active_jid() |
|---|
| 308 | message_textview = self.xmls[jid].get_widget('message_textview') |
|---|
| 309 | if not message_textview.is_focus(): |
|---|
| 310 | message_textview.grab_focus() |
|---|
| 311 | message_textview.emit('key_press_event', event) |
|---|
| 312 | |
|---|
| 313 | def on_chat_notebook_key_press_event(self, widget, event): |
|---|
| 314 | st = '1234567890' # zero is here cause humans count from 1, pc from 0 :P |
|---|
| 315 | jid = self.get_active_jid() |
|---|
| 316 | if event.keyval == gtk.keysyms.Escape: # ESCAPE |
|---|
| 317 | if self.widget_name == 'tabbed_chat_window': |
|---|
| 318 | self.remove_tab(jid) |
|---|
| 319 | elif event.keyval == gtk.keysyms.F4 and \ |
|---|
| 320 | (event.state & gtk.gdk.CONTROL_MASK): # CTRL + F4 |
|---|
| 321 | self.remove_tab(jid) |
|---|
| 322 | elif event.string and event.string in st and \ |
|---|
| 323 | (event.state & gtk.gdk.MOD1_MASK): # alt + 1,2,3.. |
|---|
| 324 | self.notebook.set_current_page(st.index(event.string)) |
|---|
| 325 | elif event.keyval == gtk.keysyms.Page_Down: |
|---|
| 326 | if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE DOWN |
|---|
| 327 | current = self.notebook.get_current_page() |
|---|
| 328 | if current > 0: |
|---|
| 329 | self.notebook.set_current_page(current-1) |
|---|
| 330 | elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE DOWN |
|---|
| 331 | conversation_textview = self.xmls[jid].\ |
|---|
| 332 | get_widget('conversation_textview') |
|---|
| 333 | rect = conversation_textview.get_visible_rect() |
|---|
| 334 | iter = conversation_textview.get_iter_at_location(rect.x,\ |
|---|
| 335 | rect.y + rect.height) |
|---|
| 336 | conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 0) |
|---|
| 337 | elif event.keyval == gtk.keysyms.Page_Up: |
|---|
| 338 | if event.state & gtk.gdk.CONTROL_MASK: # CTRL + PAGE UP |
|---|
| 339 | current = self.notebook.get_current_page() |
|---|
| 340 | if current < (self.notebook.get_n_pages()-1): |
|---|
| 341 | self.notebook.set_current_page(current + 1) |
|---|
| 342 | elif event.state & gtk.gdk.SHIFT_MASK: # SHIFT + PAGE UP |
|---|
| 343 | conversation_textview = self.xmls[jid].\ |
|---|
| 344 | get_widget('conversation_textview') |
|---|
| 345 | rect = conversation_textview.get_visible_rect() |
|---|
| 346 | iter = conversation_textview.get_iter_at_location(rect.x, rect.y) |
|---|
| 347 | conversation_textview.scroll_to_iter(iter, 0.1, True, 0, 1) |
|---|
| 348 | # or event.keyval == gtk.keysyms.KP_Up |
|---|
| 349 | elif event.keyval == gtk.keysyms.Up: |
|---|
| 350 | if event.state & gtk.gdk.SHIFT_MASK: # SHIFT + UP |
|---|
| 351 | conversation_scrolledwindow = self.xml.get_widget('conversation_scrolledwindow') |
|---|
| 352 | conversation_scrolledwindow.emit('scroll-child', |
|---|
| 353 | gtk.SCROLL_PAGE_BACKWARD, False) |
|---|
| 354 | elif event.keyval == gtk.keysyms.ISO_Left_Tab: # SHIFT + TAB |
|---|
| 355 | if (event.state & gtk.gdk.CONTROL_MASK): # CTRL + SHIFT + TAB |
|---|
| 356 | current = self.notebook.get_current_page() |
|---|
| 357 | if current > 0: |
|---|
| 358 | self.notebook.set_current_page(current - 1) |
|---|
| 359 | else: |
|---|
| 360 | self.notebook.set_current_page(self.notebook.get_n_pages()-1) |
|---|
| 361 | elif event.keyval == gtk.keysyms.Tab: # TAB |
|---|
| 362 | if event.state & gtk.gdk.CONTROL_MASK: # CTRL + TAB |
|---|
| 363 | current = self.notebook.get_current_page() |
|---|
| 364 | if current < (self.notebook.get_n_pages()-1): |
|---|
| 365 | self.notebook.set_current_page(current + 1) |
|---|
| 366 | else: |
|---|
| 367 | self.notebook.set_current_page(0) |
|---|
| 368 | elif event.keyval == gtk.keysyms.v: |
|---|
| 369 | if event.state & gtk.gdk.CONTROL_MASK: # CTRL + V |
|---|
| 370 | jid = self.get_active_jid() |
|---|
| 371 | message_textview = self.xmls[jid].get_widget('message_textview') |
|---|
| 372 | if not message_textview.is_focus(): |
|---|
| 373 | message_textview.grab_focus() |
|---|
| 374 | message_textview.emit('key_press_event', event) |
|---|
| 375 | elif (event.state & gtk.gdk.CONTROL_MASK) or \ |
|---|
| 376 | (event.keyval == gtk.keysyms.Control_L) or \ |
|---|
| 377 | (event.keyval == gtk.keysyms.Control_R): |
|---|
| 378 | # we pressed a control key or ctrl+sth: we don't block |
|---|
| 379 | # the event in order to let ctrl+c (copy text) and |
|---|
| 380 | # others do their default work |
|---|
| 381 | pass |
|---|
| 382 | else: # it's a normal key press make sure message_textview has focus |
|---|
| 383 | message_textview = self.xmls[jid].get_widget('message_textview') |
|---|
| 384 | if not message_textview.is_focus(): |
|---|
| 385 | message_textview.grab_focus() |
|---|
| 386 | message_textview.emit('key_press_event', event) |
|---|
| 387 | |
|---|
| 388 | def on_conversation_vadjustment_value_changed(self, widget): |
|---|
| 389 | jid = self.get_active_jid() |
|---|
| 390 | if not self.nb_unread[jid]: |
|---|
| 391 | return |
|---|
| 392 | textview = self.xmls[jid].get_widget('conversation_textview') |
|---|
| 393 | buffer = textview.get_buffer() |
|---|
| 394 | end_iter = buffer.get_end_iter() |
|---|
| 395 | end_rect = textview.get_iter_location(end_iter) |
|---|
| 396 | visible_rect = textview.get_visible_rect() |
|---|
| 397 | if end_rect.y <= (visible_rect.y + visible_rect.height) and \ |
|---|
| 398 | self.window.is_active(): |
|---|
| 399 | #we are at the end |
|---|
| 400 | self.nb_unread[jid] = 0 |
|---|
| 401 | self.redraw_tab(jid) |
|---|
| 402 | self.show_title() |
|---|
| 403 | if self.plugin.systray_enabled: |
|---|
| 404 | self.plugin.systray.remove_jid(jid, self.account) |
|---|
| 405 | |
|---|
| 406 | def on_conversation_textview_motion_notify_event(self, widget, event): |
|---|
| 407 | """change the cursor to a hand when we are on a mail or an url""" |
|---|
| 408 | x, y, spam = widget.window.get_pointer() |
|---|
| 409 | x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y) |
|---|
| 410 | tags = widget.get_iter_at_location(x, y).get_tags() |
|---|
| 411 | if self.change_cursor: |
|---|
| 412 | widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( |
|---|
| 413 | gtk.gdk.Cursor(gtk.gdk.XTERM)) |
|---|
| 414 | self.change_cursor = None |
|---|
| 415 | tag_table = widget.get_buffer().get_tag_table() |
|---|
| 416 | for tag in tags: |
|---|
| 417 | if tag == tag_table.lookup('url') or tag == tag_table.lookup('mail'): |
|---|
| 418 | widget.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor( |
|---|
| 419 | gtk.gdk.Cursor(gtk.gdk.HAND2)) |
|---|
| 420 | self.change_cursor = tag |
|---|
| 421 | return False |
|---|
| 422 | |
|---|
| 423 | def on_clear(self, widget, textview): |
|---|
| 424 | '''clear text in the given textview''' |
|---|
| 425 | buffer = textview.get_buffer() |
|---|
| 426 | start, end = buffer.get_bounds() |
|---|
| 427 | buffer.delete(start, end) |
|---|
| 428 | |
|---|
| 429 | def on_conversation_textview_populate_popup(self, textview, menu): |
|---|
| 430 | item = gtk.MenuItem() # seperator |
|---|
| 431 | menu.prepend(item) |
|---|
| 432 | item = gtk.ImageMenuItem(gtk.STOCK_CLEAR) |
|---|
| 433 | menu.prepend(item) |
|---|
| 434 | item.connect('activate', self.on_clear, textview) |
|---|
| 435 | menu.show_all() |
|---|
| 436 | |
|---|
| 437 | def on_conversation_textview_button_press_event(self, widget, event): |
|---|
| 438 | # Do not open the standard popup menu, so we block right button |
|---|
| 439 | # click on a taged text |
|---|
| 440 | |
|---|
| 441 | if event.button != 3: |
|---|
| 442 | return False |
|---|
| 443 | |
|---|
| 444 | win = widget.get_window(gtk.TEXT_WINDOW_TEXT) |
|---|
| 445 | x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,\ |
|---|
| 446 | int(event.x), int(event.y)) |
|---|
| 447 | iter = widget.get_iter_at_location(x, y) |
|---|
| 448 | tags = iter.get_tags() |
|---|
| 449 | |
|---|
| 450 | if not tags: |
|---|
| 451 | return False |
|---|
| 452 | |
|---|
| 453 | for tag in tags: |
|---|
| 454 | tag_name = tag.get_property('name') |
|---|
| 455 | if 'url' in tag_name or 'mail' in tag_name: |
|---|
| 456 | return True |
|---|
| 457 | |
|---|
| 458 | def print_time_timeout(self, jid): |
|---|
| 459 | if not jid in self.xmls.keys(): |
|---|
| 460 | return 0 |
|---|
| 461 | if gajim.config.get('print_time') == 'sometimes': |
|---|
| 462 | textview = self.xmls[jid].get_widget('conversation_textview') |
|---|
| 463 | buffer = textview.get_buffer() |
|---|
| 464 | end_iter = buffer.get_end_iter() |
|---|
| 465 | tim = time.localtime() |
|---|
| 466 | tim_format = time.strftime('%H:%M', tim) |
|---|
| 467 | buffer.insert_with_tags_by_name(end_iter, |
|---|
| 468 | '\n' + tim_format, |
|---|
| 469 | 'time_sometimes') |
|---|
| 470 | #scroll to the end of the textview |
|---|
| 471 | end_rect = textview.get_iter_location(end_iter) |
|---|
| 472 | visible_rect = textview.get_visible_rect() |
|---|
| 473 | if end_rect.y <= (visible_rect.y + visible_rect.height): |
|---|
| 474 | #we are at the end |
|---|
| 475 | self.scroll_to_end(textview) |
|---|
| 476 | return 1 |
|---|
| 477 | if self.print_time_timeout_id.has_key(jid): |
|---|
| 478 | del self.print_time_timeout_id[jid] |
|---|
| 479 | return 0 |
|---|
| 480 | |
|---|
| 481 | def on_open_link_activated(self, widget, kind, text): |
|---|
| 482 | self.plugin.launch_browser_mailer(kind, text) |
|---|
| 483 | |
|---|
| 484 | def on_copy_link_activated(self, widget, text): |
|---|
| 485 | clip = gtk.clipboard_get() |
|---|
| 486 | clip.set_text(text) |
|---|
| 487 | |
|---|
| 488 | def make_link_menu(self, event, kind, text): |
|---|
| 489 | menu = gtk.Menu() |
|---|
| 490 | if kind == 'mail': |
|---|
| 491 | item = gtk.MenuItem(_('_Open Email Composer')) |
|---|
| 492 | else: |
|---|
| 493 | item = gtk.MenuItem(_('_Open Link')) |
|---|
| 494 | item.connect('activate', self.on_open_link_activated, kind, text) |
|---|
| 495 | menu.append(item) |
|---|
| 496 | if kind == 'mail': |
|---|
| 497 | item = gtk.MenuItem(_('_Copy Email Address')) |
|---|
| 498 | else: # It's an url |
|---|
| 499 | item = gtk.MenuItem(_('_Copy Link Address')) |
|---|
| 500 | item.connect('activate', self.on_copy_link_activated, text) |
|---|
| 501 | menu.append(item) |
|---|
| 502 | |
|---|
| 503 | menu.popup(None, None, None, event.button, event.time) |
|---|
| 504 | menu.show_all() |
|---|
| 505 | menu.reposition() |
|---|
| 506 | |
|---|
| 507 | def hyperlink_handler(self, texttag, widget, event, iter, kind): |
|---|
| 508 | if event.type == gtk.gdk.BUTTON_PRESS: |
|---|
| 509 | begin_iter = iter.copy() |
|---|
| 510 | #we get the begining of the tag |
|---|
| 511 | while not begin_iter.begins_tag(texttag): |
|---|
| 512 | begin_iter.backward_char() |
|---|
| 513 | end_iter = iter.copy() |
|---|
| 514 | #we get the end of the tag |
|---|
| 515 | while not end_iter.ends_tag(texttag): |
|---|
| 516 | end_iter.forward_char() |
|---|
| 517 | word = begin_iter.get_text(end_iter) |
|---|
| 518 | if event.button == 3: # right click |
|---|
| 519 | self.make_link_menu(event, kind, word) |
|---|
| 520 | else: |
|---|
| 521 | #we launch the correct application |
|---|
| 522 | self.plugin.launch_browser_mailer(kind, word) |
|---|
| 523 | |
|---|
| 524 | def print_with_tag_list(self, buffer, text, iter, tag_list): |
|---|
| 525 | begin_mark = buffer.create_mark('begin_tag', iter, True) |
|---|
| 526 | buffer.insert(iter, text) |
|---|
| 527 | begin_tagged = buffer.get_iter_at_mark(begin_mark) |
|---|
| 528 | for tag in tag_list: |
|---|
| 529 | buffer.apply_tag_by_name(tag, begin_tagged, iter) |
|---|
| 530 | buffer.delete_mark(begin_mark) |
|---|
| 531 | |
|---|
| 532 | def detect_and_print_special_text(self, otext, jid, other_tags, |
|---|
| 533 | print_all_special): |
|---|
| 534 | textview = self.xmls[jid].get_widget('conversation_textview') |
|---|
| 535 | buffer = textview.get_buffer() |
|---|
| 536 | |
|---|
| 537 | start = 0 |
|---|
| 538 | end = 0 |
|---|
| 539 | index = 0 |
|---|
| 540 | |
|---|
| 541 | # basic: links + mail + formatting is always checked (we like that) |
|---|
| 542 | if gajim.config.get('useemoticons'): # search for emoticons & urls |
|---|
| 543 | iterator = self.plugin.emot_and_basic_re.finditer(otext) |
|---|
| 544 | else: # search for just urls + mail + formatting |
|---|
| 545 | iterator = self.plugin.basic_pattern_re.finditer(otext) |
|---|
| 546 | for match in iterator: |
|---|
| 547 | start, end = match. |
|---|