~marco-crosio/pyroom/austosave

« back to all changes in this revision

Viewing changes to pyroom.py

  • Committer: Bruno Bord
  • Date: 2008-02-03 15:37:18 UTC
  • Revision ID: bruno@la-chose-20080203153718-y59c41m8ci42axyo
Big commit changes - update from my Mercurial rep, in fact.
* Code is now more PEP008-compliant
* It's internationalised
* French translation available.
* writeroom has been renamed as pyroom
* Two more functions: Ctrl/+ and Ctrl/- allow the user to increase/
  decrease the font size.

A lot ahead.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/python
 
2
# -*- coding: utf-8 -*-
 
3
# ------------------------------------------------------------------------------
 
4
# PyRoom - A clone of WriteRoom
 
5
# Copyright (c) 2007 Nicolas P. Rougier & NoWhereMan
 
6
# Copyright (c) 2008 Bruno Bord
 
7
#
 
8
# This program is free software: you can redistribute it and/or modify it under
 
9
# the terms of the GNU General Public License as published by the Free Software
 
10
# Foundation, either version 3 of the License, or (at your option) any later
 
11
# version.
 
12
#
 
13
# This program is distributed in the hope that it will be useful, but WITHOUT
 
14
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 
15
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
 
16
#
 
17
# You should have received a copy of the GNU General Public License along with
 
18
# this program.  If not, see <http://www.gnu.org/licenses/>.
 
19
# ------------------------------------------------------------------------------
 
20
#
 
21
# Based on code posted on ubuntu forums by NoWhereMan (www.nowhereland.it)
 
22
#  (Ubuntu thread was "WriteRoom/Darkroom/?")
 
23
#
 
24
# ------------------------------------------------------------------------------
 
25
 
 
26
import traceback
 
27
import sys
 
28
import os.path
 
29
import gobject
 
30
import gtk
 
31
import pango
 
32
import gtksourceview
 
33
import gettext
 
34
 
 
35
gettext.install('pyroom', 'locale')
 
36
 
 
37
# Some styles
 
38
 
 
39
styles = {'darkgreen': {
 
40
    'name': 'darkgreen',
 
41
    'background': '#000000',
 
42
    'foreground': '#007700',
 
43
    'lines': '#001100',
 
44
    'border': '#001100',
 
45
    'info': '#007700',
 
46
    'font': 'Droid Sans Mono',
 
47
    'fontsize': 12,
 
48
    'padding': 6,
 
49
    'size': [0.6, 0.95],
 
50
    }, 'green': {
 
51
    'name': 'green',
 
52
    'background': '#000000',
 
53
    'foreground': '#00ff00',
 
54
    'lines': '#007700',
 
55
    'border': '#003300',
 
56
    'info': '#00ff00',
 
57
    'font': 'Droid Sans Mono',
 
58
    'fontsize': 12,
 
59
    'padding': 6,
 
60
    'size': [0.6, 0.95],
 
61
    }, 'blue': {
 
62
    'name': 'blue',
 
63
    'background': '#0000ff',
 
64
    'foreground': '#ffffff',
 
65
    'lines': '#5555ff',
 
66
    'border': '#3333ff',
 
67
    'info': '#ffffff',
 
68
    'font': 'Droid Sans Mono',
 
69
    'fontsize': 12,
 
70
    'padding': 6,
 
71
    'size': [0.6, 0.95],
 
72
    }}
 
73
 
 
74
FILE_UNNAMED = _('* Unnamed *')
 
75
 
 
76
HELP = \
 
77
    _("""PyRoom - an adaptation of write room
 
78
Copyright (c) 2007 Nicolas Rougier, NoWhereMan
 
79
Copyright (c) 2008 Bruno Bord
 
80
 
 
81
This program is free software: you can redistribute it and/or modify it under
 
82
the terms of the GNU General Public License as published by the Free Software
 
83
Foundation, either version 3 of the License, or (at your option) any later
 
84
version.
 
85
 
 
86
Usage:
 
87
------
 
88
 
 
89
pyroom.py [-style] file1 file2 ...
 
90
style can be either 'blue', 'green', 'darkgreen'
 
91
 
 
92
 
 
93
Commands:
 
94
---------
 
95
Control-H: Show help in a new buffer
 
96
Control-I: Show buffer information
 
97
Control-L: Toggle line number
 
98
Control-N: Create a new buffer
 
99
Control-O: Open a file in a new buffer
 
100
Control-Q: Quit
 
101
Control-S: Save current buffer
 
102
Control-Shift-S: Save current buffer as
 
103
Control-W: Close buffer and exit if it was the last buffer
 
104
Control-Y: Redo last typing
 
105
Control-Z: Undo last typing
 
106
Control-Left: Switch to previous buffer
 
107
Control-Right: Switch to next buffer
 
108
Control-Plus: Increases font size
 
109
Control-Minus: Decreases font size
 
110
 
 
111
Warnings:
 
112
---------
 
113
No autosave.
 
114
No question whether to close a modified buffer or not
 
115
""")
 
116
 
 
117
 
 
118
class FadeLabel(gtk.Label):
 
119
    """ GTK Label with timed fade out effect """
 
120
 
 
121
    active_duration = 3000  # Fade start after this time
 
122
    fade_duration = 1500  # Fade duration
 
123
 
 
124
    def __init__(self, message='', active_color=None, inactive_color=None):
 
125
        gtk.Label.__init__(self, message)
 
126
        if not active_color:
 
127
            active_color = '#ffffff'
 
128
        self.active_color = active_color
 
129
        if not inactive_color:
 
130
            inactive_color = '#000000'
 
131
        self.inactive_color = inactive_color
 
132
        self.idle = 0
 
133
 
 
134
    def set_text(self, message, duration=None):
 
135
        if not duration:
 
136
            duration = self.active_duration
 
137
        self.modify_fg(gtk.STATE_NORMAL,
 
138
                       gtk.gdk.color_parse(self.active_color))
 
139
        gtk.Label.set_text(self, message)
 
140
        if self.idle:
 
141
            gobject.source_remove(self.idle)
 
142
        self.idle = gobject.timeout_add(duration, self.fade_start)
 
143
 
 
144
    def fade_start(self):
 
145
        self.fade_level = 1.0
 
146
        if self.idle:
 
147
            gobject.source_remove(self.idle)
 
148
        self.idle = gobject.timeout_add(25, self.fade_out)
 
149
 
 
150
    def fade_out(self):
 
151
        color = gtk.gdk.color_parse(self.inactive_color)
 
152
        (red1, green1, blue1) = (color.red, color.green, color.blue)
 
153
        color = gtk.gdk.color_parse(self.active_color)
 
154
        (red2, green2, blue2) = (color.red, color.green, color.blue)
 
155
        red = red1 + int(self.fade_level * abs(red1 - red2))
 
156
        green = green1 + int(self.fade_level * abs(green1 - green2))
 
157
        blue = blue1 + int(self.fade_level * abs(blue1 - blue2))
 
158
        self.modify_fg(gtk.STATE_NORMAL, gtk.gdk.Color(red, green, blue))
 
159
        self.fade_level -= 1.0 / (self.fade_duration / 25)
 
160
        if self.fade_level > 0:
 
161
            return True
 
162
        self.idle = 0
 
163
        return False
 
164
 
 
165
 
 
166
class PyRoom:
 
167
    """ The PyRoom class"""
 
168
 
 
169
    buffers = []
 
170
    current = 0
 
171
 
 
172
    def __init__(self, style):
 
173
 
 
174
        self.style = style
 
175
 
 
176
        # Main window
 
177
 
 
178
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
 
179
        self.window.set_name('PyRoom')
 
180
        self.window.fullscreen()
 
181
        self.window.connect('delete_event', self.delete_event)
 
182
        self.window.connect('destroy', self.destroy)
 
183
 
 
184
        self.textbox = gtksourceview.SourceView()
 
185
        self.new_buffer()
 
186
        self.textbox.connect('scroll-event', self.scroll_event)
 
187
        self.textbox.connect('key-press-event', self.key_press_event)
 
188
        self.textbox.set_wrap_mode(gtk.WRAP_WORD)
 
189
 
 
190
        self.fixed = gtk.Fixed()
 
191
        self.vbox = gtk.VBox()
 
192
        self.window.add(self.fixed)
 
193
        self.fixed.put(self.vbox, 0, 0)
 
194
 
 
195
        self.boxout = gtk.EventBox()
 
196
        self.boxout.set_border_width(1)
 
197
        self.boxin = gtk.EventBox()
 
198
        self.boxin.set_border_width(1)
 
199
        self.vbox.pack_start(self.boxout, True, True, 6)
 
200
        self.boxout.add(self.boxin)
 
201
 
 
202
        self.scrolled = gtk.ScrolledWindow()
 
203
        self.boxin.add(self.scrolled)
 
204
        self.scrolled.add(self.textbox)
 
205
        self.scrolled.set_policy(gtk.POLICY_NEVER, gtk.POLICY_NEVER)
 
206
        self.scrolled.show()
 
207
        self.scrolled.set_property('resize-mode', gtk.RESIZE_PARENT)
 
208
        self.textbox.set_property('resize-mode', gtk.RESIZE_PARENT)
 
209
        self.vbox.set_property('resize-mode', gtk.RESIZE_PARENT)
 
210
        self.vbox.show_all()
 
211
 
 
212
        # Status
 
213
 
 
214
        self.status = FadeLabel()
 
215
        self.hbox = gtk.HBox()
 
216
        self.hbox.set_spacing(12)
 
217
        self.hbox.pack_end(self.status, True, True, 0)
 
218
        self.vbox.pack_end(self.hbox, False, False, 0)
 
219
        self.status.set_alignment(0.0, 0.5)
 
220
        self.status.set_justify(gtk.JUSTIFY_LEFT)
 
221
        self.apply_style()
 
222
        self.window.show_all()
 
223
        self.status.set_text(
 
224
            _('Welcome to PyRoom 1.0, type Control-H for help'))
 
225
 
 
226
    def delete_event(self, widget, event, data=None):
 
227
        """ Quit """
 
228
        return False
 
229
 
 
230
    def destroy(self, widget, data=None):
 
231
        """ Quit """
 
232
        gtk.main_quit()
 
233
 
 
234
    def key_press_event(self, widget, event):
 
235
        """ key press event dispatcher """
 
236
 
 
237
        bindings = {
 
238
            gtk.keysyms.Left: self.prev_buffer,
 
239
            gtk.keysyms.Right: self.next_buffer,
 
240
            gtk.keysyms.h: self.show_help,
 
241
            gtk.keysyms.i: self.show_info,
 
242
            gtk.keysyms.l: self.toggle_lines,
 
243
            gtk.keysyms.n: self.new_buffer,
 
244
            gtk.keysyms.o: self.open_file,
 
245
            gtk.keysyms.q: self.quit,
 
246
            gtk.keysyms.s: self.save_file,
 
247
            gtk.keysyms.w: self.close_buffer,
 
248
            gtk.keysyms.y: self.redo,
 
249
            gtk.keysyms.z: self.undo,
 
250
            gtk.keysyms.plus: self.plus,
 
251
            gtk.keysyms.minus: self.minus,
 
252
            }
 
253
        if event.state & gtk.gdk.CONTROL_MASK:
 
254
 
 
255
            # Special case for Control-Shift-s
 
256
 
 
257
            if event.state & gtk.gdk.SHIFT_MASK:
 
258
                print event.keyval
 
259
            if event.state & gtk.gdk.SHIFT_MASK and event.keyval\
 
260
                 == gtk.keysyms.S:
 
261
                self.save_file_as()
 
262
                return True
 
263
            if bindings.has_key(event.keyval):
 
264
                bindings[event.keyval]()
 
265
                return True
 
266
        return False
 
267
 
 
268
    def scroll_event(self, widget, event):
 
269
        """\" Scroll event dispatcher """
 
270
 
 
271
        if event.direction == gtk.gdk.SCROLL_UP:
 
272
            self.scroll_up()
 
273
        elif event.direction == gtk.gdk.SCROLL_DOWN:
 
274
            self.scroll_down()
 
275
 
 
276
    def show_help(self):
 
277
        """ Create a new buffer and inserts help """
 
278
 
 
279
        buffer = self.new_buffer()
 
280
        buffer.begin_not_undoable_action()
 
281
        buffer.set_text(HELP)
 
282
        buffer.end_not_undoable_action()
 
283
 
 
284
    def new_buffer(self):
 
285
        """ Create a new buffer """
 
286
 
 
287
        buffer = gtksourceview.SourceBuffer()
 
288
        buffer.set_check_brackets(False)
 
289
        buffer.set_highlight(False)
 
290
        buffer.filename = FILE_UNNAMED
 
291
        self.buffers.insert(self.current + 1, buffer)
 
292
        buffer.place_cursor(buffer.get_start_iter())
 
293
        self.next_buffer()
 
294
        return buffer
 
295
 
 
296
    def close_buffer(self):
 
297
        """ Close current buffer """
 
298
 
 
299
        if len(self.buffers) > 1:
 
300
            self.buffers.pop(self.current)
 
301
            self.current = min(len(self.buffers) - 1, self.current)
 
302
            self.set_buffer(self.current)
 
303
        else:
 
304
            quit()
 
305
 
 
306
    def set_buffer(self, index):
 
307
        """ Set current buffer """
 
308
 
 
309
        if index >= 0 and index < len(self.buffers):
 
310
            self.current = index
 
311
            buffer = self.buffers[index]
 
312
            self.textbox.set_buffer(buffer)
 
313
            if hasattr(self, 'status'):
 
314
                self.status.set_text(
 
315
                    _('Switching to buffer %(buffer_id)d (%(buffer_name)s)'
 
316
                    % {'buffer_id': self.current + 1, 'buffer_name'
 
317
                    : buffer.filename}))
 
318
 
 
319
    def next_buffer(self):
 
320
        """ Switch to next buffer """
 
321
 
 
322
        if self.current < len(self.buffers) - 1:
 
323
            self.current += 1
 
324
        else:
 
325
            self.current = 0
 
326
        self.set_buffer(self.current)
 
327
 
 
328
    def prev_buffer(self):
 
329
        """ Switch to prev buffer """
 
330
 
 
331
        if self.current > 0:
 
332
            self.current -= 1
 
333
        else:
 
334
            self.current = len(self.buffers) - 1
 
335
        self.set_buffer(self.current)
 
336
 
 
337
    def apply_style(self, style=None):
 
338
        """ """
 
339
 
 
340
        if style:
 
341
            self.style = style
 
342
        self.window.modify_bg(gtk.STATE_NORMAL,
 
343
                              gtk.gdk.color_parse(self.style['background'
 
344
                              ]))
 
345
        self.textbox.modify_bg(gtk.STATE_NORMAL,
 
346
                               gtk.gdk.color_parse(self.style['background'
 
347
                               ]))
 
348
        self.textbox.modify_base(gtk.STATE_NORMAL,
 
349
                                 gtk.gdk.color_parse(self.style['background'
 
350
                                 ]))
 
351
        self.textbox.modify_text(gtk.STATE_NORMAL,
 
352
                                 gtk.gdk.color_parse(self.style['foreground'
 
353
                                 ]))
 
354
        self.textbox.modify_fg(gtk.STATE_NORMAL,
 
355
                               gtk.gdk.color_parse(self.style['lines']))
 
356
        self.status.active_color = self.style['foreground']
 
357
        self.status.inactive_color = self.style['background']
 
358
        self.boxout.modify_bg(gtk.STATE_NORMAL,
 
359
                              gtk.gdk.color_parse(self.style['border']))
 
360
        font_and_size = '%s %d' % (self.style['font'],
 
361
                                   self.style['fontsize'])
 
362
        self.textbox.modify_font(pango.FontDescription(font_and_size))
 
363
 
 
364
        gtk.rc_parse_string("""
 
365
            style "pyroom-colored-cursor" {
 
366
        GtkTextView::cursor-color = '"""
 
367
                             + self.style['foreground']
 
368
                             + """'
 
369
        }
 
370
        class "GtkWidget" style "pyroom-colored-cursor"
 
371
            """)
 
372
        (w, h) = (gtk.gdk.screen_width(), gtk.gdk.screen_height())
 
373
        width = int(self.style['size'][0] * w)
 
374
        height = int(self.style['size'][1] * h)
 
375
        self.vbox.set_size_request(width, height)
 
376
        self.fixed.move(self.vbox, int(((1 - self.style['size'][0]) * w)/ 2),
 
377
            int(((1 - self.style['size'][1]) * h) / 2))
 
378
        self.textbox.set_border_width(self.style['padding'])
 
379
 
 
380
    def word_count(self, buffer):
 
381
        """ Word count in a text buffer """
 
382
 
 
383
        iter1 = buffer.get_start_iter()
 
384
        iter2 = iter1.copy()
 
385
        iter2.forward_word_end()
 
386
        count = 0
 
387
        while iter2.get_offset() != iter1.get_offset():
 
388
            count += 1
 
389
            iter1 = iter2.copy()
 
390
            iter2.forward_word_end()
 
391
        return count
 
392
 
 
393
    def quit(self):
 
394
        """ quit pyroom """
 
395
 
 
396
        gtk.main_quit()
 
397
 
 
398
    def show_info(self):
 
399
        """ Display buffer information on status label for 5 seconds """
 
400
 
 
401
        buffer = self.buffers[self.current]
 
402
        if buffer.can_undo() or buffer.can_redo():
 
403
            status = _(' (modified)')
 
404
        else:
 
405
            status = ''
 
406
        self.status.set_text(_('Buffer %(buffer_id)d: %(buffer_name)s%(status)s, %(char_count)d byte(s), %(word_count)d word(s), %(lines)d line(s)') % {
 
407
            'buffer_id': self.current + 1,
 
408
            'buffer_name': buffer.filename,
 
409
            'status': status,
 
410
            'char_count': buffer.get_char_count(),
 
411
            'word_count': self.word_count(buffer),
 
412
            'lines': buffer.get_line_count(),
 
413
            }, 5000)
 
414
 
 
415
    def scroll_down(self):
 
416
        """ Scroll window down """
 
417
 
 
418
        adj = self.scrolled.get_vadjustment()
 
419
        if adj.upper > adj.page_size:
 
420
            adj.value = min(adj.upper - adj.page_size, adj.value
 
421
                             + adj.step_increment)
 
422
 
 
423
    def scroll_up(self):
 
424
        """ Scroll window up """
 
425
 
 
426
        adj = self.scrolled.get_vadjustment()
 
427
        if adj.value > adj.step_increment:
 
428
            adj.value -= adj.step_increment
 
429
        else:
 
430
            adj.value = 0
 
431
 
 
432
    def undo(self):
 
433
        """ Undo last typing """
 
434
 
 
435
        buffer = self.textbox.get_buffer()
 
436
        if buffer.can_undo():
 
437
            buffer.undo()
 
438
        else:
 
439
            self.status.set_text(_('No more undo!'))
 
440
 
 
441
    def redo(self):
 
442
        """ Redo last typing """
 
443
 
 
444
        buffer = self.textbox.get_buffer()
 
445
        if buffer.can_redo():
 
446
            buffer.redo()
 
447
        else:
 
448
            self.status.set_text(_('No more redo!'))
 
449
 
 
450
    def toggle_lines(self):
 
451
        """ Toggle lines number """
 
452
 
 
453
        b = not self.textbox.get_show_line_numbers()
 
454
        self.textbox.set_show_line_numbers(b)
 
455
 
 
456
    def open_file(self):
 
457
        """ Open file """
 
458
 
 
459
        chooser = gtk.FileChooserDialog('PyRoom', self.window,
 
460
                gtk.FILE_CHOOSER_ACTION_OPEN,
 
461
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
 
462
                gtk.STOCK_OPEN, gtk.RESPONSE_OK))
 
463
        chooser.set_default_response(gtk.RESPONSE_OK)
 
464
 
 
465
        res = chooser.run()
 
466
        if res == gtk.RESPONSE_OK:
 
467
            buffer = self.new_buffer()
 
468
            buffer.filename = chooser.get_filename()
 
469
            try:
 
470
                f = open(buffer.filename, 'r')
 
471
                buffer = self.buffers[self.current]
 
472
                buffer.begin_not_undoable_action()
 
473
                utf8 = unicode(f.read(), 'utf-8')
 
474
                buffer.set_text(utf8)
 
475
                buffer.end_not_undoable_action()
 
476
                f.close()
 
477
                self.status.set_text(_('File %s open')
 
478
                         % buffer.filename)
 
479
            except:
 
480
                buffer.set_text(_('''Unable to open %(filename)s
 
481
 %(traceback)s
 
482
'''
 
483
                                 % {'filename': buffer.filename,
 
484
                                'traceback': traceback.format_exc()}))
 
485
                self.status.set_text(_('Failed to open %s')
 
486
                         % buffer.filename)
 
487
                buffer.filename = FILE_UNNAMED
 
488
        else:
 
489
            self.status.set_text(_('Closed, no files selected'))
 
490
        chooser.destroy()
 
491
 
 
492
    def save_file(self):
 
493
        """ Save file """
 
494
 
 
495
        buffer = self.buffers[self.current]
 
496
        if buffer.filename != FILE_UNNAMED:
 
497
            f = open(buffer.filename, 'w')
 
498
            txt = buffer.get_text(buffer.get_start_iter(),
 
499
                                  buffer.get_end_iter())
 
500
            f.write(txt)
 
501
            f.close()
 
502
            buffer.begin_not_undoable_action()
 
503
            buffer.end_not_undoable_action()
 
504
            self.status.set_text(_('File %s saved') % buffer.filename)
 
505
        else:
 
506
            self.save_file_as()
 
507
 
 
508
    def save_file_as(self):
 
509
        """ Save file as """
 
510
 
 
511
        buffer = self.buffers[self.current]
 
512
        chooser = gtk.FileChooserDialog('PyRoom', self.window,
 
513
                gtk.FILE_CHOOSER_ACTION_SAVE,
 
514
                buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
 
515
                gtk.STOCK_SAVE, gtk.RESPONSE_OK))
 
516
        chooser.set_default_response(gtk.RESPONSE_OK)
 
517
        if buffer.filename != FILE_UNNAMED:
 
518
            chooser.set_filename(buffer.filename)
 
519
        res = chooser.run()
 
520
        if res == gtk.RESPONSE_OK:
 
521
            buffer.filename = chooser.get_filename()
 
522
            self.save_file()
 
523
        else:
 
524
            self.status.set_text(_('Closed, no files selected'))
 
525
        chooser.destroy()
 
526
 
 
527
    # BB
 
528
 
 
529
    def plus(self):
 
530
        """ Increases the font size"""
 
531
 
 
532
        self.style['fontsize'] += 2
 
533
        self.apply_style()
 
534
        self.status.set_text(_('Font size increased'))
 
535
 
 
536
    def minus(self):
 
537
        """ Decreases the font size"""
 
538
 
 
539
        self.style['fontsize'] -= 2
 
540
        self.apply_style()
 
541
        self.status.set_text(_('Font size decreased'))
 
542
 
 
543
 
 
544
if __name__ == '__main__':
 
545
    style = 'green'
 
546
    files = []
 
547
 
 
548
    # Look for style and file on command line
 
549
    for arg in sys.argv[1:]:
 
550
        if arg[0] == '-':
 
551
            t = arg[1:]
 
552
            if styles.has_key(arg[1:]):
 
553
                style = arg[1:]
 
554
        else:
 
555
            files.append(arg)
 
556
 
 
557
    # Create relevant buffers for file and load them
 
558
    pyroom = PyRoom(styles[style])
 
559
    if len(files):
 
560
        for filename in files:
 
561
            buffer = pyroom.new_buffer()
 
562
            buffer.filename = filename
 
563
            if os.path.exists(filename):
 
564
                try:
 
565
                    print filename
 
566
                    f = open(filename, 'r')
 
567
                    buffer.begin_not_undoable_action()
 
568
                    buffer.set_text(unicode(f.read(), 'utf-8'))
 
569
                    buffer.end_not_undoable_action()
 
570
                    f.close()
 
571
                except:
 
572
                    buffer.set_text(_('Unable to open %s\n'
 
573
                                     % buffer.filename))
 
574
                    buffer.set_text(_('Unable to open %(filename)s %(traceback)s')
 
575
                                     % {'filename': buffer.filename,
 
576
                                    'traceback': traceback.format_exc()})
 
577
                    buffer.filename = FILE_UNNAMED
 
578
        pyroom.set_buffer(0)
 
579
        pyroom.close_buffer()
 
580
    gtk.main()
 
581
 
 
582
# EOF