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