Gabriele Lanaro avatar Gabriele Lanaro committed 4dec691

added pygtkhelpers for packaging purposes

Comments (0)

Files changed (16)

filesnake/__main__.py

 Main module, it provides the starting stuff
 '''
 import sys,os
+
 # Hack to make the module importable without installing the module
 curdir = os.path.dirname(__file__)
 sys.path.append(os.path.dirname(curdir))

filesnake/pygtkhelpers/__init__.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers
+    ~~~~~~~~~~~~
+
+    Helper library for PyGTK
+
+    :copyright: 2009-2010 by pygtkhelpers Authors
+    :license: LGPL2 or later
+"""

filesnake/pygtkhelpers/addons.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers.addons
+    ~~~~~~~~~~~~~~~~~~~~
+
+    Extending GTK WIdgets (or any GObject) without subclassing them.
+
+    :copyright: 2010 by pygtkhelpers Authors
+    :license: LGPL 2 or later (see README/COPYING/LICENSE)
+
+"""
+
+from pygtkhelpers.utils import GObjectUserDataProxy
+
+
+def apply_addons(widget, *addon_types, **named_addon_types):
+    """Apply some addons to a widget.
+
+    :param widget: The widget to apply addons to.
+    :param addon_types: A list of addon types, which will be instantiated
+                         and applied to the widget with the default name of
+                         the addon.
+    :param named_addon_types: A named list of addons, the keywords will be
+                               the name of the addon when loaded and will
+                               override the default addon name. This can
+                               allow loading the same addon multpily for the
+                               same widget under different names.
+
+    Plugins should conform to the GObjectPlugin interface or be a subclass of
+    it. Once loaded, addons will be available as widget.addons.<addon_name>
+    withe standard attribute access.
+    """
+    for addon_type in addon_types:
+        apply_addon(widget, addon_type)
+
+    for name, addon_type in named_addon_types.items():
+        apply_addon(widget, addon_type, addon_name=name)
+
+
+def apply_addon(widget, addon_type, **kw):
+    """Apply a single addon to a widget
+
+    :param widget: The widget to apply the addon to.
+    :param kw: A dict of keyword arguments to be passed to the addon
+    """
+    if not hasattr(widget, 'addons'):
+        widget.addons = GObjectUserDataProxy(widget)
+
+    addon = addon_type(widget, **kw)
+    setattr(widget.addons, addon.addon_name, addon)
+
+
+class GObjectPlugin(object):
+    """A base class for addons for GObjects
+
+    :param widget: The widget to apply the addon to
+    :param addon_name: Optional name to override the class `addon_name`
+                        attribute to load a addon under a non-default name.
+
+    .. note::
+
+        Either the class's addon_name should be set or addon_name must be
+        passed to the constructor, otherwise `ValueError` will be raised.
+    """
+
+    #: The default addon name for instances of this addon
+    addon_name = None
+
+    def __init__(self, widget, addon_name=None, **kw):
+        self.widget = widget
+        self.addon_name = addon_name or self.addon_name
+        if self.addon_name is None:
+            raise ValueError('addon_name must be set.')
+        self.configure(**kw)
+
+    def configure(self, **kw):
+        """Configure and initialise the addon
+
+        For overriding in implementations.
+        """
+

filesnake/pygtkhelpers/debug/__init__.py

+"""
+    pygtkheplers.debug
+    ~~~~~~~~~~~~~~~~~~
+
+    a library for ui integrated traceback handling in pygtk
+"""
+
+from .dialogs import (
+        SimpleExceptionDialog,
+        ExtendedExceptionDialog,
+        install_hook,
+        )

filesnake/pygtkhelpers/debug/console.py

+#!/usr/bin/env python
+#
+#  pyconsole.py
+#
+#  Copyright (C) 2004-2008 by Yevgen Muntyan <muntyan@tamu.edu>
+#  Thanks to Geoffrey French for ideas.
+#
+#  This file is part of medit.  medit is free software; you can
+#  redistribute it and/or modify it under the terms of the
+#  GNU Lesser General Public License as published by the
+#  Free Software Foundation; either version 2.1 of the License,
+#  or (at your option) any later version.
+#
+#  You should have received a copy of the GNU Lesser General Public
+#  License along with medit.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+# This module 'runs' python interpreter in a TextView widget.
+# The main class is Console, usage is:
+# Console(locals=None, banner=None, completer=None, use_rlcompleter=True, start_script='') -
+# it creates the widget and 'starts' interactive session; see the end of
+# this file. If start_script is not empty, it pastes it as it was entered from keyboard.
+#
+# Console has "command" signal which is emitted when code is about to
+# be executed. You may connect to it using console.connect or console.connect_after
+# to get your callback ran before or after the code is executed.
+#
+# To modify output appearance, set attributes of console.stdout_tag and
+# console.stderr_tag.
+#
+# Console may subclass a type other than gtk.TextView, to allow syntax highlighting and stuff,
+# e.g.:
+#   console_type = pyconsole.ConsoleType(moo.edit.TextView)
+#   console = console_type(use_rlcompleter=False, start_script="import moo\nimport gtk\n")
+#
+# This widget is not a replacement for real terminal with python running
+# inside: GtkTextView is not a terminal.
+# The use case is: you have a python program, you create this widget,
+# and inspect your program interiors.
+
+import gtk
+import gtk.gdk as gdk
+import gobject
+import pango
+import gtk.keysyms as _keys
+import code
+import sys
+import keyword
+import re
+
+# commonprefix() from posixpath
+def _commonprefix(m):
+    "Given a list of pathnames, returns the longest common leading component"
+    if not m: return ''
+    prefix = m[0]
+    for item in m:
+        for i in range(len(prefix)):
+            if prefix[:i+1] != item[:i+1]:
+                prefix = prefix[:i]
+                if i == 0:
+                    return ''
+                break
+    return prefix
+
+class _ReadLine(object):
+
+    class Output(object):
+        def __init__(self, console, tag_name):
+            object.__init__(self)
+            self.buffer = console.get_buffer()
+            self.tag_name = tag_name
+        def write(self, text):
+            pos = self.buffer.get_iter_at_mark(self.buffer.get_insert())
+            self.buffer.insert_with_tags_by_name(pos, text, self.tag_name)
+
+    class History(object):
+        def __init__(self):
+            object.__init__(self)
+            self.items = ['']
+            self.ptr = 0
+            self.edited = {}
+
+        def commit(self, text):
+            if text and self.items[-1] != text:
+                self.items.append(text)
+            self.ptr = 0
+            self.edited = {}
+
+        def get(self, dir, text):
+            if len(self.items) == 1:
+                return None
+
+            if text != self.items[self.ptr]:
+                self.edited[self.ptr] = text
+            elif self.edited.has_key(self.ptr):
+                del self.edited[self.ptr]
+
+            self.ptr = self.ptr + dir
+            if self.ptr >= len(self.items):
+                self.ptr = 0
+            elif self.ptr < 0:
+                self.ptr = len(self.items) - 1
+
+            try:
+                return self.edited[self.ptr]
+            except KeyError:
+                return self.items[self.ptr]
+
+    def __init__(self):
+        object.__init__(self)
+
+        self.set_wrap_mode(gtk.WRAP_CHAR)
+        self.modify_font(pango.FontDescription("Monospace"))
+
+        self.buffer = self.get_buffer()
+        self.buffer.connect("insert-text", self.on_buf_insert)
+        self.buffer.connect("delete-range", self.on_buf_delete)
+        self.buffer.connect("mark-set", self.on_buf_mark_set)
+        self.do_insert = False
+        self.do_delete = False
+
+        self.stdout_tag = self.buffer.create_tag("stdout", foreground="#006000")
+        self.stderr_tag = self.buffer.create_tag("stderr", foreground="#B00000")
+        self._stdout = _ReadLine.Output(self, "stdout")
+        self._stderr = _ReadLine.Output(self, "stderr")
+
+        self.cursor = self.buffer.create_mark("cursor",
+                                              self.buffer.get_start_iter(),
+                                              False)
+        insert = self.buffer.get_insert()
+        self.cursor.set_visible(True)
+        insert.set_visible(False)
+
+        self.ps = ''
+        self.in_raw_input = False
+        self.run_on_raw_input = None
+        self.tab_pressed = 0
+        self.history = _ReadLine.History()
+        self.nonword_re = re.compile("[^\w\._]")
+
+    def freeze_undo(self):
+        try: self.begin_not_undoable_action()
+        except: pass
+
+    def thaw_undo(self):
+        try: self.end_not_undoable_action()
+        except: pass
+
+    def raw_input(self, ps=None):
+        if ps:
+            self.ps = ps
+        else:
+            self.ps = ''
+
+        iter = self.buffer.get_iter_at_mark(self.buffer.get_insert())
+
+        if ps:
+            self.freeze_undo()
+            self.buffer.insert(iter, self.ps)
+            self.thaw_undo()
+
+        self.__move_cursor_to(iter)
+        self.scroll_to_mark(self.cursor, 0.2)
+
+        self.in_raw_input = True
+
+        if self.run_on_raw_input:
+            run_now = self.run_on_raw_input
+            self.run_on_raw_input = None
+            self.buffer.insert_at_cursor(run_now + '\n')
+
+    def on_buf_mark_set(self, buffer, iter, mark):
+        if mark is not buffer.get_insert():
+            return
+        start = self.__get_start()
+        end = self.__get_end()
+        if iter.compare(self.__get_start()) >= 0 and \
+           iter.compare(self.__get_end()) <= 0:
+            buffer.move_mark_by_name("cursor", iter)
+            self.scroll_to_mark(self.cursor, 0.2)
+
+    def __insert(self, iter, text):
+        self.do_insert = True
+        self.buffer.insert(iter, text)
+        self.do_insert = False
+
+    def on_buf_insert(self, buf, iter, text, len):
+        if not self.in_raw_input or self.do_insert or not len:
+            return
+        buf.stop_emission("insert-text")
+        lines = text.splitlines()
+        need_eol = False
+        for l in lines:
+            if need_eol:
+                self._commit()
+                iter = self.__get_cursor()
+            else:
+                cursor = self.__get_cursor()
+                if iter.compare(self.__get_start()) < 0:
+                    iter = cursor
+                elif iter.compare(self.__get_end()) > 0:
+                    iter = cursor
+                else:
+                    self.__move_cursor_to(iter)
+            need_eol = True
+            self.__insert(iter, l)
+        self.__move_cursor(0)
+
+    def __delete(self, start, end):
+        self.do_delete = True
+        self.buffer.delete(start, end)
+        self.do_delete = False
+
+    def on_buf_delete(self, buf, start, end):
+        if not self.in_raw_input or self.do_delete:
+            return
+
+        buf.stop_emission("delete-range")
+
+        start.order(end)
+        line_start = self.__get_start()
+        line_end = self.__get_end()
+
+        if start.compare(line_end) > 0:
+            return
+        if end.compare(line_start) < 0:
+            return
+
+        self.__move_cursor(0)
+
+        if start.compare(line_start) < 0:
+            start = line_start
+        if end.compare(line_end) > 0:
+            end = line_end
+        self.__delete(start, end)
+
+    def do_key_press_event(self, event, parent_type):
+        if not self.in_raw_input:
+            return parent_type.do_key_press_event(self, event)
+
+        tab_pressed = self.tab_pressed
+        self.tab_pressed = 0
+        handled = True
+
+        state = event.state & (gdk.SHIFT_MASK |
+                                gdk.CONTROL_MASK |
+                                gdk.MOD1_MASK)
+        keyval = event.keyval
+
+        if not state:
+            if keyval == _keys.Return:
+                self._commit()
+            elif keyval == _keys.Up:
+                self.__history(-1)
+            elif keyval == _keys.Down:
+                self.__history(1)
+            elif keyval == _keys.Left:
+                self.__move_cursor(-1)
+            elif keyval == _keys.Right:
+                self.__move_cursor(1)
+            elif keyval == _keys.Home:
+                self.__move_cursor(-10000)
+            elif keyval == _keys.End:
+                self.__move_cursor(10000)
+            elif keyval == _keys.Tab:
+                cursor = self.__get_cursor()
+                if cursor.starts_line():
+                    handled = False
+                else:
+                    cursor.backward_char()
+                    if cursor.get_char().isspace():
+                        handled = False
+                    else:
+                        self.tab_pressed = tab_pressed + 1
+                        self.__complete()
+            else:
+                handled = False
+        elif state == gdk.CONTROL_MASK:
+            if keyval == _keys.u:
+                start = self.__get_start()
+                end = self.__get_cursor()
+                self.__delete(start, end)
+            else:
+                handled = False
+        else:
+            handled = False
+
+        if not handled:
+            return parent_type.do_key_press_event(self, event)
+        else:
+            return True
+
+    def __history(self, dir):
+        text = self._get_line()
+        new_text = self.history.get(dir, text)
+        if not new_text is None:
+            self.__replace_line(new_text)
+        self.__move_cursor(0)
+        self.scroll_to_mark(self.cursor, 0.2)
+
+    def __get_cursor(self):
+        return self.buffer.get_iter_at_mark(self.cursor)
+    def __get_start(self):
+        iter = self.__get_cursor()
+        iter.set_line_offset(len(self.ps))
+        return iter
+    def __get_end(self):
+        iter = self.__get_cursor()
+        if not iter.ends_line():
+            iter.forward_to_line_end()
+        return iter
+
+    def __get_text(self, start, end):
+        return self.buffer.get_text(start, end, False)
+
+    def __move_cursor_to(self, iter):
+        self.buffer.place_cursor(iter)
+        self.buffer.move_mark_by_name("cursor", iter)
+
+    def __move_cursor(self, howmany):
+        iter = self.__get_cursor()
+        end = self.__get_cursor()
+        if not end.ends_line():
+            end.forward_to_line_end()
+        line_len = end.get_line_offset()
+        move_to = iter.get_line_offset() + howmany
+        move_to = min(max(move_to, len(self.ps)), line_len)
+        iter.set_line_offset(move_to)
+        self.__move_cursor_to(iter)
+
+    def __delete_at_cursor(self, howmany):
+        iter = self.__get_cursor()
+        end = self.__get_cursor()
+        if not end.ends_line():
+            end.forward_to_line_end()
+        line_len = end.get_line_offset()
+        erase_to = iter.get_line_offset() + howmany
+        if erase_to > line_len:
+            erase_to = line_len
+        elif erase_to < len(self.ps):
+            erase_to = len(self.ps)
+        end.set_line_offset(erase_to)
+        self.__delete(iter, end)
+
+    def __get_width(self):
+        if not (self.flags() & gtk.REALIZED):
+            return 80
+        layout = pango.Layout(self.get_pango_context())
+        letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+        layout.set_text(letters)
+        pix_width = layout.get_pixel_size()[0]
+        return self.allocation.width * len(letters) / pix_width
+
+    def __print_completions(self, completions):
+        line_start = self.__get_text(self.__get_start(), self.__get_cursor())
+        line_end = self.__get_text(self.__get_cursor(), self.__get_end())
+        iter = self.buffer.get_end_iter()
+        self.__move_cursor_to(iter)
+        self.__insert(iter, "\n")
+
+        width = max(self.__get_width(), 4)
+        max_width = max([len(s) for s in completions])
+        n_columns = max(int(width / (max_width + 1)), 1)
+        col_width = int(width / n_columns)
+        total = len(completions)
+        col_length = total / n_columns
+        if total % n_columns:
+            col_length = col_length + 1
+        col_length = max(col_length, 1)
+
+        if col_length == 1:
+            n_columns = total
+            col_width = width / total
+
+        for i in range(col_length):
+            for j in range(n_columns):
+                ind = i + j*col_length
+                if ind < total:
+                    if j == n_columns - 1:
+                        n_spaces = 0
+                    else:
+                        n_spaces = col_width - len(completions[ind])
+                    self.__insert(iter, completions[ind] + " " * n_spaces)
+            self.__insert(iter, "\n")
+
+        self.__insert(iter, "%s%s%s" % (self.ps, line_start, line_end))
+        iter.set_line_offset(len(self.ps) + len(line_start))
+        self.__move_cursor_to(iter)
+        self.scroll_to_mark(self.cursor, 0.2)
+
+    def __complete(self):
+        text = self.__get_text(self.__get_start(), self.__get_cursor())
+        start = ''
+        word = text
+        nonwords = self.nonword_re.findall(text)
+        if nonwords:
+            last = text.rfind(nonwords[-1]) + len(nonwords[-1])
+            start = text[:last]
+            word = text[last:]
+
+        completions = self.complete(word)
+
+        if completions:
+            prefix = _commonprefix(completions)
+            if prefix != word:
+                start_iter = self.__get_start()
+                start_iter.forward_chars(len(start))
+                end_iter = start_iter.copy()
+                end_iter.forward_chars(len(word))
+                self.__delete(start_iter, end_iter)
+                self.__insert(end_iter, prefix)
+            elif self.tab_pressed > 1:
+                self.freeze_undo()
+                self.__print_completions(completions)
+                self.thaw_undo()
+                self.tab_pressed = 0
+
+    def complete(self, text):
+        return None
+
+    def _get_line(self):
+        start = self.__get_start()
+        end = self.__get_end()
+        return self.buffer.get_text(start, end, False)
+
+    def __replace_line(self, new_text):
+        start = self.__get_start()
+        end = self.__get_end()
+        self.__delete(start, end)
+        self.__insert(end, new_text)
+
+    def _commit(self):
+        end = self.__get_cursor()
+        if not end.ends_line():
+            end.forward_to_line_end()
+        text = self._get_line()
+        self.__move_cursor_to(end)
+        self.freeze_undo()
+        self.__insert(end, "\n")
+        self.in_raw_input = False
+        self.history.commit(text)
+        self.do_raw_input(text)
+        self.thaw_undo()
+
+    def do_raw_input(self, text):
+        pass
+
+
+class _Console(_ReadLine, code.InteractiveInterpreter):
+    def __init__(self, locals=None, banner=None,
+                 completer=None, use_rlcompleter=True,
+                 start_script=None):
+        _ReadLine.__init__(self)
+
+        code.InteractiveInterpreter.__init__(self, locals)
+        self.locals["__console__"] = self
+
+        self.start_script = start_script
+        self.completer = completer
+        self.banner = banner
+
+        if not self.completer and use_rlcompleter:
+            try:
+                import rlcompleter
+                self.completer = rlcompleter.Completer()
+            except ImportError:
+                pass
+
+        self.ps1 = ">>> "
+        self.ps2 = "... "
+        self.__start()
+        self.run_on_raw_input = start_script
+        self.raw_input(self.ps1)
+
+    def __start(self):
+        self.cmd_buffer = ""
+
+        self.freeze_undo()
+        self.thaw_undo()
+        self.buffer.set_text("")
+
+        if self.banner:
+            iter = self.buffer.get_start_iter()
+            self.buffer.insert_with_tags_by_name(iter, self.banner, "stdout")
+            if not iter.starts_line():
+                self.buffer.insert(iter, "\n")
+
+    def clear(self, start_script=None):
+        if start_script is None:
+            start_script = self.start_script
+        else:
+            self.start_script = start_script
+
+        self.__start()
+        self.run_on_raw_input = start_script
+
+    def do_raw_input(self, text):
+        if self.cmd_buffer:
+            cmd = self.cmd_buffer + "\n" + text
+        else:
+            cmd = text
+
+        saved_stdout, saved_stderr = sys.stdout, sys.stderr
+        sys.stdout, sys.stderr = self._stdout, self._stderr
+
+        if self.runsource(cmd):
+            self.cmd_buffer = cmd
+            ps = self.ps2
+        else:
+            self.cmd_buffer = ''
+            ps = self.ps1
+
+        sys.stdout, sys.stderr = saved_stdout, saved_stderr
+        self.raw_input(ps)
+
+    def execfile(self, filename):
+        saved_stdout, saved_stderr = sys.stdout, sys.stderr
+        sys.stdout, sys.stderr = self._stdout, self._stderr
+        try:
+            execfile(filename, self.locals)
+        except SystemExit:
+            raise
+        except:
+            self.showtraceback()
+        finally:
+            sys.stdout, sys.stderr = saved_stdout, saved_stderr
+
+    def do_command(self, code):
+        try:
+            eval(code, self.locals)
+        except SystemExit:
+            raise
+        except:
+            self.showtraceback()
+
+    def runcode(self, code):
+        if gtk.pygtk_version[1] < 8:
+            self.do_command(code)
+        else:
+            self.emit("command", code)
+
+    def exec_command(self, command):
+        if self._get_line():
+            self._commit()
+        self.buffer.insert_at_cursor(command)
+        self._commit()
+
+    def complete_attr(self, start, end):
+        try:
+            obj = eval(start, self.locals)
+            strings = dir(obj)
+
+            if end:
+                completions = {}
+                for s in strings:
+                    if s.startswith(end):
+                        completions[s] = None
+                completions = completions.keys()
+            else:
+                completions = strings
+
+            completions.sort()
+            return [start + "." + s for s in completions]
+        except:
+            return None
+
+    def complete(self, text):
+        if self.completer:
+            completions = []
+            i = 0
+            try:
+                while 1:
+                    s = self.completer.complete(text, i)
+                    if s:
+                        completions.append(s)
+                        i = i + 1
+                    else:
+                        completions.sort()
+                        return completions
+            except NameError:
+                return None
+
+        dot = text.rfind(".")
+        if dot >= 0:
+            return self.complete_attr(text[:dot], text[dot+1:])
+
+        completions = {}
+        strings = keyword.kwlist
+
+        if self.locals:
+            strings.extend(self.locals.keys())
+
+        try: strings.extend(eval("globals()", self.locals).keys())
+        except: pass
+
+        try:
+            exec "import __builtin__" in self.locals
+            strings.extend(eval("dir(__builtin__)", self.locals))
+        except:
+            pass
+
+        for s in strings:
+            if s.startswith(text):
+                completions[s] = None
+        completions = completions.keys()
+        completions.sort()
+        return completions
+
+    def flush(self):
+        pass
+
+    def tell(self):
+        return 0
+
+    def isatty(self):
+        return False
+
+    def truncate(self):
+        raise IOError('cant truncate fake file')
+
+def ReadLineType(t=gtk.TextView):
+    class readline(t, _ReadLine):
+        def __init__(self, *args, **kwargs):
+            t.__init__(self)
+            _ReadLine.__init__(self, *args, **kwargs)
+        def do_key_press_event(self, event):
+            return _ReadLine.do_key_press_event(self, event, t)
+    gobject.type_register(readline)
+    return readline
+
+def ConsoleType(t=gtk.TextView):
+    class console_type(t, _Console):
+        __gsignals__ = {
+            'command' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (object,)),
+            'key-press-event' : 'override'
+          }
+
+        def __init__(self, *args, **kwargs):
+            if gtk.pygtk_version[1] < 8:
+                gobject.GObject.__init__(self)
+            else:
+                t.__init__(self)
+            _Console.__init__(self, *args, **kwargs)
+
+        def do_command(self, code):
+            return _Console.do_command(self, code)
+
+        def do_key_press_event(self, event):
+            return _Console.do_key_press_event(self, event, t)
+
+    if gtk.pygtk_version[1] < 8:
+        gobject.type_register(console_type)
+
+    return console_type
+
+ReadLine = ReadLineType()
+Console = ConsoleType()
+
+def _create_widget(start_script):
+    try:
+        import moo
+        console_type = ConsoleType(moo.edit.TextView)
+        console = console_type(banner="Hello there!",
+                               use_rlcompleter=False,
+                               start_script=start_script)
+        console.set_property("highlight-current-line", False)
+        editor = moo.edit.create_editor_instance()
+        console.set_lang_by_id("python-console")
+    except ImportError:
+        console = Console(banner="Hello there!",
+                          use_rlcompleter=False,
+                          start_script=start_script)
+    console.modify_font(pango.FontDescription("Monospace"))
+    return console
+
+def _make_window(start_script="from gtk import *\n"):
+    window = gtk.Window()
+    window.set_title("pyconsole.py")
+    swin = gtk.ScrolledWindow()
+    swin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_ALWAYS)
+    window.add(swin)
+    console = _create_widget(start_script)
+    swin.add(console)
+    window.set_default_size(500, 400)
+    window.show_all()
+
+    if not gtk.main_level():
+        window.connect("destroy", gtk.main_quit)
+        gtk.main()
+
+    return console
+
+if __name__ == '__main__':
+    import sys
+    import os
+    sys.path.insert(0, os.getcwd())
+    _make_window(sys.argv[1:] and '\n'.join(sys.argv[1:]) + '\n' or None)

filesnake/pygtkhelpers/debug/dialogs.py

+"""
+    pygtkhelpers.debug.dialogs
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    An exception handler for pygtk with nice ui
+
+    :copyright: 2009 by Ronny Pfannschmidt <Ronny.Pfannschmidt@gmx.de>
+    :license: LGPL2 or later
+"""
+
+import sys
+import traceback
+import linecache
+from cgi import escape
+
+import gtk
+import gobject
+
+
+def scrolled(widget, shadow=gtk.SHADOW_NONE):
+    window = gtk.ScrolledWindow()
+    window.set_shadow_type(shadow)
+    window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
+    if widget.set_scroll_adjustments(window.get_hadjustment(),
+                                      window.get_vadjustment()):
+        window.add(widget)
+    else:
+        window.add_with_viewport(widget)
+    return window
+
+
+class SimpleExceptionDialog(gtk.MessageDialog):
+    def __init__(self, exc, tb, parent=None, **extra):
+
+        gtk.MessageDialog.__init__(self,
+                buttons=gtk.BUTTONS_CLOSE,
+                type=gtk.MESSAGE_ERROR,
+                parent=parent
+                )
+
+        self.extra = extra
+        self.exc = exc
+        self.tb = tb
+
+        self.set_resizable(True)
+
+        text = 'An exception Occured\n\n<b>%s</b>: %s'
+        self.set_markup(text%(exc.__class__.__name__, exc))
+
+        #XXX: add support for showing url's for pastebins
+
+        expander = gtk.Expander("Exception Details")
+        self.vbox.pack_start(expander)
+        expander.add(self.get_trace_view(exc, tb))
+
+        #XXX: add additional buttons for pastebin/bugreport
+
+        self.show_all()
+
+
+    def get_trace_view(self, exc, tb):
+        text = traceback.format_exception(type(exc), exc, tb)
+        textview = gtk.TextView()
+        textview.get_buffer().set_text(''.join(text))
+        return scrolled(textview)
+
+
+
+def extract_tb(tb, limit=None):
+    if limit is None and hasattr(sys, 'tracebacklimit'):
+        limit = sys.tracebacklimit
+    assert limit is None or limit>0
+
+    while tb is not None and (limit is None or limit >0):
+        frame = tb.tb_frame
+        lineno = tb.tb_lineno
+        code = frame.f_code
+        filename = code.co_filename
+        name = code.co_name
+        linecache.checkcache(filename)
+        line = linecache.getline(filename, lineno, frame.f_globals)
+        line = line.strip() if line else None
+        yield filename, lineno, name, line, frame
+        tb = tb.tb_next
+
+
+class ExtendedExceptionDialog(SimpleExceptionDialog):
+    format = ('File <span color="darkgreen">%r</span>,'
+              ' line <span color="blue"><i>%d</i></span> in <i>%s</i>\n'
+              '  %s')
+    def get_trace_view(self, exc, tb):
+        store = gtk.ListStore(str, int, str, str, object)
+        for item in extract_tb(tb):
+            store.append(item)
+
+        view = gtk.TreeView(model=store)
+        view.set_headers_visible(False)
+        cell = gtk.CellRendererText()
+        column = gtk.TreeViewColumn('Pango Markup', cell, markup=0)
+        view.append_column(column)
+        column.set_cell_data_func(cell, self.data_func)
+        return view
+
+    def data_func(self, column, cell, model, iter):
+        filename = escape(model.get_value(iter, 0))
+        lineno = model.get_value(iter, 1)
+        name = escape(model.get_value(iter, 2))
+        line = escape(model.get_value(iter, 3))
+
+        text = self.format%(filename, lineno, name, line)
+        cell.set_property('markup', text)
+
+
+_hook_installed = False
+
+def install_hook(
+        dialog=SimpleExceptionDialog,
+        invoke_old_hook=False,
+        **extra):
+    """
+    install the configured exception hook wrapping the old exception hook
+
+    don't use it twice
+
+    :oparam dialog: a different exception dialog class
+    :oparam invoke_old_hook: should we invoke the old exception hook?
+    """
+    global _hook_installed
+    old_hook = sys.excepthook
+
+    def new_hook(etype, eval, trace):
+        def handler(etype, eval, trace):
+            if etype not in (KeyboardInterrupt, SystemExit):
+                d = dialog(eval, trace, **extra)
+                d.run()
+                d.destroy()
+        gobject.idle_add(handler, etype, eval, trace)
+        if invoke_old_hook:
+            old_hook(etype, eval, trace)
+    assert not _hook_installed
+    sys.excepthook = new_hook
+    _hook_installed = True
+

filesnake/pygtkhelpers/delegates.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers.delegates
+    ~~~~~~~~~~~~~~~~~~~~~~
+
+    Delegates, which combine some UI, some signals, some signal handlers, and
+    some properties,
+
+    :copyright: 2009-2010 by pygtkhelpers Authors
+    :license: LGPL2 or later
+"""
+
+import os
+import sys
+import pkgutil
+
+import gobject, gtk
+
+from .utils import gsignal
+
+
+def get_first_builder_window(builder):
+    """Get the first toplevel widget in a gtk.Builder hierarchy.
+
+    This is mostly used for guessing purposes, and an explicit naming is
+    always going to be a better situation.
+    """
+    for obj in builder.get_objects():
+        if isinstance(obj, gtk.Window):
+            # first window
+            return obj
+
+
+class BaseDelegate(gobject.GObject):
+    """Base delegate functionality.
+
+    This is abstract.
+
+    It uses hand-created, and gtk.Builder created ui's, and combinations of the
+    two, and is responsible for automatically loading ui files from resources,
+    and connecting signals.
+
+    Additionally, it is a gobject.GObject subclass, and so can be used with the
+    gsignal, and gproperty functions from pygtkhelpers.utils in order to add
+    property and signal functionality.
+
+    The abstract elements of this class are:
+
+        1. The way it gets a toplevel widget from a ui file
+        2. How it creates a default toplevel widget if one was not found in the
+           ui file, or no ui file is specified.
+    """
+
+    builder_file = None
+    builder_path = None
+    toplevel_name = 'main'
+    builder_file_patterns = [
+        #this should be the default
+        'ui/%s.ui',
+        'ui/%s',
+        # commonly used in applications like for example pida
+        'glade/%s.glade',
+        'glade/%s',
+    ]
+
+
+    #XXX: should those get self.model as extra parameter?
+    # they get the delegate, so its there as delegate.model
+    gsignal('model-set')
+
+    # (attribute, value)
+    gsignal('model-updated', object, object) # one should emit that when changing the models
+
+    def __init__(self):
+        gobject.GObject.__init__(self)
+        self._props = {}
+        self._toplevel = None
+        self.slaves = []
+        self._load_builder()
+        if self._toplevel is None:
+            self._toplevel = self.create_default_toplevel()
+        self.widget = self._toplevel
+        self.create_ui()
+        self._connect_signals()
+        self.model = None
+
+    # Public API
+    def get_builder_toplevel(self, builder):
+        """Get the toplevel widget from a gtk.Builder file.
+        """
+        raise NotImplementedError
+
+    def create_default_toplevel(self):
+        raise NotImplementedError
+
+    def create_ui(self):
+        """Create any UI by hand.
+
+        Override to create additional UI here.
+
+        This can contain any instance initialization, so for example mutation of
+        the gtk.Builder generated UI, or creating the UI in its entirety.
+        """
+
+    def model_set(self):
+        """This method is called when the model is changed
+        """
+    def add_slave(self, slave, container_name):
+        """Add a slave delegate
+        """
+        cont = getattr(self, container_name, None)
+        if cont is None:
+            raise AttributeError(
+                'Container name must be a member of the delegate')
+        cont.add(slave.widget)
+        self.slaves.append(slave)
+        return slave
+
+    def show(self):
+        """Call show_all on the toplevel widget"""
+        self.widget.show_all()
+
+    def hide(self):
+        """Call hide on the toplevel widget"""
+        self.widget.hide()
+
+    def show_and_run(self):
+        """Show the main widget and run the gtk loop"""
+        self.show()
+        gtk.main()
+
+    def hide_and_quit(self):
+        """Hide the widget and quit the main loop"""
+        self.hide()
+        gtk.main_quit()
+
+    def _load_builder(self):
+        builder = gtk.Builder()
+        if self.builder_path:
+            if not os.path.exists(self.builder_path):
+                raise LookupError(self.__class__, self.builder_path)
+            builder.add_from_file(self.builder_path)
+        elif self.builder_file:
+
+            #XXX: more sensible selection!!
+            data = None
+            for type in self.__class__.__mro__:
+                for pattern in self.builder_file_patterns:
+                    file = pattern % self.builder_file
+                    try:
+                        data = pkgutil.get_data(type.__module__, file)
+                        break
+                    except (IOError, ImportError):
+                        continue
+                if data:
+                    break
+            if not data: #XXX: better debugging of the causes?
+                raise LookupError(self.__class__, self.builder_file)
+
+            builder.add_from_string(data)
+        else: return
+        self._toplevel = self.get_builder_toplevel(builder)
+        for obj in builder.get_objects():
+            setattr(self, gtk.Buildable.get_name(obj), obj)
+
+    def _connect_signals(self):
+        for name in self._get_all_handlers():
+            self._connect_signal(name)
+
+    def _parse_signal_handler(self, name):
+        signal_type, widget_signal = name.split('_', 1)
+        widget_name, signal_name = widget_signal.split('__')
+        return signal_type, widget_name, signal_name
+
+    def _connect_signal(self, name):
+        method = getattr(self, name)
+        signal_type, widget_name, signal_name = self._parse_signal_handler(name)
+        widget = getattr(self, widget_name, None)
+        if widget is None:
+            raise LookupError('Widget named %s is not available.'%widget_name )
+        if signal_type == 'on':
+            widget.connect(signal_name, method)
+        elif signal_type == 'after':
+            widget.connect_after(signal_name, method)
+
+    def _get_all_handlers(self):
+        for name in dir(self):
+            if ((name.startswith('on_') or
+                    name.startswith('after_')) and
+                    '__' in  name):
+                yield name
+
+    def _get_prop_handler(self, propname, action):
+        return getattr(self, '%s_property_%s' % (action, propname), None)
+
+    def set_model(self, model):
+        self._model = model
+        self.model_set()
+        self.emit('model-set')
+
+    def get_model(self):
+        return self._model
+
+    model = property(get_model, set_model)
+
+
+    # Private glib API for simple property handling
+    def do_get_property(self, prop):
+        call = self._get_prop_handler(prop.name, 'get')
+        if call is not None:
+            return call()
+        else:
+            return self._props.get(prop.name, prop.default_value)
+
+    def do_set_property(self, prop, value):
+        call = self._get_prop_handler(prop.name, 'set')
+        if call is not None:
+            call(value)
+        else:
+            self._props[prop.name] = value
+
+
+class SlaveView(BaseDelegate):
+    """A View that is a slave"""
+
+    def get_builder_toplevel(self, builder):
+        """Get the toplevel widget from a gtk.Builder file.
+
+        The slave view implementation first searches for the widget named as
+        self.toplevel_name (which defaults to "main". If this is missing, the
+        first toplevel widget is discovered in the Builder file, and it's
+        immediate child is used as the toplevel widget for the delegate.
+        """
+        toplevel = builder.get_object(self.toplevel_name)
+        if toplevel is None:
+            toplevel = get_first_builder_window(builder).child
+        if toplevel is not None:
+            toplevel.get_parent().remove(toplevel)
+        return toplevel
+
+    def create_default_toplevel(self):
+        return gtk.VBox()
+
+    def show_and_run(self):
+        """Show the main widget in a window and run the gtk loop"""
+        self.display_widget = gtk.Window()
+        self.display_widget.add(self.widget)
+        self.display_widget.show()
+        BaseDelegate.show_and_run(self)
+
+class ToplevelView(BaseDelegate):
+    """A View that is a toplevel widget"""
+
+    def get_builder_toplevel(self, builder):
+        """Get the toplevel widget from a gtk.Builder file.
+
+        The main view implementation first searches for the widget named as
+        self.toplevel_name (which defaults to "main". If this is missing, or not
+        a gtk.Window, the first toplevel window found in the gtk.Builder is
+        used.
+        """
+        toplevel = builder.get_object(self.toplevel_name)
+        if not gobject.type_is_a(toplevel, gtk.Window):
+            toplevel = None
+        if toplevel is None:
+            toplevel = get_first_builder_window(builder)
+        return toplevel
+
+    def create_default_toplevel(self):
+        return gtk.Window()
+
+
+class WindowView(ToplevelView):
+    """A View that is a Window"""
+    def set_title(self, title):
+        self.get_toplevel().set_title(title)
+

filesnake/pygtkhelpers/forms.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers.forms
+    ~~~~~~~~~~~~~~~~~~
+
+    Providing specialized delegates that can be used to map and validate
+    against schemas. Validation and schema support is provided by Flatland_.
+
+    :copyright: 2005-2008 by pygtkhelpers Authors
+    :license: LGPL 2 or later (see README/COPYING/LICENSE)
+"""
+
+import sys
+import gtk
+
+from flatland import Dict, String, Integer, Boolean
+
+from pygtkhelpers.proxy import ProxyGroup, proxy_for
+from pygtkhelpers.delegates import SlaveView
+from pygtkhelpers.utils import gsignal
+
+
+def _view_type_for_element(element):
+    # now do something with element.__class__
+    ## something nasty
+    for element_type in element.__class__.__mro__:
+        if element_type in element_views:
+            return element_views[element_type]
+
+
+def widget_for(element):
+    """Create a widget for a schema item
+    """
+    view_type = _view_type_for_element(element)
+    if view_type is None:
+        raise KeyError('No view type for %r' % element)
+    builder = view_widgets.get(view_type)
+    if builder is None:
+        raise KeyError('No widget type for %r' % view_type)
+    return builder(element)
+
+
+class Field(object):
+    """Encapsulates the widget and the label display
+    """
+
+    def __init__(self, element, widget, label_widget=None):
+        self.element = element
+        self.widget = widget
+        self.proxy = proxy_for(widget)
+        self.label_widget = gtk.Label()
+
+    def set_label(self, text):
+        self.label_widget.set_text(text)
+
+    def _unparent(self):
+        self.widget.unparent()
+        self.label_widget.unparent()
+
+    def layout_as_table(self, table, row):
+        self._unparent()
+        self.label_widget.set_alignment(1.0, 0.5)
+        table.attach(self.label_widget, 0, 1, row, row+1,
+            xoptions=gtk.SHRINK|gtk.FILL, yoptions=gtk.SHRINK)
+        table.attach(self.widget, 1, 2, row, row+1,
+            xoptions=gtk.EXPAND|gtk.FILL, yoptions=gtk.SHRINK)
+
+
+
+
+
+class FieldSet(object):
+
+    def __init__(self, delegate, schema_type):
+        self.delegate = delegate
+        self.schema = schema_type()
+        self.proxies = ProxyGroup()
+        self.fields = {}
+        self.proxies.connect('changed', self._on_proxies_changed)
+        for name, element in self.schema.items():
+            self._setup_widget(name, element)
+
+    def _setup_widget(self, name, element):
+        widget = getattr(self.delegate, name, None)
+        #XXX (AA) this will always be the case, we are running too soon
+        if widget is None:
+            widget = widget_for(element)
+            setattr(self.delegate, name, widget)
+        field = self.fields[name] = Field(element, widget=widget)
+        field.set_label(name.capitalize())
+        self.proxies.add_proxy(name, field.proxy)
+
+    def _on_proxies_changed(self, group, proxy, name, value):
+        self.schema[name].set(value)
+
+    def layout_as_table(self):
+        table = gtk.Table(len(self.fields), 2)
+        table.set_row_spacings(6)
+        table.set_col_spacings(6)
+        table.set_border_width(6)
+        for row, name in enumerate(self.fields):
+            self.fields[name].layout_as_table(table, row)
+        return table
+
+
+class FormView(SlaveView):
+    """A specialized delegate that adds widget proxying and schema support
+    """
+
+    schema_type = None
+
+    def create_ui(self):
+        self.form = FieldSet(self, self.schema_type)
+        self.widget.pack_start(self.form.layout_as_table())
+
+
+
+class WidgetBuilder(object):
+    """Defer widget building to allow post-configuration
+    """
+    def __init__(self, widget_type):
+        self.widget_type = widget_type
+
+    def __call__(self, element):
+        return self.widget_type()
+
+
+class ElementBuilder(object):
+
+    default_style = None
+
+    styles = {}
+
+    def __call__(self, element):
+        options = getattr(element, 'render_options', {})
+        style = options.get('style', self.default_style)
+        widget_type = self.styles.get(style)
+        if widget_type is None:
+            raise NotImplementedError(element)
+        widget = widget_type()
+        return self.build(widget, style, element, options)
+
+    def build(self, widget, style, element, options):
+        raise NotImplementedError
+
+
+class BooleanBuilder(ElementBuilder):
+
+    default_style = 'check'
+
+    styles = {
+        'check': gtk.CheckButton,
+        'toggle': gtk.ToggleButton
+    }
+
+    def build(self, widget, style, element, options):
+        if style == 'toggle':
+            widget.connect('toggled', self._on_toggle_toggled)
+            widget.set_use_stock(True)
+            self._on_toggle_toggled(widget)
+        return widget
+
+    def _on_toggle_toggled(self, toggle):
+        if toggle.get_active():
+            toggle.set_label(gtk.STOCK_YES)
+        else:
+            toggle.set_label(gtk.STOCK_NO)
+
+
+class StringBuilder(ElementBuilder):
+
+    default_style = 'uniline'
+
+    styles = {
+        'uniline': gtk.Entry,
+        'multiline': gtk.TextView,
+    }
+
+    def build(self, widget, style, element, options):
+        if style == 'multiline':
+            widget.set_size_request(-1, 100)
+        return widget
+
+
+class IntegerBuilder(ElementBuilder):
+
+    default_style = 'spin'
+
+    styles = {
+        'spin': gtk.SpinButton,
+        'slider': gtk.HScale,
+    }
+
+    def build(self, widget, style, element, options):
+        widget.set_digits(0)
+        adj = widget.get_adjustment()
+        min, max = -sys.maxint, sys.maxint
+        for v in element.validators:
+            if hasattr(v, 'minimum'):
+                min = v.minimum
+            elif hasattr(v, 'maximum'):
+                max = v.maximum
+        adj.set_all(min, min, max, 1.0, 10.0)
+        return widget
+
+
+VIEW_ENTRY = 'entry'
+VIEW_PASSWORD = 'password'
+VIEW_TEXT = 'text'
+VIEW_NUMBER = 'integer'
+VIEW_LIST = 'list'
+VIEW_CHECK = 'check'
+
+VIEW_LAYOUT_LIST = 'layout-list'
+VIEW_LAYOUT_TABLE = 'layout-table'
+
+
+#: Map of flatland element types to view types
+element_views = {
+    String: VIEW_ENTRY,
+    Integer: VIEW_NUMBER,
+    Boolean: VIEW_CHECK,
+}
+
+#: map of view types to flatland element types
+view_widgets = {
+    VIEW_ENTRY: StringBuilder(),
+    VIEW_NUMBER: IntegerBuilder(),
+    VIEW_CHECK: BooleanBuilder(),
+}
+
+

filesnake/pygtkhelpers/gthreads.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers.gthreads
+    ~~~~~~~~~~~~~~~~~~~~~
+
+    Helpers for integration of aysnchronous behaviour in PyGTK.
+
+    :copyright: 2005-2010 by pygtkhelpers Authors
+    :license: LGPL 2 or later (see README/COPYING/LICENSE)
+"""
+
+
+import os
+import threading, thread
+import Queue as queue
+import subprocess
+import gobject
+import time
+
+
+class AsyncTask(object):
+    """Perform lengthy tasks without delaying the UI loop cycle.
+
+    AsyncTasks removes the boilerplate of deferring a task to a thread and
+    receiving intermittent feedback from the thread. It Handles creating and
+    starting a thread for the task, and forcing any user interface calls to be
+    pushed to the GTK main loop from the thread, thus ensuring against
+    insanity which invariably ensues if this precaustion is not taken.
+
+    It is also assumed that each action that the async worker performs cancels
+    the old one (if it's still working), thus there's no problem when the task
+    takes too long.  You can either extend this class or pass two callable
+    objects through its constructor.
+
+    The first on is the 'work_callback' this is where the lengthy
+    operation must be performed. This object may return an object or a group
+    of objects, these will be passed onto the second callback 'loop_callback'.
+    You must be aware on how the argument passing is done. If you return an
+    object that is not a tuple then it's passed directly to the loop callback.
+    If you return `None` no arguments are supplied. If you return a tuple
+    object then these will be the arguments sent to the loop callback.
+
+    The loop callback is called inside Gtk+'s main loop and it's where you
+    should stick code that affects the UI.
+    """
+    def __init__(self, work_callback=None, loop_callback=None, daemon=True):
+        self.counter = 0
+        gobject.threads_init() #the glib mainloop doesn't love us else
+        self.daemon = daemon
+
+        if work_callback is not None:
+            self.work_callback = work_callback
+        if loop_callback is not None:
+            self.loop_callback = loop_callback
+
+    def start(self, *args, **kwargs):
+        """Start the task
+
+        This is:
+            * not threadsave
+            * assumed to be called in the gtk mainloop
+        """
+        args = (self.counter,) + args
+        thread = threading.Thread(
+                target=self._work_callback,
+                args=args, kwargs=kwargs
+                )
+        thread.setDaemon(self.daemon)
+        thread.start()
+
+    def work_callback(self):
+        pass
+
+    def loop_callback(self):
+        pass
+
+    def _work_callback(self, counter, *args, **kwargs):
+        ret = self.work_callback(*args, **kwargs)
+        #tuple necessary cause idle_add wont allow more args
+        gobject.idle_add(self._loop_callback, (counter, ret))
+
+    def _loop_callback(self, vargs):
+        counter, ret = vargs
+        if counter != self.counter:
+            return
+
+        if ret is None:
+            ret = ()
+        if not isinstance(ret, tuple):
+            ret = (ret,)
+
+        self.loop_callback(*ret)
+
+
+class GeneratorTask(AsyncTask):
+    """
+    The diference between this task and AsyncTask
+    is that the `work` callback returns a generator.
+    For each value the generator yields
+    the `loop` callback is called inside Gtk+'s main loop.
+
+    :param work: callback that returns results
+    :param loop: callback inside the gtk thread
+    :keyword priority: gtk priority the loop callback will have
+    :keyword pass_generator:
+        will pass the generator instance
+        as `generator_task` to the worker callback
+
+    A simple example::
+
+        def work():
+            for i in range(10000):
+                yield i
+
+        def loop(val):
+            print val
+
+        gt = GeneratorTask(work, loop)
+        gt.start()
+        import gtk
+        gtk.main()
+    """
+    def __init__(self, work_callback, loop_callback, complete_callback=None,
+                 priority=gobject.PRIORITY_DEFAULT_IDLE,
+                 pass_generator=False):
+        AsyncTask.__init__(self, work_callback, loop_callback)
+        self.priority = priority
+        self._complete_callback = complete_callback
+        self._pass_generator = pass_generator
+
+    def _work_callback(self, counter, *args, **kwargs):
+        self._stopped = False
+        if self._pass_generator:
+            kwargs = kwargs.copy()
+            kwargs['generator_task'] = self
+        for ret in self.work_callback(*args, **kwargs):
+            #XXX: what about checking self.counter?
+            if self._stopped:
+                thread.exit()
+            gobject.idle_add(self._loop_callback, (counter, ret),
+                             priority=self.priority)
+        if self._complete_callback is not None:
+            gobject.idle_add(self._complete_callback,
+                             priority=self.priority)
+
+    def stop(self):
+        self._stopped = True
+
+    @property
+    def is_stopped(self):
+        return self._stopped
+
+
+def gcall(func, *args, **kwargs):
+    """
+    Calls a function, with the given arguments inside Gtk's main loop.
+    Example::
+        gcall(lbl.set_text, "foo")
+
+    If this call would be made in a thread there could be problems, using
+    it inside Gtk's main loop makes it thread safe.
+    """
+    return gobject.idle_add(lambda: (func(*args, **kwargs) or False))
+
+
+def invoke_in_mainloop(func, *args, **kwargs):
+    """
+    invoke a function in the mainloop, pass the data back
+    """
+    results = queue.Queue()
+
+    def run():
+        try:
+            data = func(*args, **kwargs)
+            results.put(data)
+            results.put(None)
+        except BaseException, e: #XXX: handle
+            results.put(None)
+            results.put(e)
+            raise
+
+    gcall(run)
+
+    data = results.get()
+    exception = results.get()
+
+    if exception is None:
+        return data
+    else:
+        raise exception

filesnake/pygtkhelpers/proxy.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers.proxy
+    ~~~~~~~~~~~~~~~~~~
+
+    Controllers for managing data display widgets.
+
+    :copyright: 2005-2008 by pygtkhelpers Authors
+    :license: LGPL 2 or later (see README/COPYING/LICENSE)
+
+    An example session of using a proxy::
+
+        >>> import gtk
+        >>> from pygtkhelpers.proxy import proxy_for
+        >>> widget = gtk.Entry()
+        >>> proxy = proxy_for(widget)
+        >>> proxy
+        <GtkEntryProxy object at 0x9aea25c (PyGTKHelperGObjectProxy at 0x9e6ec50)>
+        >>> proxy.update('hello')
+        >>> proxy.read()
+        'hello'
+        >>> def changed(proxy, value):
+        ...     print proxy, 'changed to', value
+        ...
+        ...
+        >>> proxy.connect('changed', changed)
+        32L
+        >>> proxy.update('bye bye')
+        <GtkEntryProxy object at 0x9aea25c (PyGTKHelperGObjectProxy at 0x9e6ec50)> changed to bye bye
+        >>> widget.get_text()
+        'bye bye'
+        >>> widget.set_text('banana')
+        <GtkEntryProxy object at 0x9aea25c (PyGTKHelperGObjectProxy at 0x9e6ec50)> changed to banana
+        >>> proxy.read()
+        'banana'
+"""
+
+
+import gobject, gtk
+
+from pygtkhelpers.utils import gsignal
+from pygtkhelpers.ui.widgets import StringList, SimpleComboBox
+
+
+class GObjectProxy(gobject.GObject):
+    """A proxy for a gtk.Widget
+
+    This proxy provides a common api to gtk widgets, so that they can be used
+    without knowing which specific widget they are. All proxy types should
+    extend this class.
+    """
+    __gtype_name__ = 'PyGTKHelperGObjectProxy'
+
+    gsignal('changed', object)
+
+    signal_name = None
+
+    def __init__(self, widget):
+        gobject.GObject.__init__(self)
+        self.widget = widget
+        self.connections = []
+        self.connect_widget()
+
+    # public API
+
+    def update(self, value):
+        """Update the widget's value
+        """
+        self.update_internal(value)
+        self.emit('changed', self.get_widget_value())
+
+    def read(self):
+        """Get the widget's value
+        """
+        return self.get_widget_value()
+
+    # implementor API
+
+    def block(self):
+        for signal_id in self.connections:
+            self.widget.handler_block(signal_id)
+
+    def unblock(self):
+        for signal_id in self.connections:
+            self.widget.handler_unblock(signal_id)
+
+    def update_internal(self, value):
+        """Update the widget's value without firing a changed signal
+        """
+        self.block()
+        self.set_widget_value(value)
+        self.unblock()
+
+    def widget_changed(self, *args):
+        """Called to indicate that a widget's value has been changed.
+
+        This will usually be called from a proxy implementation on response to
+        whichever signal was connected in `connect_widget`
+
+        The `*args` are there so you can use this as a signal handler.
+        """
+        self.emit('changed', self.get_widget_value())
+
+    def set_widget_value(self, value):
+        """Set the value of the widget.
+
+        This will update the view to match the value given. This is called
+        internally, and is called while the proxy is blocked, so no signals
+        are emitted from this action.
+
+        This method should be overriden in subclasses depending on how a
+        widget's value is set.
+        """
+
+    def get_widget_value(self):
+        """Get the widget value.
+
+        This method should be overridden in subclasses to return a value from
+        the widget.
+        """
+
+    def connect_widget(self):
+        """Perform the initial connection of the widget
+
+        the default implementation will connect to the widgets signal
+        based on self.signal_name
+        """
+        if self.signal_name is not None:
+            # None for read only widgets
+            sid = self.widget.connect(self.signal_name, self.widget_changed)
+            self.connections.append(sid)
+
+
+
+class SinglePropertyGObjectProxy(GObjectProxy):
+    """Proxy which uses a single property to set and get the value.
+    """
+    prop_name = None
+
+    def set_widget_value(self, value):
+        return self.widget.set_property(self.prop_name, value)
+
+    def get_widget_value(self):
+        return self.widget.get_property(self.prop_name)
+
+
+class SingleDelegatedPropertyGObjectProxy(SinglePropertyGObjectProxy):
+    """Proxy which uses a delegated property on its widget.
+    """
+    prop_name = None
+    dprop_name = None
+
+    def __init__(self, widget):
+        self.owidget = widget
+        widget = widget.get_property(self.dprop_name)
+        GObjectProxy.__init__(self, widget)
+
+
+class GtkEntryProxy(SinglePropertyGObjectProxy):
+    """Proxy for a gtk.Entry.
+    """
+    prop_name = 'text'
+    signal_name = 'changed'
+
+
+class GtkToggleButtonProxy(SinglePropertyGObjectProxy):
+    """Proxy for a gtk.ToggleButton.
+    """
+    prop_name = 'active'
+    signal_name = 'toggled'
+
+
+class GtkColorButtonProxy(SinglePropertyGObjectProxy):
+    """Proxy for a gtk.ColorButton
+    """
+    prop_name = 'color'
+    signal_name = 'color-set'
+
+
+class StringListProxy(GObjectProxy):
+    """Proxy for a pygtkhelpers.ui.widgets.StringList.
+    """
+    signal_name = 'content-changed'
+
+    def get_widget_value(self):
+        return self.widget.value
+
+    def set_widget_value(self, value):
+        self.widget.value = value
+
+
+class GtkRangeProxy(GObjectProxy):
+    """Base class for widgets employing a gtk.Range.
+    """
+    signal_name = 'value-changed'
+
+    def get_widget_value(self):
+        return self.widget.get_value()
+
+    def set_widget_value(self, value):
+        self.widget.set_value(value)
+
+
+class GtkFileChooserProxy(GObjectProxy):
+    """Proxy for a gtk.FileChooser.
+    """
+    signal_name = 'selection-changed'
+
+    def get_widget_value(self):
+        if self.widget.get_select_multiple():
+            return self.widget.get_filenames()
+        else:
+            return self.widget.get_filename()
+
+    def set_widget_value(self, value):
+        if self.widget.get_select_multiple():
+            self.widget.unselect_all()
+            for filename in value:
+                self.widget.select_file(filename)
+        else:
+            self.widget.set_filename(value)
+
+
+class GtkFontButtonProxy(SinglePropertyGObjectProxy):
+    """Proxy for a gtk.FontButton.
+    """
+    signal_name = 'font-set'
+    prop_name = 'font-name'
+
+
+class GtkComboBoxProxy(GObjectProxy):
+    """Proxy for a gtk.ComboBox.
+    """
+    signal_name = 'changed'
+
+    def get_widget_value(self):
+        if not self.active_row:
+            return
+        return self.get_row_value(self.active_row)
+
+    def set_widget_value(self, value):
+        # what a pain in the arse
+        for i, row in enumerate(self.model):
+            if self.get_row_value(row) == value:
+                self.widget.set_active(i)
+
+    @property
+    def active_row(self):
+        if self.widget.get_active() == -1:
+            return
+        return self.model[self.widget.get_active()]
+
+    @property
+    def model(self):
+        return self.widget.get_model()
+
+    def get_row_value(self, row):
+        row = list(row) #XXX: that sucks
+        value = row[1:]
+        if not value:
+            value = row[0]
+        elif len(value) == 1:
+            value = value[0]
+        return value
+
+
+class GtkTextViewProxy(SingleDelegatedPropertyGObjectProxy):
+    """Proxy for a gtk.TextView.
+    """
+    signal_name = 'changed'
+    prop_name = 'text'
+    dprop_name = 'buffer'
+
+
+class GtkLabelProxy(SinglePropertyGObjectProxy):
+    """Proxy for a gtk.Label.
+    """
+    prop_name = 'label'
+
+
+class GtkImageProxy(SinglePropertyGObjectProxy):
+    """Proxy for a gtk.Image.
+    """
+    prop_name = 'file'
+
+
+class GtkLinkButtonProxy(SinglePropertyGObjectProxy):
+    """Proxy for a gtk.LinkButton.
+    """
+    prop_name = 'uri'
+
+
+class GtkProgressBarProxy(SinglePropertyGObjectProxy):
+    """Proxy for a gtk.ProgressBar.
+    """
+    prop_name = 'fraction'
+
+
+
+widget_proxies = {
+    gtk.Entry: GtkEntryProxy,
+    gtk.ToggleButton: GtkToggleButtonProxy,
+    gtk.CheckButton: GtkToggleButtonProxy,
+    gtk.RadioButton: GtkToggleButtonProxy,
+    gtk.CheckMenuItem: GtkToggleButtonProxy,
+    gtk.ColorButton: GtkColorButtonProxy,
+    gtk.ComboBox: GtkComboBoxProxy,
+    gtk.SpinButton: GtkRangeProxy,
+    gtk.HScale: GtkRangeProxy,
+    gtk.VScale: GtkRangeProxy,
+    gtk.VScrollbar: GtkRangeProxy,
+    gtk.HScrollbar: GtkRangeProxy,
+    gtk.FileChooserButton: GtkFileChooserProxy,
+    gtk.FileChooserWidget: GtkFileChooserProxy,
+    gtk.FontButton: GtkFontButtonProxy,
+    gtk.Label: GtkLabelProxy,
+    gtk.Image: GtkImageProxy,
+    gtk.LinkButton: GtkLinkButtonProxy,
+    gtk.ProgressBar: GtkProgressBarProxy,
+    gtk.TextView: GtkTextViewProxy,
+    StringList: StringListProxy,
+    SimpleComboBox: GtkComboBoxProxy,
+}
+
+def proxy_for(widget):
+    """Create a proxy for a Widget
+
+    :param widget: A gtk.Widget to proxy
+
+    This will raise a KeyError if there is no proxy type registered for the
+    widget type.
+    """
+    proxy_type = widget_proxies.get(widget.__class__)
+    if proxy_type is None:
+        raise KeyError('There is no proxy type registered for %r' % widget)
+    return proxy_type(widget)
+
+
+class ProxyGroup(gobject.GObject):
+    """A controller to handle multiple proxies, and sub-groups
+
+    A ProxyGroup is a bridge to reduce multiple proxies and sub-groups to a
+    single signal based on the key of the individual proxies.
+    """
+
+    gsignal('changed', object, str, object)
+
+    def __init__(self):
+        gobject.GObject.__init__(self)
+
+    def add_proxy(self, name, proxy):
+        """Add a proxy to this group
+
+        :param name: The name or key of the proxy, which will be emitted with
+                     the changed signal
+        :param proxy: The proxy instance to add
+        """
+        proxy.connect('changed', self._on_proxy_changed, name)
+
+    def add_proxy_for(self, name, widget):
+        """Create a proxy for a widget and add it to this group
+
+        :param name: The name or key of the proxy, which will be emitted with
+                     the changed signal
+        :param widget: The widget to create a proxy for
+        """
+        proxy = proxy_for(widget)
+        self.add_proxy(name, proxy)
+
+    def add_group(self, group):
+        """Add an existing group to this group and proxy its signals
+
+        :param group: The ProxyGroup instance to add
+        """
+        group.connect('changed', self._on_group_changed)
+
+    def _on_proxy_changed(self, proxy, value, name):
+        self.emit('changed', proxy, name, value)
+
+    def _on_group_changed(self, group, proxy, value, name):
+        self.emit('changed', proxy, name, value)
+

filesnake/pygtkhelpers/test.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers.test
+    ~~~~~~~~~~~~~~~~~
+
+    Assistance for unittesting pygtk
+
+    :copyright: 2005-2008 by pygtkhelpers Authors
+    :license: LGPL 2 or later (see README/COPYING/LICENSE)
+"""
+
+class CheckCalled(object):
+    """Utility to check whether a signal has been emitted
+
+    :param object: The Object that will fire the signal
+    :param signal: The signal name
+
+    This class should be used when testing whether a signal has been called.
+    It could be used in conjuntion with :func:`pygtkhelpers.utils.refresh_gui`
+    in order to block the UI adequately to check::
+
+        >>> import gtk
+        >>> from pygtkhelpers.utils import refresh_gui
+        >>> b = gtk.Button()
+        >>> check = CheckCalled(b, 'clicked')
+        >>> b.clicked()
+        >>> assert check.called
+        >>> assert check.called_count = 1
+        >>> b.click()
+        >>> assert check.called_count = 2
+
+    """
+    def __init__(self, object, signal):
+        self.called = None
+        self.called_count = 0
+        object.connect(signal, self)
+
+    def __call__(self, *k):
+        self.called = k
+        self.called_count += 1
+

filesnake/pygtkhelpers/ui/__init__.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers.ui
+    ~~~~~~~~~~~~~~~
+
+    UI Components.
+
+    :copyright: 2005-2010 by pygtkhelpers Authors
+    :license: LGPL 2 or later (see README/COPYING/LICENSE)
+"""

filesnake/pygtkhelpers/ui/dialogs.py

+# -*- coding: utf-8 -*-
+
+"""
+    pygtkhelpers.ui.dialogs
+    ~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Dialog helpers
+
+    largely inspired by kiwi.ui.dialogs
+
+    :copyright: 2009-2010 by pygtkhelpers Authors
+    :license: LGPL 2 or later (see README/COPYING/LICENSE)
+"""
+
+#XXX: i18n
+import os
+
+import gtk