1#!/usr/local/bin/python-legacy 2''' 3 $Id$ 4 5 pygtk-demo.py 6 2004-07-18: Some enhancements for building the demolist like in gtk-demos of the 7 gtk+ distribution. 8 2004-07-22: Simple syntaxhighlighting implemented, based on the tokenizer-module. 9''' 10 11import string 12import re 13import pygtk 14pygtk.require('2.0') 15import gobject 16import gtk 17import pango 18 19# use for simple syntax highlighting ;-) 20import tokenize 21import keyword 22 23import demos 24D_TEMPL = '%sDemo' 25 26# Some programmatic definition for the testgtk_demos list. This avoids extra 27# maintenance if the demo list grows up. The current definition requires 28# a class or function with a swapped case name+'Demo' like in the doc string. 29# Swapped case is build from the __doc__-string programatically. 30child_demos = {} 31testgtk_demos = [] 32for descr, mod in demos.demo_list: 33 # Find some categorized demos 34 try: 35 main, child = descr.split('/') 36 except ValueError: 37 # No, only one application 38 demo_class = D_TEMPL % re.sub('(\S+) *', 39 lambda m:(m.group(1)[0].isupper() and m.group(1) or m.group(1).capitalize()), 40 descr) 41 testgtk_demos.append((descr, mod, demo_class)) 42 else: 43 # Ok. Some more testing 44 demo_class = D_TEMPL % re.sub('(\S+) *', 45 lambda m:(m.group(1)[0].isupper() and m.group(1) or m.group(1).capitalize()), 46 child) 47 try: 48 # Applicationgroup already defined? 49 child_demos[main.upper()].append((child, mod, demo_class)) 50 except KeyError: 51 # No. Start a new category 52 child_demos.setdefault(main.upper(), []).append((child, mod, demo_class)) 53 testgtk_demos.append((main, None, None, child_demos[main.upper()])) 54 55( 56 TITLE_COLUMN, 57 MODULE_COLUMN, 58 FUNC_COLUMN, 59 ITALIC_COLUMN 60) = range(4) 61 62CHILDREN_COLUMN = 3 63 64class InputStream(object): 65 ''' Simple Wrapper for File-like objects. [c]StringIO doesn't provide 66 a readline function for use with generate_tokens. 67 Using a iterator-like interface doesn't succeed, because the readline 68 function isn't used in such a context. (see <python-lib>/tokenize.py) 69 ''' 70 def __init__(self, data): 71 self.__data = [ '%s\n' % x for x in data.splitlines() ] 72 self.__lcount = 0 73 def readline(self): 74 try: 75 line = self.__data[self.__lcount] 76 self.__lcount += 1 77 except IndexError: 78 line = '' 79 self.__lcount = 0 80 return line 81 82 83class PyGtkDemo(gtk.Window): 84 info_buffer = None 85 source_buffer = None 86 module_cache = {} 87 88 def __init__(self): 89 gtk.Window.__init__(self) 90 self.set_title("PyGTK Code Demos") 91 self.connect('destroy', lambda w: gtk.main_quit()) 92 self.set_default_size(800, 400) 93 94 hbox = gtk.HBox(False, 3) 95 self.add(hbox) 96 97 treeview = self.__create_treeview() 98 hbox.pack_start(treeview, False, False) 99 100 self.notebook = gtk.Notebook() 101 hbox.pack_start(self.notebook, expand=True) 102 103 scrolled_window, self.info_buffer = self.__create_text(False) 104 self._new_notebook_page(scrolled_window, '_Info') 105 tag = self.info_buffer.create_tag('title') 106 tag.set_property('font', 'Sans 18') 107 108 scrolled_window, self.source_buffer = self.__create_text(True) 109 self._new_notebook_page(scrolled_window, '_Source') 110 tag = self.source_buffer.create_tag('source') 111 tag.set_property('font', 'monospace') 112 tag.set_property('pixels_above_lines', 0) 113 tag.set_property('pixels_below_lines', 0) 114 tag = self.source_buffer.create_tag('keyword', foreground='#00007F', 115 weight=pango.WEIGHT_BOLD) 116 tag = self.source_buffer.create_tag('string', foreground='#7F007F') 117 tag = self.source_buffer.create_tag('comment', foreground='#007F00', 118 style=pango.STYLE_ITALIC) 119 120 self.show_all() 121 122 def run(self): 123 gtk.main() 124 125 def _new_notebook_page(self, widget, label): 126 l = gtk.Label('') 127 l.set_text_with_mnemonic(label) 128 self.notebook.append_page(widget, l) 129 130 def __create_treeview(self): 131 model = gtk.TreeStore( 132 gobject.TYPE_STRING, 133 gobject.TYPE_STRING, 134 gobject.TYPE_STRING, 135 gobject.TYPE_BOOLEAN 136 ) 137 138 treeview = gtk.TreeView(model) 139 selection = treeview.get_selection() 140 selection.set_mode(gtk.SELECTION_BROWSE) 141 treeview.set_size_request(200, -1) 142 143 for module in testgtk_demos: 144 iter = model.append(None) 145 model.set(iter, 146 TITLE_COLUMN, module[TITLE_COLUMN], 147 MODULE_COLUMN, module[MODULE_COLUMN], 148 FUNC_COLUMN, module[FUNC_COLUMN], 149 ITALIC_COLUMN, False 150 ) 151 152 try: 153 children = module[CHILDREN_COLUMN] 154 for child_module in children: 155 child_iter = model.append(iter) 156 model.set(child_iter, 157 TITLE_COLUMN, child_module[TITLE_COLUMN], 158 MODULE_COLUMN, child_module[MODULE_COLUMN], 159 FUNC_COLUMN, child_module[FUNC_COLUMN], 160 ITALIC_COLUMN, False 161 ) 162 except IndexError: 163 pass 164 165 cell = gtk.CellRendererText() 166 cell.set_property('style', pango.STYLE_ITALIC) 167 168 column = gtk.TreeViewColumn("Widget (double click for demo)", cell, 169 text=TITLE_COLUMN, style_set=ITALIC_COLUMN) 170 171 treeview.append_column(column) 172 173 selection.connect('changed', self.selection_changed_cb) 174 treeview.connect('row-activated', self.row_activated_cb) 175 176 treeview.expand_all() 177 178 return treeview 179 180 def __create_text(self, is_source=False): 181 scrolled_window = gtk.ScrolledWindow() 182 scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) 183 scrolled_window.set_shadow_type(gtk.SHADOW_IN) 184 185 text_view = gtk.TextView() 186 scrolled_window.add(text_view) 187 188 buffer = gtk.TextBuffer(None) 189 text_view.set_buffer(buffer) 190 text_view.set_editable(False) 191 text_view.set_cursor_visible(False) 192 193 text_view.set_wrap_mode(not is_source) 194 195 return scrolled_window, buffer 196 197 def row_activated_cb(self, treeview, path, column): 198 model = treeview.get_model() 199 iter = model.get_iter(path) 200 module_name = model.get_value(iter, MODULE_COLUMN) 201 func_name = model.get_value(iter, FUNC_COLUMN) 202 italic_value = model.get_value(iter, ITALIC_COLUMN) 203 if module_name is None: # a "category" row is activated 204 return True 205 try: 206 self.module_cache[module_name].present() 207 except KeyError: 208 module = getattr(demos, module_name) 209 model.set(iter, ITALIC_COLUMN, not italic_value) 210 cmd = 'demos.%s.%s' % (module_name, func_name) 211 #print cmd 212 window = eval(cmd)(self) 213 if window: 214 window.connect('destroy', self.window_closed_cb, model, path) 215 self.module_cache[module_name] = window 216 217 def selection_changed_cb(self, selection): 218 model, iter = selection.get_selected() 219 if not iter: 220 return False 221 222 name = model.get_value(iter, MODULE_COLUMN) 223 if name is not None: 224 self.load_module(name) 225 226 def window_closed_cb (self, window, model, path): 227 iter = model.get_iter(path) 228 module_name = model.get_value(iter, MODULE_COLUMN) 229 del self.module_cache[module_name] 230 italic_value = model.get_value(iter, ITALIC_COLUMN) 231 if italic_value: 232 model.set(iter, ITALIC_COLUMN, not italic_value) 233 234 235 def read_module(self, module): 236 filename = module.__file__ 237 if filename[-4:] == '.pyc': 238 filename = filename[:-1] 239 fd = open(filename) 240 return fd.read() 241 242 def insert_documentation(self, module): 243 buffer = self.info_buffer 244 iter = buffer.get_iter_at_offset(0) 245 246 lines = string.split(module.__doc__ or '', '\n') 247 buffer.insert(iter, lines[0]) 248 start = buffer.get_iter_at_offset(0) 249 buffer.apply_tag_by_name('title', start, iter) 250 buffer.insert(iter, '\n') 251 for line in lines[1:]: 252 buffer.insert(iter, line) 253 buffer.insert(iter, '\n') 254 255 def clear_buffers(self): 256 start, end = self.info_buffer.get_bounds() 257 self.info_buffer.delete(start, end) 258 259 start, end = self.source_buffer.get_bounds() 260 self.source_buffer.delete(start, end) 261 262 def insert_source(self, data): 263 source_buffer = self.source_buffer 264 iter = source_buffer.get_iter_at_offset(0) 265 266 last_erow, last_ecol = 0, 0 267 was_newline = False # multiline statement detection 268 for x in tokenize.generate_tokens(InputStream(data).readline): 269 # x has 5-tuples 270 tok_type, tok_str = x[0], x[1] 271 srow, scol = x[2] 272 erow, ecol = x[3] 273 274 # The tokenizer 'eats' the whitespaces, so we have to insert this again 275 # if needed. 276 if srow == last_erow: 277 # Same line, spaces between statements 278 if scol != last_ecol: 279 source_buffer.insert_with_tags_by_name(iter, ' '*(scol-last_ecol), 'source') 280 else: 281 # New line. 282 # First: Detect multiline statements. There is no special in the tokenizer stream. 283 if was_newline is False and last_erow != 0: 284 source_buffer.insert_with_tags_by_name(iter, ' \\\n', 'source') 285 # new line check if it starts with col 0 286 if scol != 0: 287 source_buffer.insert_with_tags_by_name(iter, ' '*scol, 'source') 288 last_erow = erow 289 last_ecol = ecol 290 291 if tok_type == tokenize.COMMENT: 292 was_newline = True # newline is in tok_str included. 293 source_buffer.insert_with_tags_by_name(iter, tok_str, 'source', 'comment') 294 continue 295 elif tok_type == tokenize.NAME: 296 if tok_str in keyword.kwlist: 297 source_buffer.insert_with_tags_by_name(iter, tok_str, 'source', 'keyword') 298 continue 299 elif tok_type == tokenize.STRING: 300 source_buffer.insert_with_tags_by_name(iter, tok_str, 'source', 'string') 301 continue 302 303 # No special format for use. Check for newline. 304 was_newline = tok_type in (tokenize.NEWLINE, tokenize.NL) 305 source_buffer.insert_with_tags_by_name(iter, tok_str, 'source') 306 307 def load_module(self, name): 308 self.clear_buffers() 309 module = getattr(demos, name) 310 if module.__doc__: 311 self.insert_documentation(module) 312 313 source = self.read_module(module) 314 self.insert_source(source) 315 316if __name__ == '__main__': 317 print "PyGTK Demo", 318 print "(gtk: v%d.%d.%d, " % gtk.gtk_version, 319 print "pygtk: v%d.%d.%d)" % gtk.pygtk_version 320 PyGtkDemo().run() 321