1
### Copyright (C) 2005 Thomas M. Hinkle
3
### This library is free software; you can redistribute it and/or
4
### modify it under the terms of the GNU General Public License as
5
### published by the Free Software Foundation; either version 2 of the
6
### License, or (at your option) any later version.
8
### This library is distributed in the hope that it will be useful,
9
### but WITHOUT ANY WARRANTY; without even the implied warranty of
10
### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
### General Public License for more details.
13
### You should have received a copy of the GNU General Public License
14
### along with this library; if not, write to the Free Software
15
### Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
18
# Largely based on hypertext.py example in pygtk docs by
19
# Maik Hertha <maik.hertha@berlin.de>
21
import pango,gtk,gobject
22
import re, xml.sax.saxutils
23
from TextBufferMarkup import PangoBuffer
24
from gourmet.gdebug import *
26
class LinkedPangoBuffer (PangoBuffer):
28
href_regexp = re.compile(r"<a href=['\"]([^'\"]+)['\"][^>]*>(.*?)</a>")
30
url_markup = 'underline="single" color="blue"'
32
url_props = [('underline',pango.UNDERLINE_SINGLE),
33
('foreground-gdk',gtk.gdk.color_parse('blue')),
38
def set_text (self, txt):
39
m = self.href_regexp.search(txt)
43
body = xml.sax.saxutils.unescape(m.groups()[1])
44
if self.markup_dict.has_key(body) and self.markup_dict[body]!=href:
45
raise """Damn -- our not-so-clever implementation of <a href=""> parsing requires
46
that no two distinct links have the same text describing them!"""
47
self.markup_dict[body]=href
48
m = self.href_regexp.search(txt,m.end())
49
txt = self.href_regexp.sub(r'<span %s>\2</span>'%self.url_markup,txt)
50
PangoBuffer.set_text(self,txt)
52
def insert_with_tags (self, itr, text, *tags):
54
for p,v in self.url_props:
57
if isinstance(v,gtk.gdk.Color):
59
if v.red==c.red and v.blue==c.blue and v.green==c.green:
61
elif t.get_property(p)==v:
66
if match and self.markup_dict.has_key(text):
67
new_tag = self.create_tag()
68
new_tag.set_data('href',self.markup_dict[text])
72
print 'Funny',text,'looks like a link, but is not in markup_dict',self.markup_dict
73
PangoBuffer.insert_with_tags(self,itr,text,*tags)
75
class LinkedTextView (gtk.TextView):
77
hovering_over_link = False
78
hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
79
regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM)
82
'link-activated':(gobject.SIGNAL_RUN_LAST,
84
[gobject.TYPE_STRING]),
88
gobject.GObject.__init__(self)
89
gtk.TextView.__init__(self)
90
self.set_buffer(self.make_buffer())
91
buf = self.get_buffer()
92
self.set_text = buf.set_text
93
self.connect('key-press-event',self.key_press_event)
94
self.connect('event-after',self.event_after)
95
self.connect('motion-notify-event',self.motion_notify_event)
96
self.connect('visibility-notify-event',self.visibility_notify_event)
98
def make_buffer (self):
99
return LinkedPangoBuffer()
101
# Links can be activated by pressing Enter.
102
def key_press_event(self, text_view, event):
103
keyname = gtk.gdk.keyval_name(event.keyval)
104
if keyname in ['Return','KP_Enter']:
105
buffer = text_view.get_buffer()
106
iter = buffer.get_iter_at_mark(buffer.get_insert())
107
return self.follow_if_link(text_view, iter)
109
# Links can also be activated by clicking.
110
def event_after(self, text_view, event):
111
if event.type != gtk.gdk.BUTTON_RELEASE:
113
if event.button != 1:
115
buffer = text_view.get_buffer()
117
# we shouldn't follow a link if the user has selected something
119
start, end = buffer.get_selection_bounds()
121
# If there is nothing selected, None is return
124
if start.get_offset() != end.get_offset():
127
x, y = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
128
int(event.x), int(event.y))
129
iter = text_view.get_iter_at_location(x, y)
130
self.follow_if_link(text_view, iter)
133
# Looks at all tags covering the position (x, y) in the text view,
134
# and if one of them is a link, change the cursor to the "hands" cursor
135
# typically used by web browsers.
136
def set_cursor_if_appropriate(self, text_view, x, y):
138
buffer = text_view.get_buffer()
139
iter = text_view.get_iter_at_location(x, y)
140
tags = iter.get_tags()
143
href = tag.get_data("href")
147
if hovering != self.hovering_over_link:
148
self.hovering_over_link = hovering
150
if self.hovering_over_link:
151
text_view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(self.hand_cursor)
153
text_view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(self.regular_cursor)
155
# Update the cursor image if the pointer moved.
156
def motion_notify_event(self, text_view, event):
157
x, y = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
158
int(event.x), int(event.y))
159
self.set_cursor_if_appropriate(text_view, x, y)
160
text_view.window.get_pointer()
163
# Also update the cursor image if the window becomes visible
164
# (e.g. when a window covering it got iconified).
165
def visibility_notify_event(self, text_view, event):
166
wx, wy, mod = text_view.window.get_pointer()
167
bx, by = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy)
169
self.set_cursor_if_appropriate (text_view, bx, by)
172
def follow_if_link (self, text_view, iter):
173
''' Looks at all tags covering the position of iter in the text view,
174
and if one of them is a link, follow it by showing the page identified
175
by the data attached to it.
177
tags = iter.get_tags()
179
href = tag.get_data('href')
181
self.emit('link-activated',href)
184
if gtk.pygtk_version[1] < 8:
185
gobject.type_register(LinkedTextView)
187
if __name__ == '__main__':
188
def print_link (tv,l):
190
tv = LinkedTextView()
191
tv.connect('link-activated',print_link)
194
tv.get_buffer().set_text(u"""This is some text
195
Some <i>fancy</i>, <u>fancy</u>, text.
196
This is <a href="foo">a link</a>, a
197
<a href="fancy_desc">fancy, fancy</a> link.
199
<a href="123:foo">recipe link</a>
201
<a href="456:boo">\xbc recipe boogoochooboo</a>
207
w.connect('delete-event',lambda *args: gtk.main_quit())