Changeset 9123 for branches/pep/src/chat_control.py
- Timestamp:
- 12/12/07 09:44:46 (12 months ago)
- Files:
-
- 1 modified
-
branches/pep/src/chat_control.py (modified) (46 diffs)
Legend:
- Unmodified
- Added
- Removed
-
branches/pep/src/chat_control.py
r8478 r9123 1 1 ## chat_control.py 2 2 ## 3 ## Copyright (C) 2006 Yann Le Boulanger <asterix@lagaule.org>3 ## Copyright (C) 2006 Yann Leboulanger <asterix@lagaule.org> 4 4 ## Copyright (C) 2006-2007 Nikos Kouremenos <kourem@gmail.com> 5 5 ## Copyright (C) 2006 Travis Shirk <travis@pobox.com> 6 ## Copyright (C) 2006Dimitur Kirov <dkirov@gmail.com>6 ## Dimitur Kirov <dkirov@gmail.com> 7 7 ## Copyright (C) 2007 Lukas Petrovicky <lukas@petrovicky.net> 8 ## Copyright (C) 2007 Julien Pivotto <roidelapluie@gmail.com> 8 ## Julien Pivotto <roidelapluie@gmail.com> 9 ## Stephan Erb <steve-e@h3c.de> 9 10 ## 10 ## This program is free software; you can redistribute it and/or modify 11 ## This file is part of Gajim. 12 ## 13 ## Gajim is free software; you can redistribute it and/or modify 11 14 ## it under the terms of the GNU General Public License as published 12 ## by the Free Software Foundation; version 2only.15 ## by the Free Software Foundation; version 3 only. 13 16 ## 14 ## This program is distributed in the hope that it will be useful,17 ## Gajim is distributed in the hope that it will be useful, 15 18 ## but WITHOUT ANY WARRANTY; without even the implied warranty of 16 19 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 20 ## GNU General Public License for more details. 21 ## 22 ## You should have received a copy of the GNU General Public License 23 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>. 18 24 ## 19 25 … … 28 34 import history_window 29 35 import notify 36 import re 30 37 31 38 from common import gajim … … 46 53 HAS_GTK_SPELL = False 47 54 48 49 55 # the next script, executed in the "po" directory, 50 56 # generates the following list. 51 57 ##!/bin/sh 52 #LANG=$(for i in *.po; do j=${i/.po/}; echo -n "_('"$j"')":" '"$j"', " ; done)58 #LANG=$(for i in *.po; do j=${i/.po/}; echo -n "_('"$j"')":" '"$j"', " ; done) 53 59 #echo "{_('en'):'en'",$LANG"}" 54 60 langs = {_('English'): 'en', _('Belarusian'): 'be', _('Bulgarian'): 'bg', _('Breton'): 'br', _('Czech'): 'cs', _('German'): 'de', _('Greek'): 'el', _('British'): 'en_GB', _('Esperanto'): 'eo', _('Spanish'): 'es', _('Basque'): 'eu', _('French'): 'fr', _('Croatian'): 'hr', _('Italian'): 'it', _('Norwegian (b)'): 'nb', _('Dutch'): 'nl', _('Norwegian'): 'no', _('Polish'): 'pl', _('Portuguese'): 'pt', _('Brazilian Portuguese'): 'pt_BR', _('Russian'): 'ru', _('Serbian'): 'sr', _('Slovak'): 'sk', _('Swedish'): 'sv', _('Chinese (Ch)'): 'zh_CN'} 55 56 61 57 62 ################################################################################ … … 59 64 '''A base class containing a banner, ConversationTextview, MessageTextView 60 65 ''' 66 def make_href(self, match): 67 url_color = gajim.config.get('urlmsgcolor') 68 return '<a href="%s"><span color="%s">%s</span></a>' % (match.group(), 69 url_color, match.group()) 70 61 71 def get_font_attrs(self): 62 ''' get pango font attributes for banner from theme settings '''72 ''' get pango font attributes for banner from theme settings ''' 63 73 theme = gajim.config.get('roster_theme') 64 74 bannerfont = gajim.config.get_per('themes', theme, 'bannerfont') … … 119 129 event_keymod): 120 130 pass # Derived should implement this rather than connecting to the event itself. 131 132 def status_url_clicked(self, widget, url): 133 helpers.launch_browser_mailer('url', url) 121 134 122 135 def __init__(self, type_id, parent_win, widget_name, contact, acct, … … 138 151 self._on_banner_eventbox_button_press_event) 139 152 self.handlers[id] = widget 153 154 self.urlfinder = re.compile(r"(www\.(?!\.)|[a-z][a-z0-9+.-]*://)[^\s<>'\"]+[^!,\.\s<>\)'\"\]]") 155 156 if gajim.HAVE_PYSEXY: 157 import sexy 158 self.banner_status_label = sexy.UrlLabel() 159 self.banner_status_label.connect('url_activated', self.status_url_clicked) 160 else: 161 self.banner_status_label = gtk.Label() 162 self.banner_status_label.set_selectable(True) 163 self.banner_status_label.set_alignment(0,0.5) 164 165 banner_vbox = self.xml.get_widget('banner_vbox') 166 banner_vbox.pack_start(self.banner_status_label) 167 self.banner_status_label.show() 168 140 169 # Init DND 141 170 self.TARGET_TYPE_URI_LIST = 80 … … 152 181 # Create textviews and connect signals 153 182 self.conv_textview = ConversationTextview(self.account) 183 id = self.conv_textview.tv.connect('key_press_event', 184 self._conv_textview_key_press_event) 185 self.handlers[id] = self.conv_textview.tv 154 186 # FIXME: DND on non editable TextView, find a better way 155 187 self.drag_entered = False … … 247 279 except (gobject.GError, RuntimeError), msg: 248 280 dialogs.AspellDictError(lang) 249 self.style_event_id = 0250 281 self.conv_textview.tv.show() 251 282 self._paint_banner() … … 253 284 # For JEP-0172 254 285 self.user_nick = None 286 287 self.smooth = True 255 288 256 289 def on_msg_textview_populate_popup(self, textview, menu): … … 322 355 banner_name_label = self.xml.get_widget('banner_name_label') 323 356 self.disconnect_style_event(banner_name_label) 357 self.disconnect_style_event(self.banner_status_label) 324 358 if bgcolor: 325 359 banner_eventbox.modify_bg(gtk.STATE_NORMAL, … … 331 365 banner_name_label.modify_fg(gtk.STATE_NORMAL, 332 366 gtk.gdk.color_parse(textcolor)) 367 self.banner_status_label.modify_fg(gtk.STATE_NORMAL, 368 gtk.gdk.color_parse(textcolor)) 333 369 default_fg = False 334 370 else: … … 337 373 self._on_style_set_event(banner_name_label, None, default_fg, 338 374 default_bg) 339 375 self._on_style_set_event(self.banner_status_label, None, default_fg, 376 default_bg) 377 340 378 def disconnect_style_event(self, widget): 341 if self.style_event_id: 342 widget.disconnect(self.style_event_id) 343 del self.handlers[self.style_event_id] 344 self.style_event_id = 0 379 # Try to find the event_id 380 found = False 381 for id in self.handlers: 382 if self.handlers[id] == widget: 383 found = True 384 break 385 if found: 386 widget.disconnect(id) 387 del self.handlers[id] 345 388 346 389 def connect_style_event(self, widget, set_fg = False, set_bg = False): 347 390 self.disconnect_style_event(widget) 348 self.style_event_id = widget.connect('style-set', 349 self._on_style_set_event, set_fg, set_bg) 350 self.handlers[self.style_event_id] = widget 351 391 id = widget.connect('style-set', self._on_style_set_event, set_fg, set_bg) 392 self.handlers[id] = widget 393 352 394 def _on_style_set_event(self, widget, style, *opts): 353 395 '''set style of widget from style class *.Frame.Eventbox … … 363 405 widget.modify_fg(gtk.STATE_NORMAL, fg_color) 364 406 self.connect_style_event(widget, opts[0], opts[1]) 365 407 408 def _conv_textview_key_press_event(self, widget, event): 409 if gtk.gtk_version < (2, 12, 0): 410 return 411 if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK): 412 return False 413 self.parent_win.notebook.emit('key_press_event', event) 414 366 415 def _on_keypress_event(self, widget, event): 367 416 if event.state & gtk.gdk.CONTROL_MASK: … … 390 439 self.parent_win.notebook.emit('key_press_event', event) 391 440 return True 441 392 442 elif event.keyval == gtk.keysyms.m and \ 393 443 (event.state & gtk.gdk.MOD1_MASK): # alt + m opens emoticons menu … … 403 453 cursor = msg_tv.get_iter_location(buf.get_iter_at_mark( 404 454 buf.get_insert())) 405 cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT,455 cursor = msg_tv.buffer_to_window_coords(gtk.TEXT_WINDOW_TEXT, 406 456 cursor.x, cursor.y) 407 457 x = origin[0] + cursor[0] … … 520 570 def _on_drag_data_received(self, widget, context, x, y, selection, 521 571 target_type, timestamp): 522 pass # Derived classes SHOULD implement this method572 pass # Derived classes SHOULD implement this method 523 573 524 574 def _on_drag_leave(self, widget, context, time): … … 615 665 if kind in ('incoming', 'incoming_queue'): 616 666 gc_message = False 617 if self.type_id == message_control.TYPE_GC:667 if self.type_id == message_control.TYPE_GC: 618 668 gc_message = True 619 669 … … 713 763 jid = self.contact.jid 714 764 715 if gajim.interface.instances['logs'].has_key(jid): 716 gajim.interface.instances['logs'][jid].window.present() 717 else: 718 gajim.interface.instances['logs'][jid] = \ 765 if gajim.interface.instances.has_key('logs'): 766 gajim.interface.instances['logs'].window.present() 767 gajim.interface.instances['logs'].open_history(jid, self.account) 768 else: 769 gajim.interface.instances['logs'] = \ 719 770 history_window.HistoryWindow(jid, self.account) 720 771 … … 796 847 # minimum for conversation_textview and maximum for message_textview 797 848 # we set to automatic the scrollbar policy 798 diff_y = message_height - requisition.height849 diff_y = message_height - requisition.height 799 850 if diff_y != 0: 800 851 if conversation_height + diff_y < min_height: … … 813 864 gtk.POLICY_NEVER) 814 865 self.msg_scrolledwindow.set_property('height-request', -1) 815 816 self.conv_textview.bring_scroll_to_end(diff_y - 18) 817 866 self.conv_textview.bring_scroll_to_end(diff_y - 18, False) 867 else: 868 self.conv_textview.bring_scroll_to_end(diff_y - 18, self.smooth) 869 self.smooth = True # reinit the flag 818 870 # enable scrollbar automatic policy for horizontal scrollbar 819 871 # if message we have in message_textview is too big … … 896 948 return 897 949 self.sent_history_pos = self.sent_history_pos - 1 950 self.smooth = False 898 951 conv_buf.set_text(self.sent_history[self.sent_history_pos]) 899 952 elif direction == 'down': … … 905 958 906 959 self.sent_history_pos = self.sent_history_pos + 1 960 self.smooth = False 907 961 conv_buf.set_text(self.sent_history[self.sent_history_pos]) 908 962 … … 949 1003 old_msg_kind = None # last kind of the printed message 950 1004 CHAT_CMDS = ['clear', 'compact', 'help', 'me', 'ping', 'say'] 951 952 def __init__(self, parent_win, contact, acct, resource = None):1005 1006 def __init__(self, parent_win, contact, acct, session, resource = None): 953 1007 ChatControlBase.__init__(self, self.TYPE_ID, parent_win, 954 1008 'chat_child_vbox', contact, acct, resource) 955 1009 956 1010 # for muc use: 957 1011 # widget = self.xml.get_widget('muc_window_actions_button') … … 969 1023 self.show_bigger_avatar_timeout_id = None 970 1024 self.bigger_avatar_window = None 971 self.show_avatar(self.contact.resource) 1025 self.show_avatar(self.contact.resource) 972 1026 973 1027 # chatstate timers and state … … 983 1037 self._on_message_tv_buffer_changed) 984 1038 self.handlers[id] = message_tv_buffer 985 1039 986 1040 widget = self.xml.get_widget('avatar_eventbox') 987 1041 id = widget.connect('enter-notify-event', … … 1003 1057 if self.contact.jid in gajim.encrypted_chats[self.account]: 1004 1058 self.xml.get_widget('gpg_togglebutton').set_active(True) 1005 1059 1060 self.set_session(session) 1061 1006 1062 self.status_tooltip = gtk.Tooltips() 1007 1063 self.update_ui() … … 1046 1102 id = menuitem.connect('activate', 1047 1103 gtkgui_helpers.on_avatar_save_as_menuitem_activate, 1048 self.contact.jid, self.account, self.contact.get_shown_name() + 1104 self.contact.jid, self.account, self.contact.get_shown_name() + \ 1049 1105 '.jpeg') 1050 1106 self.handlers[id] = menuitem … … 1147 1203 status = contact.status 1148 1204 if status is not None: 1149 self.status_tooltip.set_tip(banner_eventbox, status)1150 self.status_tooltip.enable()1151 1205 banner_name_label.set_ellipsize(pango.ELLIPSIZE_END) 1152 status = helpers.reduce_chars_newlines(status, max_lines = 1) 1153 status_escaped = gobject.markup_escape_text(status) 1206 self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) 1207 status_reduced = helpers.reduce_chars_newlines(status, max_lines = 1) 1208 status_escaped = gobject.markup_escape_text(status_reduced) 1154 1209 1155 1210 font_attrs, font_attrs_small = self.get_font_attrs() … … 1180 1235 label_text = '<span %s>%s</span><span %s>%s</span>' % \ 1181 1236 (font_attrs, name, font_attrs_small, acct_info) 1237 1182 1238 if status_escaped: 1183 label_text += '\n<span %s>%s</span>' %\ 1184 (font_attrs_small, status_escaped) 1185 else: 1239 if gajim.HAVE_PYSEXY: 1240 status_text = self.urlfinder.sub(self.make_href, status_escaped) 1241 status_text = '<span %s>%s</span>' % (font_attrs_small, status_text) 1242 else: 1243 status_text = '<span %s>%s</span>' % (font_attrs_small, status_escaped) 1244 self.status_tooltip.set_tip(banner_eventbox, status) 1245 self.banner_status_label.show() 1246 self.banner_status_label.set_no_show_all(False) 1247 else: 1248 status_text = '' 1186 1249 self.status_tooltip.disable() 1250 self.banner_status_label.hide() 1251 self.banner_status_label.set_no_show_all(True) 1252 1253 self.banner_status_label.set_markup(status_text) 1187 1254 # setup the label that holds name and jid 1188 1255 banner_name_label.set_markup(label_text) … … 1232 1299 1233 1300 if command == 'me': 1234 return False # This is not really a command 1301 if len(message_array): 1302 return False # /me is not really a command 1303 else: 1304 self.get_command_help(command) 1305 return True # do not send "/me" as message 1235 1306 1236 1307 if command == 'help': … … 1296 1367 contact = self.contact 1297 1368 1369 encrypted = bool(self.session) and self.session.enable_encryption 1370 1298 1371 keyID = '' 1299 encrypted = False1300 1372 if self.xml.get_widget('gpg_togglebutton').get_active(): 1301 1373 keyID = contact.keyID 1302 1374 encrypted = True 1303 1304 1375 1305 1376 chatstates_on = gajim.config.get('outgoing_chat_state_notifications') != \ … … 1314 1385 # because we want it sent with REAL message 1315 1386 # (not standlone) eg. one that has body 1316 1387 1317 1388 if contact.our_chatstate: 1318 1389 # We already asked for xep 85, don't ask it twice … … 1394 1465 self.kbd_activity_in_last_30_secs = False 1395 1466 1467 def on_cancel_session_negotiation(self): 1468 msg = _('Session negotiation cancelled') 1469 ChatControlBase.print_conversation_line(self, msg, 'status', '', None) 1470 1471 # print esession settings to textview 1472 def print_esession_details(self): 1473 if self.session and self.session.enable_encryption: 1474 msg = _('E2E encryption enabled') 1475 ChatControlBase.print_conversation_line(self, msg, 'status', '', None) 1476 1477 if self.session.loggable: 1478 msg = _('Session WILL be logged') 1479 else: 1480 msg = _('Session WILL NOT be logged') 1481 1482 ChatControlBase.print_conversation_line(self, msg, 'status', '', None) 1483 else: 1484 msg = _('E2E encryption disabled') 1485 ChatControlBase.print_conversation_line(self, msg, 'status', '', None) 1486 1396 1487 def print_conversation(self, text, frm = '', tim = None, 1397 1488 encrypted = False, subject = None, xhtml = None): … … 1413 1504 name = '' 1414 1505 else: 1415 ec = gajim.encrypted_chats[self.account] 1416 if encrypted and jid not in ec: 1417 msg = _('Encryption enabled') 1418 ChatControlBase.print_conversation_line(self, msg, 1419 'status', '', tim) 1420 ec.append(jid) 1421 elif not encrypted and jid in ec: 1422 msg = _('Encryption disabled') 1423 ChatControlBase.print_conversation_line(self, msg, 1424 'status', '', tim) 1425 ec.remove(jid) 1426 self.xml.get_widget('gpg_togglebutton').set_active(encrypted) 1506 if self.session and self.session.enable_encryption: 1507 if not encrypted: 1508 msg = _('The following message was NOT encrypted') 1509 ChatControlBase.print_conversation_line(self, msg, 1510 'status', '', tim) 1511 else: 1512 # GPG encryption 1513 ec = gajim.encrypted_chats[self.account] 1514 if encrypted and jid not in ec: 1515 msg = _('OpenPGP Encryption enabled') 1516 ChatControlBase.print_conversation_line(self, msg, 1517 'status', '', tim) 1518 ec.append(jid) 1519 elif not encrypted and jid in ec: 1520 msg = _('OpenPGP Encryption disabled') 1521 ChatControlBase.print_conversation_line(self, msg, 1522 'status', '', tim) 1523 ec.remove(jid) 1524 self.xml.get_widget('gpg_togglebutton').set_active(encrypted) 1525 1427 1526 if not frm: 1428 1527 kind = 'incoming' … … 1531 1630 xml = gtkgui_helpers.get_glade('chat_control_popup_menu.glade') 1532 1631 menu = xml.get_widget('chat_control_popup_menu') 1533 1632 1534 1633 history_menuitem = xml.get_widget('history_menuitem') 1535 1634 toggle_gpg_menuitem = xml.get_widget('toggle_gpg_menuitem') 1635 toggle_e2e_menuitem = xml.get_widget('toggle_e2e_menuitem') 1536 1636 add_to_roster_menuitem = xml.get_widget('add_to_roster_menuitem') 1537 1637 send_file_menuitem = xml.get_widget('send_file_menuitem') 1538 1638 information_menuitem = xml.get_widget('information_menuitem') 1539 1639 convert_to_gc_menuitem = xml.get_widget('convert_to_groupchat') 1640 muc_icon = gajim.interface.roster.load_icon('muc_active') 1641 if muc_icon: 1642 convert_to_gc_menuitem.set_image(muc_icon) 1643 1644 ag = gtk.accel_groups_from_object(self.parent_win.window)[0] 1645 history_menuitem.add_accelerator('activate', ag, gtk.keysyms.h, gtk.gdk.CONTROL_MASK, 1646 gtk.ACCEL_VISIBLE) 1647 information_menuitem.add_accelerator('activate', ag, gtk.keysyms.i, 1648 gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) 1649 1540 1650 contact = self.parent_win.get_active_contact() 1541 1651 jid = contact.jid 1542 1543 # history_menuitem 1544 if gajim.jid_is_transport(jid): 1545 history_menuitem.set_sensitive(False) 1546 1652 1547 1653 # check if gpg capabitlies or else make gpg toggle insensitive 1548 1654 gpg_btn = self.xml.get_widget('gpg_togglebutton') … … 1551 1657 toggle_gpg_menuitem.set_active(isactive) 1552 1658 toggle_gpg_menuitem.set_property('sensitive', is_sensitive) 1553 1659 1660 # TODO: check that the remote client supports e2e 1661 if not gajim.HAVE_PYCRYPTO: 1662 toggle_e2e_menuitem.set_sensitive(False) 1663 else: 1664 isactive = int(self.session != None and self.session.enable_encryption) 1665 toggle_e2e_menuitem.set_active(isactive) 1666 1554 1667 # If we don't have resource, we can't do file transfer 1555 1668 # in transports, contact holds our info we need to disable it too … … 1561 1674 else: 1562 1675 send_file_menuitem.set_sensitive(False) 1563 1676 1677 # check if it's possible to convert to groupchat 1678 if gajim.get_transport_name_from_jid(jid) or \ 1679 gajim.connections[self.account].is_zeroconf: 1680 convert_to_gc_menuitem.set_sensitive(False) 1681 1564 1682 # add_to_roster_menuitem 1565 1683 if _('Not in Roster') in contact.groups: … … 1569 1687 add_to_roster_menuitem.hide() 1570 1688 add_to_roster_menuitem.set_no_show_all(True) 1571 1572 1689 1573 1690 # connect signals 1574 1691 id = history_menuitem.connect('activate', … … 1583 1700 id = toggle_gpg_menuitem.connect('activate', 1584 1701 self._on_toggle_gpg_menuitem_activate) 1702 id = toggle_e2e_menuitem.connect('activate', 1703 self._on_toggle_e2e_menuitem_activate) 1585 1704 self.handlers[id] = toggle_gpg_menuitem 1586 1705 id = information_menuitem.connect('activate', 1587 1706 self._on_contact_information_menuitem_activate) 1588 1707 self.handlers[id] = information_menuitem 1589 menu.connect('selection-done', lambda w:w.destroy()) 1708 id = convert_to_gc_menuitem.connect('activate', 1709 self._on_convert_to_gc_menuitem_activate) 1710 self.handlers[id] = convert_to_gc_menuitem 1711 menu.connect('selection-done', self.destroy_menu, history_menuitem, 1712 information_menuitem) 1590 1713 return menu 1714 1715 def destroy_menu(self, menu, history_menuitem, information_menuitem): 1716 # destroy accelerators 1717 ag = gtk.accel_groups_from_object(self.parent_win.window)[0] 1718 history_menuitem.remove_accelerator(ag, gtk.keysyms.h, gtk.gdk.CONTROL_MASK) 1719 information_menuitem.remove_accelerator(ag, gtk.keysyms.i, gtk.gdk.CONTROL_MASK) 1720 # destroy menu 1721
