1#!/usr/bin/env python 2'''Text Widget/Hypertext 3 4Usually, tags modify the appearance of text in the view, e.g. making it 5bold or colored or underlined. But tags are not restricted to appearance. 6They can also affect the behavior of mouse and key presses, as this demo 7shows.''' 8# pygtk version: Maik Hertha <maik.hertha@berlin.de> 9 10import pygtk 11pygtk.require('2.0') 12import gtk 13import pango 14 15class HypertextDemo(gtk.Window): 16 hovering_over_link = False 17 hand_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2) 18 regular_cursor = gtk.gdk.Cursor(gtk.gdk.XTERM) 19 20 def __init__(self, parent=None): 21 gtk.Window.__init__(self) 22 try: 23 self.set_screen(parent.get_screen()) 24 except AttributeError: 25 self.connect('destroy', lambda *w: gtk.main_quit()) 26 27 self.set_title(self.__class__.__name__) 28 self.set_default_size(450, 450) 29 self.set_border_width(0) 30 31 view = gtk.TextView() 32 view.set_wrap_mode(gtk.WRAP_WORD) 33 view.connect("key-press-event", self.key_press_event) 34 view.connect("event-after", self.event_after) 35 view.connect("motion-notify-event", self.motion_notify_event) 36 view.connect("visibility-notify-event", self.visibility_notify_event) 37 38 buffer = view.get_buffer() 39 40 sw = gtk.ScrolledWindow() 41 sw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 42 self.add(sw) 43 sw.add(view) 44 45 self.show_page(buffer, 1) 46 47 self.show_all() 48 49 # Links can be activated by pressing Enter. 50 def key_press_event(self, text_view, event): 51 if (event.keyval == gtk.keysyms.Return or 52 event.keyval == gtk.keysyms.KP_Enter): 53 buffer = text_view.get_buffer() 54 iter = buffer.get_iter_at_mark(buffer.get_insert()) 55 self.follow_if_link(text_view, iter) 56 return False 57 58 # Links can also be activated by clicking. 59 def event_after(self, text_view, event): 60 if event.type != gtk.gdk.BUTTON_RELEASE: 61 return False 62 if event.button != 1: 63 return False 64 buffer = text_view.get_buffer() 65 66 # we shouldn't follow a link if the user has selected something 67 try: 68 start, end = buffer.get_selection_bounds() 69 except ValueError: 70 # If there is nothing selected, None is return 71 pass 72 else: 73 if start.get_offset() != end.get_offset(): 74 return False 75 76 x, y = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, 77 int(event.x), int(event.y)) 78 iter = text_view.get_iter_at_location(x, y) 79 80 self.follow_if_link(text_view, iter) 81 return False 82 83 84 # Looks at all tags covering the position (x, y) in the text view, 85 # and if one of them is a link, change the cursor to the "hands" cursor 86 # typically used by web browsers. 87 def set_cursor_if_appropriate(self, text_view, x, y): 88 hovering = False 89 90 buffer = text_view.get_buffer() 91 iter = text_view.get_iter_at_location(x, y) 92 93 tags = iter.get_tags() 94 for tag in tags: 95 page = tag.get_data("page") 96 if page != 0: 97 hovering = True 98 break 99 100 if hovering != self.hovering_over_link: 101 self.hovering_over_link = hovering 102 103 if self.hovering_over_link: 104 text_view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(self.hand_cursor) 105 else: 106 text_view.get_window(gtk.TEXT_WINDOW_TEXT).set_cursor(self.regular_cursor) 107 108 # Update the cursor image if the pointer moved. 109 def motion_notify_event(self, text_view, event): 110 x, y = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, 111 int(event.x), int(event.y)) 112 self.set_cursor_if_appropriate(text_view, x, y) 113 text_view.window.get_pointer() 114 return False 115 116 # Also update the cursor image if the window becomes visible 117 # (e.g. when a window covering it got iconified). 118 def visibility_notify_event(self, text_view, event): 119 wx, wy, mod = text_view.window.get_pointer() 120 bx, by = text_view.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET, wx, wy) 121 122 self.set_cursor_if_appropriate (text_view, bx, by) 123 return False 124 125 def insert_link(self, buffer, iter, text, page): 126 ''' Inserts a piece of text into the buffer, giving it the usual 127 appearance of a hyperlink in a web browser: blue and underlined. 128 Additionally, attaches some data on the tag, to make it recognizable 129 as a link. 130 ''' 131 tag = buffer.create_tag(None, 132 foreground="blue", underline=pango.UNDERLINE_SINGLE) 133 tag.set_data("page", page) 134 buffer.insert_with_tags(iter, text, tag) 135 136 137 def show_page(self, buffer, page): 138 ''' Fills the buffer with text and interspersed links. In any real 139 hypertext app, this method would parse a file to identify the links. 140 ''' 141 buffer.set_text("", 0) 142 iter = buffer.get_iter_at_offset(0) 143 if page == 1: 144 buffer.insert(iter, "Some text to show that simple ") 145 self.insert_link(buffer, iter, "hypertext", 3) 146 buffer.insert(iter, " can easily be realized with ") 147 self.insert_link(buffer, iter, "tags", 2) 148 buffer.insert(iter, ".") 149 150 elif page == 2: 151 buffer.insert(iter, 152 "A tag is an attribute that can be applied to some range of text. " 153 "For example, a tag might be called \"bold\" and make the text inside " 154 "the tag bold. However, the tag concept is more general than that " 155 "tags don't have to affect appearance. They can instead affect the " 156 "behavior of mouse and key presses, \"lock\" a range of text so the " 157 "user can't edit it, or countless other things.\n", -1) 158 self.insert_link(buffer, iter, "Go back", 1) 159 elif page == 3: 160 tag = buffer.create_tag(None, weight=pango.WEIGHT_BOLD) 161 buffer.insert_with_tags(iter, "hypertext:\n", tag) 162 buffer.insert(iter, 163 "machine-readable text that is not sequential but is organized " 164 "so that related items of information are connected.\n") 165 self.insert_link(buffer, iter, "Go back", 1) 166 167 168 def follow_if_link(self, text_view, iter): 169 ''' Looks at all tags covering the position of iter in the text view, 170 and if one of them is a link, follow it by showing the page identified 171 by the data attached to it. 172 ''' 173 tags = iter.get_tags() 174 for tag in tags: 175 page = tag.get_data("page") 176 if page != 0: 177 self.show_page(text_view.get_buffer(), page) 178 break 179 180 181def main(): 182 HypertextDemo() 183 gtk.main() 184 185if __name__ == '__main__': 186 main() 187