From aff3697f069ba767232a8d9426963d3a4e27325b Mon Sep 17 00:00:00 2001 From: Yann Leboulanger Date: Tue, 9 Oct 2007 17:48:22 +0000 Subject: [PATCH] use python-sexy if available to have clickable links in chat window banner. fixes #2962 --- README.html | 1 + data/glade/message_window.glade | 2006 ++++++++++++------------------- src/chat_control.py | 68 +- src/common/gajim.py | 6 + src/features_window.py | 8 + src/groupchat_control.py | 13 +- 6 files changed, 871 insertions(+), 1231 deletions(-) diff --git a/README.html b/README.html index 7196e281b..f1f224896 100644 --- a/README.html +++ b/README.html @@ -38,6 +38,7 @@ Gajim is a GTK+ app that loves GNOME. You can do 'make' so you don't require gno
  • notification-daemon or notify-python (and D-Bus) to get cooler popups
  • D-Bus running to have gajim-remote working
  • python-dbus bindings
  • +
  • python-sexy to have clickable URLs in chat windows
  • diff --git a/data/glade/message_window.glade b/data/glade/message_window.glade index 77d9028b1..b7f4d0f42 100644 --- a/data/glade/message_window.glade +++ b/data/glade/message_window.glade @@ -1,1216 +1,796 @@ - - - + + + - - - - GTK_WINDOW_TOPLEVEL - GTK_WIN_POS_NONE - False - 480 - 440 - True - False - True - False - False - GDK_WINDOW_TYPE_HINT_NORMAL - GDK_GRAVITY_NORTH_WEST - True - False - - - - True - 0.5 - 0.5 - 1 - 1 - 2 - 0 - 0 - 0 - - - - True - True - True - True - 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 - - - - - 0 - True - True - - - - - - 3 - True - False - 6 - - - - True - Click to insert an emoticon (Alt+M) - 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-execute - 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 - 0.5 - 0 - 0 - PANGO_ELLIPSIZE_END - -1 - False - 0 - - - 0 - True - True - - - - - - 20 - 20 - True - True - GTK_RELIEF_NONE - True - - - - True - gtk-close - 1 - 0.5 - 0.5 - 0 - 6 - - - - - 0 - False - False - - - - - - - tab - - - - - - 3 - False - 0 - - - - True - 0.5 - 0.5 - 1 - 1 - 0 - 1 - 3 - 3 - - - + + + + + + False + False + + + + + True + True + 3 + 495 + + + 150 + True + 6 + + + True + 6 + + + 200 + 60 + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + + + + + + True + True + GTK_POLICY_NEVER + GTK_POLICY_NEVER + GTK_SHADOW_IN + + + + + + False + 1 + + + + + + + False + False + + + + + 100 + True + False + GTK_POLICY_NEVER + GTK_POLICY_AUTOMATIC + GTK_SHADOW_IN + + + True + True + 1 + False + + + + + False + False + + + + + 1 + + + + + True + 3 + + + True + + + + + + + + True + 6 + + + True + True + Click to insert an emoticon (Alt+M) + 0 + + + True + + + True + 2 + gtk-missing-image + + + + + True + GTK_ARROW_DOWN + + + 1 + + + + + + + False + False + + + + + True + True + 0 + + + True + 0 + 0 + + + True + 2 + + + True + gtk-execute + + + False + False + + + + + True + _Actions + True + + + False + False + 1 + + + + + True + GTK_ARROW_DOWN + + + False + False + 2 + + + + + + + + + False + False + 1 + + + + + True + + + False + 2 + 2 + + + + + True + True + 0 + + + True + 0 + 0 + + + True + 2 + + + True + gtk-jump-to + + + False + False + + + + + True + _Send + True + + + False + False + 1 + + + + + + + + + False + False + 3 + + + + + False + 1 + + + + + False + 2 + + + + + 1 + + + + + True + False + + + True + 4 + + + True + gtk-missing-image + + + False + False + + + + + True + True + + + False + False + 1 + + + + + 20 + 20 + True + True + GTK_RELIEF_NONE + 0 + + + True + 6 + gtk-close + 1 + + + + + False + False + 2 + + + + + + + tab + 1 + False + + + + + + + diff --git a/src/chat_control.py b/src/chat_control.py index cd586732f..1d5796779 100644 --- a/src/chat_control.py +++ b/src/chat_control.py @@ -28,6 +28,7 @@ import message_control import dialogs import history_window import notify +import re from common import gajim from common import helpers @@ -46,7 +47,6 @@ try: except: HAS_GTK_SPELL = False - # the next script, executed in the "po" directory, # generates the following list. ##!/bin/sh @@ -54,11 +54,15 @@ except: #echo "{_('en'):'en'",$LANG"}" 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'} - ################################################################################ class ChatControlBase(MessageControl): '''A base class containing a banner, ConversationTextview, MessageTextView ''' + def make_href(self, match): + url_color = gajim.config.get('urlmsgcolor') + return '%s' % (match.group(), + url_color, match.group()) + def get_font_attrs(self): ''' get pango font attributes for banner from theme settings ''' theme = gajim.config.get('roster_theme') @@ -120,6 +124,9 @@ class ChatControlBase(MessageControl): event_keymod): pass # Derived should implement this rather than connecting to the event itself. + def status_url_clicked(self, widget, url): + helpers.launch_browser_mailer('url', url) + def __init__(self, type_id, parent_win, widget_name, contact, acct, resource = None): MessageControl.__init__(self, type_id, parent_win, widget_name, @@ -138,6 +145,22 @@ class ChatControlBase(MessageControl): id = widget.connect('button-press-event', self._on_banner_eventbox_button_press_event) self.handlers[id] = widget + + self.urlfinder = re.compile("(https?://|www|ftp)[^ ]+") + + if gajim.HAVE_PYSEXY: + import sexy + self.banner_status_label = sexy.UrlLabel() + self.banner_status_label.connect('url_activated', self.status_url_clicked) + else: + self.banner_status_label = gtk.Label() + self.banner_status_label.set_selectable(True) + self.banner_status_label.set_alignment(0,0.5) + + banner_vbox = self.xml.get_widget('banner_vbox') + banner_vbox.pack_start(self.banner_status_label) + self.banner_status_label.show() + # Init DND self.TARGET_TYPE_URI_LIST = 80 self.dnd_list = [ ( 'text/uri-list', 0, self.TARGET_TYPE_URI_LIST ), @@ -247,7 +270,6 @@ class ChatControlBase(MessageControl): spell.set_language(lang) except (gobject.GError, RuntimeError), msg: dialogs.AspellDictError(lang) - self.style_event_id = 0 self.conv_textview.tv.show() self._paint_banner() @@ -324,6 +346,7 @@ class ChatControlBase(MessageControl): banner_eventbox = self.xml.get_widget('banner_eventbox') banner_name_label = self.xml.get_widget('banner_name_label') self.disconnect_style_event(banner_name_label) + self.disconnect_style_event(self.banner_status_label) if bgcolor: banner_eventbox.modify_bg(gtk.STATE_NORMAL, gtk.gdk.color_parse(bgcolor)) @@ -333,25 +356,33 @@ class ChatControlBase(MessageControl): if textcolor: banner_name_label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse(textcolor)) + self.banner_status_label.modify_fg(gtk.STATE_NORMAL, + gtk.gdk.color_parse(textcolor)) default_fg = False else: default_fg = True if default_bg or default_fg: self._on_style_set_event(banner_name_label, None, default_fg, default_bg) - + self._on_style_set_event(self.banner_status_label, None, default_fg, + default_bg) + def disconnect_style_event(self, widget): - if self.style_event_id: - widget.disconnect(self.style_event_id) - del self.handlers[self.style_event_id] - self.style_event_id = 0 + # Try to find the event_id + found = False + for id in self.handlers: + if self.handlers[id] == widget: + found = True + break + if found: + widget.disconnect(id) + del self.handlers[id] def connect_style_event(self, widget, set_fg = False, set_bg = False): self.disconnect_style_event(widget) - self.style_event_id = widget.connect('style-set', - self._on_style_set_event, set_fg, set_bg) - self.handlers[self.style_event_id] = widget - + id = widget.connect('style-set', self._on_style_set_event, set_fg, set_bg) + self.handlers[id] = widget + def _on_style_set_event(self, widget, style, *opts): '''set style of widget from style class *.Frame.Eventbox opts[0] == True -> set fg color @@ -365,7 +396,7 @@ class ChatControlBase(MessageControl): fg_color = widget.style.fg[gtk.STATE_SELECTED] widget.modify_fg(gtk.STATE_NORMAL, fg_color) self.connect_style_event(widget, opts[0], opts[1]) - + def _on_keypress_event(self, widget, event): if event.state & gtk.gdk.CONTROL_MASK: # CTRL + l|L: clear conv_textview @@ -1162,6 +1193,7 @@ class ChatControl(ChatControlBase): self.status_tooltip.set_tip(banner_eventbox, status) self.status_tooltip.enable() banner_name_label.set_ellipsize(pango.ELLIPSIZE_END) + self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) status = helpers.reduce_chars_newlines(status, max_lines = 1) status_escaped = gobject.markup_escape_text(status) @@ -1192,9 +1224,15 @@ class ChatControl(ChatControlBase): # weight="heavy" size="x-large" label_text = '%s%s' % \ (font_attrs, name, font_attrs_small, acct_info) + if status_escaped: - label_text += '\n%s' %\ - (font_attrs_small, status_escaped) + if gajim.HAVE_PYSEXY: + status_text = self.urlfinder.sub(self.make_href, status_escaped) + status_text = '%s' % (font_attrs_small, status_text) + else: + status_text = '%s' % (font_attrs_small, status_escaped) + + self.banner_status_label.set_markup(status_text) else: self.status_tooltip.disable() # setup the label that holds name and jid diff --git a/src/common/gajim.py b/src/common/gajim.py index f43967edf..2a6de1b4f 100644 --- a/src/common/gajim.py +++ b/src/common/gajim.py @@ -142,6 +142,12 @@ try: except ImportError: HAVE_PYCRYPTO = False +HAVE_PYSEXY = True +try: + import sexy +except ImportError: + HAVE_PYSEXY = False + def get_nick_from_jid(jid): pos = jid.find('@') return jid[:pos] diff --git a/src/features_window.py b/src/features_window.py index 1cf562d46..cd3c0b45c 100644 --- a/src/features_window.py +++ b/src/features_window.py @@ -98,6 +98,10 @@ class FeaturesWindow: _('Generate XHTML output from RST code (see http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html).'), _('Requires python-docutils.'), _('Requires python-docutils.')), + _('libsexy'): (self.pysexy_available, + _('Ability to have clickable URLs in chat window.'), + _('Requires python-sexy.'), + _('Requires python-sexy.')), } # name, supported @@ -294,3 +298,7 @@ class FeaturesWindow: except: return False return True + + def pysexy_available(self): + from common import gajim + return gajim.HAVE_PYSEXY diff --git a/src/groupchat_control.py b/src/groupchat_control.py index 5b8c436e5..278ebcbd3 100644 --- a/src/groupchat_control.py +++ b/src/groupchat_control.py @@ -481,6 +481,7 @@ class GroupchatControl(ChatControlBase): houses the room jid, subject. ''' self.name_label.set_ellipsize(pango.ELLIPSIZE_END) + self.banner_status_label.set_ellipsize(pango.ELLIPSIZE_END) font_attrs, font_attrs_small = self.get_font_attrs() if self.is_continued: nicks = [] @@ -495,16 +496,22 @@ class GroupchatControl(ChatControlBase): text = '%s' % (font_attrs, title) else: text = '%s' % (font_attrs, self.room_jid) + self.name_label.set_markup(text) + if self.subject: subject = helpers.reduce_chars_newlines(self.subject, max_lines = 2) subject = gobject.markup_escape_text(subject) - text += '\n%s' % (font_attrs_small, subject) + if gajim.HAVE_PYSEXY: + subject_text = self.urlfinder.sub(self.make_href, subject) + subject_text = '%s' % (font_attrs_small, + subject_text) + else: + subject_text = '%s' % (font_attrs_small, subject) + self.banner_status_label.set_markup(subject_text) # tooltip must always hold ALL the subject self.subject_tooltip.set_tip(self.event_box, self.subject) - self.name_label.set_markup(text) - def prepare_context_menu(self): '''sets sensitivity state for configure_room''' ag = gtk.accel_groups_from_object(self.parent_win.window)[0]