<repo_name>0xPr0xy/soundcloud-cli<file_sep>/urwid/widget.py
#!/usr/bin/python
#
# Urwid basic widget classes
#    Copyright (C) 2004-2011  <NAME>
#
#    This library 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.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: http://excess.org/urwid/

from operator import attrgetter

from urwid.util import MetaSuper, decompose_tagmarkup, calc_width, \
    is_wide_char, move_prev_char, move_next_char
from urwid.compat import bytes
from urwid.text_layout import calc_pos, calc_coords, shift_line
from urwid import signals
from urwid import text_layout
from urwid.canvas import CanvasCache, CompositeCanvas, SolidCanvas, \
    apply_text_layout
from urwid.command_map import command_map
from urwid.split_repr import split_repr, remove_defaults, python3_repr


# Widget sizing methods
# (use the same string objects to make some comparisons faster)
FLOW = 'flow'
BOX = 'box'
FIXED = 'fixed'

# Text alignment modes 
LEFT = 'left'
RIGHT = 'right'
CENTER = 'center'

# Filler alignment modes 
TOP = 'top'
MIDDLE = 'middle'
BOTTOM = 'bottom'

# Text wrapping modes
SPACE = 'space'
ANY = 'any'
CLIP = 'clip'

# Extras for Padding
PACK = 'pack'
GIVEN = 'given'
RELATIVE = 'relative'
RELATIVE_100 = (RELATIVE, 100)


class WidgetMeta(MetaSuper, signals.MetaSignals):
    """
    Automatic caching of render and rows methods.

    Class variable no_cache is a list of names of methods to not cache.
    Class variable ignore_focus if defined and True indicates that this
    widget is not affected by the focus parameter, so it may be ignored
    when caching.
    """
    def __init__(cls, name, bases, d):
        no_cache = d.get("no_cache", [])
        
        super(WidgetMeta, cls).__init__(name, bases, d)

        if "render" in d:
            if "render" not in no_cache:
                render_fn = cache_widget_render(cls)
            else:
                render_fn = nocache_widget_render(cls)
            cls.render = render_fn

        if "rows" in d and "rows" not in no_cache:
            cls.rows = cache_widget_rows(cls)
        if "no_cache" in d:
            del cls.no_cache
        if "ignore_focus" in d:
            del cls.ignore_focus

class WidgetError(Exception):
    pass

def validate_size(widget, size, canv):
    """
    Raise a WidgetError if a canv does not match size size.
    """
    if (size and size[1:] != (0,) and size[0] != canv.cols()) or \
        (len(size)>1 and size[1] != canv.rows()):
        raise WidgetError("Widget %r rendered (%d x %d) canvas"
            " when passed size %r!" % (widget, canv.cols(),
            canv.rows(), size))

def update_wrapper(new_fn, fn):
    """
    Copy as much of the function detail from fn to new_fn
    as we can.
    """
    try:
        new_fn.__name__ = fn.__name__
        new_fn.__dict__.update(fn.__dict__)
        new_fn.__doc__ = fn.__doc__
        new_fn.__module__ = fn.__module__
    except TypeError:
        pass # python2.3 ignore read-only attributes


def cache_widget_render(cls):
    """
    Return a function that wraps the cls.render() method
    and fetches and stores canvases with CanvasCache.
    """
    ignore_focus = bool(getattr(cls, "ignore_focus", False))
    fn = cls.render
    def cached_render(self, size, focus=False):
        focus = focus and not ignore_focus
        canv = CanvasCache.fetch(self, cls, size, focus)
        if canv:
            return canv

        canv = fn(self, size, focus=focus)
        validate_size(self, size, canv)
        if canv.widget_info:
            canv = CompositeCanvas(canv)
        canv.finalize(self, size, focus)
        CanvasCache.store(cls, canv)
        return canv
    cached_render.original_fn = fn
    update_wrapper(cached_render, fn)
    return cached_render

def nocache_widget_render(cls):
    """
    Return a function that wraps the cls.render() method
    and finalizes the canvas that it returns.
    """
    fn = cls.render
    if hasattr(fn, "original_fn"):
        fn = fn.original_fn
    def finalize_render(self, size, focus=False):
        canv = fn(self, size, focus=focus)
        if canv.widget_info:
            canv = CompositeCanvas(canv)
        validate_size(self, size, canv)
        canv.finalize(self, size, focus)
        return canv
    finalize_render.original_fn = fn
    update_wrapper(finalize_render, fn)
    return finalize_render

def nocache_widget_render_instance(self):
    """
    Return a function that wraps the cls.render() method
    and finalizes the canvas that it returns, but does not 
    cache the canvas.
    """
    fn = self.render.original_fn
    def finalize_render(size, focus=False):
        canv = fn(self, size, focus=focus)
        if canv.widget_info:
            canv = CompositeCanvas(canv)
        canv.finalize(self, size, focus)
        return canv
    finalize_render.original_fn = fn
    update_wrapper(finalize_render, fn)
    return finalize_render

def cache_widget_rows(cls):
    """
    Return a function that wraps the cls.rows() method
    and returns rows from the CanvasCache if available.
    """
    ignore_focus = bool(getattr(cls, "ignore_focus", False))
    fn = cls.rows
    def cached_rows(self, size, focus=False):
        focus = focus and not ignore_focus
        canv = CanvasCache.fetch(self, cls, size, focus)
        if canv:
            return canv.rows()

        return fn(self, size, focus)
    update_wrapper(cached_rows, fn)
    return cached_rows


class Widget(object):
    """
    base class of widgets
    """
    __metaclass__ = WidgetMeta
    _selectable = False
    _sizing = set([])
    _command_map = command_map # default to the single shared CommandMap

    def _invalidate(self):
        CanvasCache.invalidate(self)

    def _emit(self, name, *args):
        """
        Convenience function to emit signals with self as first
        argument.
        """
        signals.emit_signal(self, name, self, *args)
    
    def selectable(self):
        """
        Return True if this widget should take focus.  Default
        implementation returns the value of self._selectable.
        """
        return self._selectable
    
    def sizing(self):
        """
        Return a set including one or more of 'box', 'flow' and
        'fixed'.  Default implementation returns the value of
        self._sizing.
        """
        return self._sizing

    def pack(self, size, focus=False):
        """
        Return a 'packed' (maxcol, maxrow) for this widget.  Default 
        implementation (no packing defined) returns size, and
        calculates maxrow if not given.
        """
        if size == ():
            if FIXED in self.sizing():
                raise NotImplementedError('Fixed widgets must override'
                    ' Widget.pack()')
            raise WidgetError('Cannot pack () size, this is not a fixed'
                ' widget: %s' % repr(self))
        elif len(size) == 1:
            if FLOW in self.sizing():
                return size + (self.rows(size, focus),)
            raise WidgetError('Cannot pack (maxcol,) size, this is not a'
                ' flow widget: %s' % repr(self))
        return size

    # this property returns the widget without any decorations, default
    # implementation returns self.
    base_widget = property(lambda self:self)
    

    # Use the split_repr module to create __repr__ from _repr_words
    # and _repr_attrs
    __repr__ = split_repr

    def _repr_words(self):
        words = []
        if self.selectable():
            words = ["selectable"] + words
        if self.sizing():
            sizing_modes = list(self.sizing())
            sizing_modes.sort()
            words.append("/".join(sizing_modes))
        return words + ["widget"]

    def _repr_attrs(self):
        return {}
    

class FlowWidget(Widget):
    """
    base class of widgets that determine their rows from the number of
    columns available.
    """
    _sizing = set([FLOW])
    
    def rows(self, size, focus=False):
        """
        All flow widgets must implement this function.
        """
        raise NotImplementedError()

    def render(self, size, focus=False):
        """
        All widgets must implement this function.
        """
        raise NotImplementedError()


class BoxWidget(Widget):
    """
    base class of width and height constrained widgets such as
    the top level widget attached to the display object
    """
    _selectable = True
    _sizing = set([BOX])
    
    def render(self, size, focus=False):
        """
        All widgets must implement this function.
        """
        raise NotImplementedError()
    

def fixed_size(size):
    """
    raise ValueError if size != ().
    
    Used by FixedWidgets to test size parameter.
    """
    if size != ():
        raise ValueError("FixedWidget takes only () for size." \
            "passed: %r" % (size,))

class FixedWidget(Widget):
    """
    base class of widgets that know their width and height and
    cannot be resized
    """
    _sizing = set([FIXED])
    
    def render(self, size, focus=False):
        """
        All widgets must implement this function.
        """
        raise NotImplementedError()
    
    def pack(self, size=None, focus=False):
        """
        All fixed widgets must implement this function.
        """
        raise NotImplementedError()


class Divider(FlowWidget):
    """
    Horizontal divider widget
    """
    ignore_focus = True

    def __init__(self,div_char=u" ",top=0,bottom=0):
        """
        Create a horizontal divider widget.

        div_char -- character to repeat across line
        top -- number of blank lines above
        bottom -- number of blank lines below

        >>> Divider()
        <Divider flow widget>
        >>> Divider(u'-')
        <Divider flow widget '-'>
        >>> Divider(u'x', 1, 2)
        <Divider flow widget 'x' bottom=2 top=1>
        """
        self.__super.__init__()
        self.div_char = div_char
        self.top = top
        self.bottom = bottom

    def _repr_words(self):
        return self.__super._repr_words() + [
            python3_repr(self.div_char)] * (self.div_char != u" ")

    def _repr_attrs(self):
        attrs = dict(self.__super._repr_attrs())
        if self.top: attrs['top'] = self.top
        if self.bottom: attrs['bottom'] = self.bottom
        return attrs

    def rows(self, size, focus=False):
        """
        Return the number of lines that will be rendered.

        >>> Divider().rows((10,))
        1
        >>> Divider(u'x', 1, 2).rows((10,))
        4
        """
        (maxcol,) = size
        return self.top + 1 + self.bottom

    def render(self, size, focus=False):
        """
        Render the divider as a canvas and return it.

        >>> Divider().render((10,)).text # ... = b in Python 3
        [...'          ']
        >>> Divider(u'-', top=1).render((10,)).text
        [...'          ', ...'----------']
        >>> Divider(u'x', bottom=2).render((5,)).text
        [...'xxxxx', ...'     ', ...'     ']
        """
        (maxcol,) = size
        canv = SolidCanvas(self.div_char, maxcol, 1)
        canv = CompositeCanvas(canv)
        if self.top or self.bottom:
            canv.pad_trim_top_bottom(self.top, self.bottom)
        return canv


class SolidFill(BoxWidget):
    _selectable = False
    ignore_focus = True

    def __init__(self, fill_char=" "):
        """
        Create a box widget that will fill an area with a single 
        character.

        fill_char -- character to fill area with

        >>> SolidFill(u'8')
        <SolidFill box widget '8'>
        """
        self.__super.__init__()
        self.fill_char = fill_char

    def _repr_words(self):
        return self.__super._repr_words() + [python3_repr(self.fill_char)]

    def render(self, size, focus=False ):
        """
        Render the Fill as a canvas and return it.

        >>> SolidFill().render((4,2)).text # ... = b in Python 3
        [...'    ', ...'    ']
        >>> SolidFill('#').render((5,3)).text
        [...'#####', ...'#####', ...'#####']
        """
        maxcol, maxrow = size
        return SolidCanvas(self.fill_char, maxcol, maxrow)

class TextError(Exception):
    pass

class Text(FlowWidget):
    """
    a horizontally resizeable text widget
    """
    ignore_focus = True
    _repr_content_length_max = 140

    def __init__(self, markup, align=LEFT, wrap=SPACE, layout=None):
        """
        markup -- content of text widget, one of:
            plain string -- string is displayed
            ( attr, markup2 ) -- markup2 is given attribute attr
            [ markupA, markupB, ... ] -- list items joined together
        align -- align mode for text layout
        wrap -- wrap mode for text layout
        layout -- layout object to use, defaults to StandardTextLayout

        >>> Text(u"Hello")
        <Text flow widget 'Hello'>
        >>> t = Text(('bold', u"stuff"), 'right', 'any')
        >>> t
        <Text flow widget 'stuff' align='right' wrap='any'>
        >>> print t.text
        stuff
        >>> t.attrib
        [('bold', 5)]
        """
        self.__super.__init__()
        self._cache_maxcol = None
        self.set_text(markup)
        self.set_layout(align, wrap, layout)

    def _repr_words(self):
        """
        Show the text in the repr in python3 format (b prefix for byte
        strings) and truncate if it's too long
        """
        first = self.__super._repr_words()
        text = self.get_text()[0]
        rest = python3_repr(text)
        if len(rest) > self._repr_content_length_max:
            rest = (rest[:self._repr_content_length_max * 2 // 3 - 3] +
                '...' + rest[-self._repr_content_length_max // 3:])
        return first + [rest]

    def _repr_attrs(self):
        attrs = dict(self.__super._repr_attrs(),
            align=self._align_mode, 
            wrap=self._wrap_mode)
        return remove_defaults(attrs, Text.__init__)
    
    def _invalidate(self):
        self._cache_maxcol = None
        self.__super._invalidate()

    def set_text(self,markup):
        """
        Set content of text widget.

        markup -- see __init__() for description.

        >>> t = Text(u"foo")
        >>> print t.text
        foo
        >>> t.set_text(u"bar")
        >>> print t.text
        bar
        >>> t.text = u"baz"  # not supported because text stores text but set_text() takes markup
        Traceback (most recent call last):
        AttributeError: can't set attribute
        """
        self._text, self._attrib = decompose_tagmarkup(markup)
        self._invalidate()

    def get_text(self):
        """
        Returns (text, attributes).

        text -- complete string content (unicode) of text widget
        attributes -- run length encoded attributes for text

        >>> Text(u"Hello").get_text() # ... = u in Python 2
        (...'Hello', [])
        >>> Text(('bright', u"Headline")).get_text()
        (...'Headline', [('bright', 8)])
        >>> Text([('a', u"one"), u"two", ('b', u"three")]).get_text()
        (...'onetwothree', [('a', 3), (None, 3), ('b', 5)])
        """
        return self._text, self._attrib

    text = property(lambda self:self.get_text()[0])
    attrib = property(lambda self:self.get_text()[1])

    def set_align_mode(self, mode):
        """
        Set text alignment / justification.

        Valid modes for StandardTextLayout are:
            'left', 'center' and 'right'

        >>> t = Text(u"word")
        >>> t.set_align_mode('right')
        >>> t.align
        'right'
        >>> t.render((10,)).text # ... = b in Python 3
        [...'      word']
        >>> t.align = 'center'
        >>> t.render((10,)).text
        [...'   word   ']
        >>> t.align = 'somewhere'
        Traceback (most recent call last):
        TextError: Alignment mode 'somewhere' not supported.
        """
        if not self.layout.supports_align_mode(mode):
            raise TextError("Alignment mode %r not supported."%
                (mode,))
        self._align_mode = mode
        self._invalidate()

    def set_wrap_mode(self, mode):
        """
        Set wrap mode.

        Valid modes for StandardTextLayout are :
            'any'    : wrap at any character
            'space'    : wrap on space character
            'clip'    : truncate lines instead of wrapping

        >>> t = Text(u"some words")
        >>> t.render((6,)).text # ... = b in Python 3
        [...'some  ', ...'words ']
        >>> t.set_wrap_mode('clip')
        >>> t.wrap
        'clip'
        >>> t.render((6,)).text
        [...'some w']
        >>> t.wrap = 'any'  # Urwid 0.9.9 or later
        >>> t.render((6,)).text
        [...'some w', ...'ords  ']
        >>> t.wrap = 'somehow'
        Traceback (most recent call last):
        TextError: Wrap mode 'somehow' not supported.
        """
        if not self.layout.supports_wrap_mode(mode):
            raise TextError("Wrap mode %r not supported."%(mode,))
        self._wrap_mode = mode
        self._invalidate()

    def set_layout(self, align, wrap, layout=None):
        """
        Set layout object, align and wrap modes.

        align -- align mode for text layout
        wrap -- wrap mode for text layout
        layout -- layout object to use, defaults to StandardTextLayout

        >>> t = Text(u"hi")
        >>> t.set_layout('right', 'clip')
        >>> t
        <Text flow widget 'hi' align='right' wrap='clip'>
        """
        if layout is None:
            layout = text_layout.default_layout
        self._layout = layout
        self.set_align_mode(align)
        self.set_wrap_mode(wrap)

    align = property(lambda self:self._align_mode, set_align_mode)
    wrap = property(lambda self:self._wrap_mode, set_wrap_mode)
    layout = property(lambda self:self._layout)

    def render(self, size, focus=False):
        """
        Render contents with wrapping and alignment.  Return canvas.

        >>> Text(u"important things").render((18,)).text # ... = b in Python 3
        [...'important things  ']
        >>> Text(u"important things").render((11,)).text
        [...'important  ', ...'things     ']
        """
        (maxcol,) = size
        text, attr = self.get_text()
        #assert isinstance(text, unicode)
        trans = self.get_line_translation( maxcol, (text,attr) )
        return apply_text_layout(text, attr, trans, maxcol)

    def rows(self, size, focus=False):
        """
        Return the number of rows the rendered text spans.

        >>> Text(u"important things").rows((18,))
        1
        >>> Text(u"important things").rows((11,))
        2
        """
        (maxcol,) = size
        return len(self.get_line_translation(maxcol))

    def get_line_translation(self, maxcol, ta=None):
        """
        Return layout structure used to map self.text to a canvas.
        This method is used internally, but may be useful for
        debugging custom layout classes.

        maxcol -- columns available for display
        ta -- None or the (text, attr) tuple returned from
              self.get_text()
        """
        if not self._cache_maxcol or self._cache_maxcol != maxcol:
            self._update_cache_translation(maxcol, ta)
        return self._cache_translation

    def _update_cache_translation(self,maxcol, ta):
        if ta:
            text, attr = ta
        else:
            text, attr = self.get_text()
        self._cache_maxcol = maxcol
        self._cache_translation = self._calc_line_translation(
            text, maxcol )

    def _calc_line_translation(self, text, maxcol ):
        return self.layout.layout(
            text, self._cache_maxcol, 
            self._align_mode, self._wrap_mode )

    def pack(self, size=None, focus=False):
        """
        Return the number of screen columns and rows required for
        this Text widget to be displayed without wrapping or 
        clipping, as a single element tuple.

        size -- None for unlimited screen columns or (maxcol,) to
                specify a maximum column size

        >>> Text(u"important things").pack()
        (16, 1)
        >>> Text(u"important things").pack((15,))
        (9, 2)
        >>> Text(u"important things").pack((8,))
        (8, 2)
        """
        text, attr = self.get_text()

        if size is not None:
            (maxcol,) = size
            if not hasattr(self.layout, "pack"):
                return size
            trans = self.get_line_translation( maxcol, (text,attr))
            cols = self.layout.pack( maxcol, trans )
            return (cols, len(trans))

        i = 0
        cols = 0
        while i < len(text):
            j = text.find('\n', i)
            if j == -1:
                j = len(text)
            c = calc_width(text, i, j)
            if c>cols:
                cols = c
            i = j+1
        return (cols, text.count('\n') + 1)


class EditError(TextError):
    pass


class Edit(Text):
    """
    Text editing widget implements cursor movement, text insertion and
    deletion.  A caption may prefix the editing area.  Uses text class
    for text layout.
    """

    # allow users of this class to listen for change events
    # sent when the value of edit_text changes
    # (this variable is picked up by the MetaSignals metaclass)
    signals = ["change"]

    def valid_char(self, ch):
        """Return true for printable characters."""
        return is_wide_char(ch,0) or (len(ch)==1 and ord(ch) >= 32)

    def selectable(self): return True

    def __init__(self, caption=u"", edit_text=u"", multiline=False,
            align=LEFT, wrap=SPACE, allow_tab=False,
            edit_pos=None, layout=None, mask=None):
        """
        caption -- markup for caption preceeding edit_text
        edit_text -- text string for editing
        multiline -- True: 'enter' inserts newline  False: return it
        align -- align mode
        wrap -- wrap mode
        allow_tab -- True: 'tab' inserts 1-8 spaces  False: return it
        edit_pos -- initial position for cursor, None:at end
        layout -- layout object
        mask -- character to mask away text with, None means no masking

        >>> Edit()
        <Edit selectable flow widget '' edit_pos=0>
        >>> Edit(u"Y/n? ", u"yes")
        <Edit selectable flow widget 'yes' caption='Y/n? ' edit_pos=3>
        >>> Edit(u"Name ", u"Smith", edit_pos=1)
        <Edit selectable flow widget 'Smith' caption='Name ' edit_pos=1>
        >>> Edit(u"", u"3.14", align='right')
        <Edit selectable flow widget '3.14' align='right' edit_pos=4>
        """

        self.__super.__init__("", align, wrap, layout)
        self.multiline = multiline
        self.allow_tab = allow_tab
        self._edit_pos = 0
        self.set_caption(caption)
        self.set_edit_text(edit_text)
        if edit_pos is None:
            edit_pos = len(edit_text)
        self.set_edit_pos(edit_pos)
        self.set_mask(mask)
        self._shift_view_to_cursor = False

    def _repr_words(self):
        return self.__super._repr_words()[:-1] + [
            python3_repr(self._edit_text)] + [
            'caption=' + python3_repr(self._caption)] * bool(self._caption) + [
            'multiline'] * (self.multiline is True)

    def _repr_attrs(self):
        attrs = dict(self.__super._repr_attrs(),
            edit_pos=self._edit_pos)
        return remove_defaults(attrs, Edit.__init__)

    def get_text(self):
        """
        Returns (text, attributes).

        text -- complete text of caption and edit_text, maybe masked away
        attributes -- run length encoded attributes for text

        >>> Edit("What? ","oh, nothing.").get_text() # ... = u in Python 2
        (...'What? oh, nothing.', [])
        >>> Edit(('bright',"user@host:~$ "),"ls").get_text()
        (...'user@host:~$ ls', [('bright', 13)])
        """

        if self._mask is None:
            return self._caption + self._edit_text, self._attrib
        else:
            return self._caption + (self._mask * len(self._edit_text)), self._attrib
    
    def set_text(self, markup):
        """
        Not supported by Edit widget.

        >>> Edit().set_text("test")
        Traceback (most recent call last):
        EditError: set_text() not supported.  Use set_caption() or set_edit_text() instead.
        """
        # hack to let Text.__init__() work
        if not hasattr(self, '_text') and markup == "":
            self._text = None
            return

        raise EditError("set_text() not supported.  Use set_caption()"
            " or set_edit_text() instead.")

    def get_pref_col(self, size):
        """
        Return the preferred column for the cursor, or the
        current cursor x value.  May also return 'left' or 'right'
        to indicate the leftmost or rightmost column available.

        This method is used internally and by other widgets when
        moving the cursor up or down between widgets so that the 
        column selected is one that the user would expect.

        >>> size = (10,)
        >>> Edit().get_pref_col(size)
        0
        >>> e = Edit("","word")
        >>> e.get_pref_col(size)
        4
        >>> e.keypress(size, 'left')
        >>> e.get_pref_col(size)
        3
        >>> e.keypress(size, 'end')
        >>> e.get_pref_col(size)
        'right'
        >>> e = Edit("","2\\nwords")
        >>> e.keypress(size, 'left')
        >>> e.keypress(size, 'up')
        >>> e.get_pref_col(size)
        4
        >>> e.keypress(size, 'left')
        >>> e.get_pref_col(size)
        0
        """
        (maxcol,) = size
        pref_col, then_maxcol = self.pref_col_maxcol
        if then_maxcol != maxcol:
            return self.get_cursor_coords((maxcol,))[0]
        else:
            return pref_col
    
    def update_text(self):
        """
        No longer supported.
        
        >>> Edit().update_text()
        Traceback (most recent call last):
        EditError: update_text() has been removed.  Use set_caption() or set_edit_text() instead.
        """
        raise EditError("update_text() has been removed.  Use "
            "set_caption() or set_edit_text() instead.")

    def set_caption(self, caption):
        """
        Set the caption markup for this widget.

        caption -- see Text.__init__() for description of markup

        >>> e = Edit("")
        >>> e.set_caption("cap1")
        >>> print e.caption
        cap1
        >>> e.set_caption(('bold', "cap2"))
        >>> print e.caption
        cap2
        >>> e.attrib
        [('bold', 4)]
        >>> e.caption = "cap3"  # not supported because caption stores text but set_caption() takes markup
        Traceback (most recent call last):
        AttributeError: can't set attribute
        """
        self._caption, self._attrib = decompose_tagmarkup(caption)
        self._invalidate()

    caption = property(lambda self:self._caption)

    def set_edit_pos(self, pos):
        """
        Set the cursor position with a self.edit_text offset.  
        Clips pos to [0, len(edit_text)].

        >>> e = Edit(u"", u"word")
        >>> e.edit_pos
        4
        >>> e.set_edit_pos(2)
        >>> e.edit_pos
        2
        >>> e.edit_pos = -1  # Urwid 0.9.9 or later
        >>> e.edit_pos
        0
        >>> e.edit_pos = 20
        >>> e.edit_pos
        4
        """
        if pos < 0:
            pos = 0
        if pos > len(self._edit_text):
            pos = len(self._edit_text)
        self.highlight = None
        self.pref_col_maxcol = None, None
        self._edit_pos = pos
        self._invalidate()
    
    edit_pos = property(lambda self:self._edit_pos, set_edit_pos)

    def set_mask(self, mask):
        """
        Set the character for masking text away. Empty means no masking.
        """

        self._mask = mask
        self._invalidate()

    def set_edit_text(self, text):
        """
        Set the edit text for this widget.

        >>> e = Edit()
        >>> e.set_edit_text(u"yes")
        >>> print e.edit_text
        yes
        >>> e
        <Edit selectable flow widget 'yes' edit_pos=0>
        >>> e.edit_text = u"no"  # Urwid 0.9.9 or later
        >>> print e.edit_text
        no
        """
        try:
            text = unicode(text)
        except Exception:
            raise EditError("Can't convert edit text to a string!")
        self.highlight = None
        self._emit("change", text)
        self._edit_text = text
        if self.edit_pos > len(text):
            self.edit_pos = len(text)
        self._invalidate()

    def get_edit_text(self):
        """
        Return the edit text for this widget.

        >>> e = Edit(u"What? ", u"oh, nothing.")
        >>> print e.get_edit_text()
        oh, nothing.
        >>> print e.edit_text
        oh, nothing.
        """
        return self._edit_text

    edit_text = property(get_edit_text, set_edit_text)

    def insert_text(self, text):
        """
        Insert text at the cursor position and update cursor.
        This method is used by the keypress() method when inserting
        one or more characters into edit_text.

        >>> e = Edit(u"", u"42")
        >>> e.insert_text(u".5")
        >>> e
        <Edit selectable flow widget '42.5' edit_pos=4>
        >>> e.set_edit_pos(2)
        >>> e.insert_text(u"a")
        >>> print e.edit_text
        42a.5
        """
        text = self._normalize_to_caption(text)
        result_text, result_pos = self.insert_text_result(text)
        self.set_edit_text(result_text)
        self.set_edit_pos(result_pos)
        self.highlight = None

    def _normalize_to_caption(self, text):
        """
        Return text converted to the same type as self.caption
        (bytes or unicode)
        """
        tu = isinstance(text, unicode)
        cu = isinstance(self._caption, unicode)
        if tu == cu:
            return text
        if tu:
            return text.encode('ascii') # follow python2's implicit conversion
        return text.decode('ascii')

    def insert_text_result(self, text):
        """
        Return result of insert_text(text) without actually performing the
        insertion.  Handy for pre-validation.
        """

        # if there's highlighted text, it'll get replaced by the new text
        text = self._normalize_to_caption(text)
        if self.highlight:
            start, stop = self.highlight
            btext, etext = self.edit_text[:start], self.edit_text[stop:]
            result_text =  btext + etext
            result_pos = start
        else:
            result_text = self.edit_text
            result_pos = self.edit_pos


        result_text = (result_text[:result_pos] + text +
            result_text[result_pos:])
        result_pos += len(text)
        return (result_text, result_pos)

    def keypress(self, size, key):
        """
        Handle editing keystrokes, return others.

        >>> e, size = Edit(), (20,)
        >>> e.keypress(size, 'x')
        >>> e.keypress(size, 'left')
        >>> e.keypress(size, '1')
        >>> print e.edit_text
        1x
        >>> e.keypress(size, 'backspace')
        >>> e.keypress(size, 'end')
        >>> e.keypress(size, '2')
        >>> print e.edit_text
        x2
        >>> e.keypress(size, 'shift f1')
        'shift f1'
        """
        (maxcol,) = size

        p = self.edit_pos
        if self.valid_char(key):
            self.insert_text( key )

        elif key=="tab" and self.allow_tab:
            key = " "*(8-(self.edit_pos%8))
            self.insert_text( key )

        elif key=="enter" and self.multiline:
            key = "\n"
            self.insert_text( key )

        elif self._command_map[key] == 'cursor left':
            if p==0: return key
            p = move_prev_char(self.edit_text,0,p)
            self.set_edit_pos(p)
        
        elif self._command_map[key] == 'cursor right':
            if p >= len(self.edit_text): return key
            p = move_next_char(self.edit_text,p,len(self.edit_text))
            self.set_edit_pos(p)
        
        elif self._command_map[key] in ('cursor up', 'cursor down'):
            self.highlight = None
            
            x,y = self.get_cursor_coords((maxcol,))
            pref_col = self.get_pref_col((maxcol,))
            assert pref_col is not None
            #if pref_col is None: 
            #    pref_col = x

            if self._command_map[key] == 'cursor up': y -= 1
            else: y += 1

            if not self.move_cursor_to_coords((maxcol,),pref_col,y):
                return key
        
        elif key=="backspace":
            self.pref_col_maxcol = None, None
            if not self._delete_highlighted():
                if p == 0: return key
                p = move_prev_char(self.edit_text,0,p)
                self.set_edit_text( self.edit_text[:p] + 
                    self.edit_text[self.edit_pos:] )
                self.set_edit_pos( p )

        elif key=="delete":
            self.pref_col_maxcol = None, None
            if not self._delete_highlighted():
                if p >= len(self.edit_text):
                    return key
                p = move_next_char(self.edit_text,p,len(self.edit_text))
                self.set_edit_text( self.edit_text[:self.edit_pos] + 
                    self.edit_text[p:] )
        
        elif self._command_map[key] in ('cursor max left', 'cursor max right'):
            self.highlight = None
            self.pref_col_maxcol = None, None
            
            x,y = self.get_cursor_coords((maxcol,))
            
            if self._command_map[key] == 'cursor max left':
                self.move_cursor_to_coords((maxcol,), LEFT, y)
            else:
                self.move_cursor_to_coords((maxcol,), RIGHT, y)
            return
            
            
        else:
            # key wasn't handled
            return key

    def move_cursor_to_coords(self, size, x, y):
        """
        Set the cursor position with (x,y) coordinates.
        Returns True if move succeeded, False otherwise.
        
        >>> size = (10,)
        >>> e = Edit("","edit\\ntext")
        >>> e.move_cursor_to_coords(size, 5, 0)
        True
        >>> e.edit_pos
        4
        >>> e.move_cursor_to_coords(size, 5, 3)
        False
        >>> e.move_cursor_to_coords(size, 0, 1)
        True
        >>> e.edit_pos
        5
        """
        (maxcol,) = size
        trans = self.get_line_translation(maxcol)
        top_x, top_y = self.position_coords(maxcol, 0)
        if y < top_y or y >= len(trans):
            return False

        pos = calc_pos( self.get_text()[0], trans, x, y )
        e_pos = pos - len(self.caption)
        if e_pos < 0: e_pos = 0
        if e_pos > len(self.edit_text): e_pos = len(self.edit_text)
        self.edit_pos = e_pos
        self.pref_col_maxcol = x, maxcol
        self._invalidate()
        return True

    def mouse_event(self, size, event, button, x, y, focus):
        """
        Move the cursor to the location clicked for button 1.

        >>> size = (20,)
        >>> e = Edit("","words here")
        >>> e.mouse_event(size, 'mouse press', 1, 2, 0, True)
        True
        >>> e.edit_pos
        2
        """
        (maxcol,) = size
        if button==1:
            return self.move_cursor_to_coords( (maxcol,), x, y )


    def _delete_highlighted(self):
        """
        Delete all highlighted text and update cursor position, if any
        text is highlighted.
        """
        if not self.highlight: return
        start, stop = self.highlight
        btext, etext = self.edit_text[:start], self.edit_text[stop:]
        self.set_edit_text( btext + etext )
        self.edit_pos = start
        self.highlight = None
        return True


    def render(self, size, focus=False):
        """
        Render edit widget and return canvas.  Include cursor when in
        focus.

        >>> c = Edit("? ","yes").render((10,), focus=True)
        >>> c.text # ... = b in Python 3
        [...'? yes     ']
        >>> c.cursor
        (5, 0)
        """
        (maxcol,) = size
        self._shift_view_to_cursor = bool(focus)

        canv = Text.render(self,(maxcol,))
        if focus:
            canv = CompositeCanvas(canv)
            canv.cursor = self.get_cursor_coords((maxcol,))

        # .. will need to FIXME if I want highlight to work again
        #if self.highlight:
        #    hstart, hstop = self.highlight_coords()
        #    d.coords['highlight'] = [ hstart, hstop ]
        return canv

    
    def get_line_translation(self, maxcol, ta=None ):
        trans = Text.get_line_translation(self, maxcol, ta)
        if not self._shift_view_to_cursor: 
            return trans
        
        text, ignore = self.get_text()
        x,y = calc_coords( text, trans, 
            self.edit_pos + len(self.caption) )
        if x < 0:
            return ( trans[:y]
                + [shift_line(trans[y],-x)]
                + trans[y+1:] )
        elif x >= maxcol:
            return ( trans[:y] 
                + [shift_line(trans[y],-(x-maxcol+1))]
                + trans[y+1:] )
        return trans
            

    def get_cursor_coords(self, size):
        """
        Return the (x,y) coordinates of cursor within widget.
        
        >>> Edit("? ","yes").get_cursor_coords((10,))
        (5, 0)
        """
        (maxcol,) = size

        self._shift_view_to_cursor = True
        return self.position_coords(maxcol,self.edit_pos)
    
    
    def position_coords(self,maxcol,pos):
        """
        Return (x,y) coordinates for an offset into self.edit_text.
        """
        
        p = pos + len(self.caption)
        trans = self.get_line_translation(maxcol)
        x,y = calc_coords(self.get_text()[0], trans,p)
        return x,y

        




class IntEdit(Edit):
    """Edit widget for integer values"""

    def valid_char(self, ch):
        """
        Return true for decimal digits.
        """
        return len(ch)==1 and ch in "0123456789"

    def __init__(self,caption="",default=None):
        """
        caption -- caption markup
        default -- default edit value

        >>> IntEdit(u"", 42)
        <IntEdit selectable flow widget '42' edit_pos=2>
        """
        if default is not None: val = str(default)
        else: val = ""
        self.__super.__init__(caption,val)

    def keypress(self, size, key):
        """
        Handle editing keystrokes.  Remove leading zeros.

        >>> e, size = IntEdit(u"", 5002), (10,)
        >>> e.keypress(size, 'home')
        >>> e.keypress(size, 'delete')
        >>> print e.edit_text
        002
        >>> e.keypress(size, 'end')
        >>> print e.edit_text
        2
        """
        (maxcol,) = size
        unhandled = Edit.keypress(self,(maxcol,),key)

        if not unhandled:
        # trim leading zeros
            while self.edit_pos > 0 and self.edit_text[:1] == "0":
                self.set_edit_pos( self.edit_pos - 1)
                self.set_edit_text(self.edit_text[1:])

        return unhandled

    def value(self):
        """
        Return the numeric value of self.edit_text.

        >>> e, size = IntEdit(), (10,)
        >>> e.keypress(size, '5')
        >>> e.keypress(size, '1')
        >>> e.value() == 51
        True
        """
        if self.edit_text:
            return long(self.edit_text)
        else:
            return 0


def delegate_to_widget_mixin(attribute_name):
    """
    Return a mixin class that delegates all standard widget methods
    to an attribute given by attribute_name.

    This mixin is designed to be used as a superclass of another widget.
    """
    # FIXME: this is so common, let's add proper support for it
    # when layout and rendering are separated

    get_delegate = attrgetter(attribute_name)
    class DelegateToWidgetMixin(Widget):
        no_cache = ["rows"] # crufty metaclass work-around

        def render(self, size, focus=False):
            canv = get_delegate(self).render(size, focus=focus)
            return CompositeCanvas(canv)

        selectable = property(lambda self:get_delegate(self).selectable)
        get_cursor_coords = property(
            lambda self:get_delegate(self).get_cursor_coords)
        get_pref_col = property(lambda self:get_delegate(self).get_pref_col)
        keypress = property(lambda self:get_delegate(self).keypress)
        move_cursor_to_coords = property(
            lambda self:get_delegate(self).move_cursor_to_coords)
        rows = property(lambda self:get_delegate(self).rows)
        mouse_event = property(lambda self:get_delegate(self).mouse_event)
        sizing = property(lambda self:get_delegate(self).sizing)
        pack = property(lambda self:get_delegate(self).pack)
    return DelegateToWidgetMixin



class WidgetWrapError(Exception):
    pass

class WidgetWrap(delegate_to_widget_mixin('_wrapped_widget'), Widget):
    def __init__(self, w):
        """
        w -- widget to wrap, stored as self._w

        This object will pass the functions defined in Widget interface
        definition to self._w.

        The purpose of this widget is to provide a base class for
        widgets that compose other widgets for their display and
        behaviour.  The details of that composition should not affect
        users of the subclass.  The subclass may decide to expose some
        of the wrapped widgets by behaving like a ContainerWidget or
        WidgetDecoration, or it may hide them from outside access.
        """
        self._wrapped_widget = w

    def _set_w(self, w):
        """
        Change the wrapped widget.  This is meant to be called
        only by subclasses.

        >>> size = (10,)
        >>> ww = WidgetWrap(Edit("hello? ","hi"))
        >>> ww.render(size).text # ... = b in Python 3
        [...'hello? hi ']
        >>> ww.selectable()
        True
        >>> ww._w = Text("goodbye") # calls _set_w()
        >>> ww.render(size).text
        [...'goodbye   ']
        >>> ww.selectable()
        False
        """
        self._wrapped_widget = w
        self._invalidate()
    _w = property(lambda self:self._wrapped_widget, _set_w)

    def _raise_old_name_error(self, val=None):
        raise WidgetWrapError("The WidgetWrap.w member variable has "
            "been renamed to WidgetWrap._w (not intended for use "
            "outside the class and its subclasses).  "
            "Please update your code to use self._w "
            "instead of self.w.")
    w = property(_raise_old_name_error, _raise_old_name_error)



def _test():
    import doctest
    doctest.testmod()

if __name__=='__main__':
    _test()
<file_sep>/urwid/container.py
#!/usr/bin/python
#
# Urwid container widget classes
#    Copyright (C) 2004-2011  <NAME>
#
#    This library 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.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: http://excess.org/urwid/

from urwid.util import is_mouse_press
from urwid.widget import Widget, BoxWidget, FlowWidget, Divider
from urwid.decoration import Padding, Filler, calculate_padding, calculate_filler, \
    decompose_align_width, decompose_valign_height
from urwid.monitored_list import MonitoredList
from urwid.canvas import CompositeCanvas, CanvasOverlay, CanvasCombine, \
    SolidCanvas, CanvasJoin


class WidgetContainer(Widget):
    def __init__(self, widget_list):
        self.__super.__init__()
        self._widget_list = MonitoredList([])
        self._set_widget_list(widget_list)
        self._widget_list.set_modified_callback(self._invalidate)


    def _get_widget_list(self):
        return self._widget_list
    def _set_widget_list(self, widget_list):
        """
        widget_list -- iterable containing widgets

        Copy the values from widget_list into self.widget_list 
        """
    widget_list = property(_get_widget_list, _set_widget_list)

    def __getitem__(self, index):
        """
        Return the base widget of the widget at self.widget_list[index].
        """
        w = self._widget_list[index]
        if hasattr(w, 'base_widget'):
            w = w.base_widget
        return w

    def __len__(self):
        return len(self._widget_list)

    def __iter__(self):
        i = 0
        try:
            while True:
                v = self[i]
                yield v
                i += 1
        except IndexError:
            return

    def __contains__(self, value):
        for v in self:
            if v == value:
                return True
        return False

    def __reversed__(self):
        for i in reversed(range(len(self))):
            yield self[i]

    def index(self, value):
        for i, v in enumerate(self):
            if v == value:
                return i
        raise ValueError

    def count(self, value):
        return sum(1 for v in self if v == value)




class GridFlow(FlowWidget):

    def selectable(self): 
        """Return True if the cell in focus is selectable."""
        return self.focus_cell and self.focus_cell.selectable()
        
    def __init__(self, cells, cell_width, h_sep, v_sep, align):
        """
        cells -- list of flow widgets to display
        cell_width -- column width for each cell
        h_sep -- blank columns between each cell horizontally
        v_sep -- blank rows between cells vertically (if more than
                 one row is required to display all the cells)
        align -- horizontal alignment of cells, see "align" parameter
                 of Padding widget for available options
        """
        self.__super.__init__()
        self.cells = cells
        self.cell_width = cell_width
        self.h_sep = h_sep
        self.v_sep = v_sep
        self.align = align
        self.focus_cell = None
        if cells:
            self.focus_cell = cells[0]
        self._cache_maxcol = None

    def set_focus(self, cell):
        """Set the cell in focus.  
        
        cell -- widget or integer index into self.cells"""
        if type(cell) == int:
            assert cell>=0 and cell<len(self.cells)
            self.focus_cell = self.cells[cell]
        else:
            assert cell in self.cells
            self.focus_cell = cell
        self._cache_maxcol = None
        self._invalidate()
        

    def get_focus (self):
        """Return the widget in focus."""
        return self.focus_cell

    def get_display_widget(self, size):
        """
        Arrange the cells into columns (and possibly a pile) for 
        display, input or to calculate rows. 
        """
        (maxcol,) = size
        # use cache if possible
        if self._cache_maxcol == maxcol:
            return self._cache_display_widget

        self._cache_maxcol = maxcol
        self._cache_display_widget = self.generate_display_widget(
            size)

        return self._cache_display_widget

    def generate_display_widget(self, size):
        """
        Actually generate display widget (ignoring cache)
        """
        (maxcol,) = size
        d = Divider()
        if len(self.cells) == 0: # how dull
            return d

        if self.v_sep > 1:
            # increase size of divider
            d.top = self.v_sep-1

        # cells per row
        bpr = (maxcol+self.h_sep) // (self.cell_width+self.h_sep)

        if bpr == 0: # too narrow, pile them on top of eachother
            l = [self.cells[0]]
            f = 0
            for b in self.cells[1:]:
                if b is self.focus_cell:
                    f = len(l)
                if self.v_sep:
                    l.append(d)
                l.append(b)
            return Pile(l, f)
        
        if bpr >= len(self.cells): # all fit on one row
            k = len(self.cells)
            f = self.cells.index(self.focus_cell)
            cols = Columns(self.cells, self.h_sep, f)
            rwidth = (self.cell_width+self.h_sep)*k - self.h_sep
            row = Padding(cols, self.align, rwidth)
            return row

        
        out = []
        s = 0
        f = 0
        while s < len(self.cells):
            if out and self.v_sep:
                out.append(d)
            k = min( len(self.cells), s+bpr )
            cells = self.cells[s:k]
            if self.focus_cell in cells:
                f = len(out)
                fcol = cells.index(self.focus_cell)
                cols = Columns(cells, self.h_sep, fcol)
            else:
                cols = Columns(cells, self.h_sep)
            rwidth = (self.cell_width+self.h_sep)*(k-s)-self.h_sep
            row = Padding(cols, self.align, rwidth)
            out.append(row)
            s += bpr
        return Pile(out, f)    
    
    def _set_focus_from_display_widget(self, w):
        """Set the focus to the item in focus in the display widget."""
        if isinstance(w, Padding):
            # unwrap padding
            w = w._original_widget
        w = w.get_focus()
        if w in self.cells:
            self.set_focus(w)
            return
        if isinstance(w, Padding):
            # unwrap padding
            w = w._original_widget
        w = w.get_focus()
        #assert w == self.cells[0], repr((w, self.cells))
        self.set_focus(w)

    def keypress(self, size, key):
        """
        Pass keypress to display widget for handling.  
        Capture    focus changes."""
        
        d = self.get_display_widget(size)
        if not d.selectable():
            return key
        key = d.keypress(size, key)
        if key is None:
            self._set_focus_from_display_widget(d)
        return key

    def rows(self, size, focus=False):
        """Return rows used by this widget."""
        d = self.get_display_widget(size)
        return d.rows(size, focus=focus)
    
    def render(self, size, focus=False ):
        """Use display widget to render."""
        d = self.get_display_widget(size)
        return d.render(size, focus)

    def get_cursor_coords(self, size):
        """Get cursor from display widget."""
        d = self.get_display_widget(size)
        if not d.selectable():
            return None
        return d.get_cursor_coords(size)
    
    def move_cursor_to_coords(self, size, col, row ):
        """Set the widget in focus based on the col + row."""
        d = self.get_display_widget(size)
        if not d.selectable():
            # happy is the default
            return True
        
        r =  d.move_cursor_to_coords(size, col, row)
        if not r:
            return False
        
        self._set_focus_from_display_widget(d)
        self._invalidate()
        return True
    
    def mouse_event(self, size, event, button, col, row, focus):
        """Send mouse event to contained widget."""
        d = self.get_display_widget(size)
        
        r = d.mouse_event(size, event, button, col, row, focus)
        if not r:
            return False
        
        self._set_focus_from_display_widget(d)
        self._invalidate()
        return True
        
    
    def get_pref_col(self, size):
        """Return pref col from display widget."""
        d = self.get_display_widget(size)
        if not d.selectable():
            return None
        return d.get_pref_col(size)
    

        
class OverlayError(Exception):
    pass

class Overlay(BoxWidget):
    def __init__(self, top_w, bottom_w, align, width, valign, height,
            min_width=None, min_height=None ):
        """
        top_w -- a flow, box or fixed widget to overlay "on top"
        bottom_w -- a box widget to appear "below" previous widget
        align -- one of:
            'left', 'center', 'right'
            ('fixed left', columns)
            ('fixed right', columns)
            ('relative', percentage 0=left 100=right)
        width -- one of:
            None if top_w is a fixed widget
            number of columns wide
            ('fixed right', columns)  Only if align is 'fixed left'
            ('fixed left', columns)  Only if align is 'fixed right'
            ('relative', percentage of total width)
        valign -- one of:
            'top', 'middle', 'bottom'
            ('fixed top', rows)
            ('fixed bottom', rows)
            ('relative', percentage 0=top 100=bottom)
        height -- one of:
            None if top_w is a flow or fixed widget
            number of rows high
            ('fixed bottom', rows)  Only if valign is 'fixed top'
            ('fixed top', rows)  Only if valign is 'fixed bottom'
            ('relative', percentage of total height)
        min_width -- the minimum number of columns for top_w
            when width is not fixed
        min_height -- one of:
            minimum number of rows for the widget when height not fixed

        Overlay widgets behave similarly to Padding and Filler widgets
        when determining the size and position of top_w.  bottom_w is
        always rendered the full size available "below" top_w.
        """
        self.__super.__init__()

        self.top_w = top_w
        self.bottom_w = bottom_w

        self.set_overlay_parameters(align, width, valign, height,
            min_width, min_height)

    def set_overlay_parameters(self, align, width, valign, height,
            min_width=None, min_height=None):
        """
        Adjust the overlay size and position parameters.

        See __init__() for a description of the parameters.
        """
        at,aa,wt,wa=decompose_align_width(align, width, OverlayError)
        vt,va,ht,ha=decompose_valign_height(valign,height,OverlayError)

        self.align_type, self.align_amount = at, aa
        self.width_type, self.width_amount = wt, wa
        if self.width_type and self.width_type != 'fixed':
            self.min_width = min_width
        else:
            self.min_width = None

        self.valign_type, self.valign_amount = vt, va
        self.height_type, self.height_amount = ht, ha
        if self.height_type not in ('fixed', None):
            self.min_height = min_height
        else:
            self.min_height = None
        self._invalidate()

    def selectable(self):
        """Return selectable from top_w."""
        return self.top_w.selectable()
    
    def keypress(self, size, key):
        """Pass keypress to top_w."""
        return self.top_w.keypress(self.top_w_size(size,
                       *self.calculate_padding_filler(size, True)), key)
    
    def get_cursor_coords(self, size):
        """Return cursor coords from top_w, if any."""
        if not hasattr(self.body, 'get_cursor_coords'):
            return None
        (maxcol, maxrow) = size
        left, right, top, bottom = self.calculate_padding_filler(size,
            True)
        x, y = self.top_w.get_cursor_coords(
            (maxcol-left-right, maxrow-top-bottom) )
        if y >= maxrow:  # required??
            y = maxrow-1
        return x+left, y+top
    
    def calculate_padding_filler(self, size, focus):
        """Return (padding left, right, filler top, bottom)."""
        (maxcol, maxrow) = size
        height = None
        if self.width_type is None:
            # top_w is a fixed widget
            width, height = self.top_w.pack((),focus=focus)
            assert height, "fixed widget must have a height"
            left, right = calculate_padding(self.align_type,
                self.align_amount, 'fixed', width, 
                None, maxcol, clip=True )
        else:
            left, right = calculate_padding(self.align_type,
                self.align_amount, self.width_type,
                self.width_amount, self.min_width, maxcol)

        if height:
            # top_w is a fixed widget
            top, bottom = calculate_filler(self.valign_type, 
                self.valign_amount, 'fixed', height,
                None, maxrow)
            if maxrow-top-bottom < height:
                bottom = maxrow-top-height
        elif self.height_type is None:
            # top_w is a flow widget
            height = self.top_w.rows((maxcol,),focus=focus)
            top, bottom =  calculate_filler( self.valign_type,
                self.valign_amount, 'fixed', height, 
                None, maxrow )
            if height > maxrow: # flow widget rendered too large
                bottom = maxrow - height
        else:
            top, bottom = calculate_filler(self.valign_type, 
                self.valign_amount, self.height_type, 
                self.height_amount, self.min_height, maxrow)
        return left, right, top, bottom
    
    def top_w_size(self, size, left, right, top, bottom):
        """Return the size to pass to top_w."""
        if self.width_type is None:
            # top_w is a fixed widget
            return ()
        maxcol, maxrow = size
        if self.width_type is not None and self.height_type is None:
            # top_w is a flow widget
            return (maxcol-left-right,)
        return (maxcol-left-right, maxrow-top-bottom)
            
    
    def render(self, size, focus=False):
        """Render top_w overlayed on bottom_w."""
        left, right, top, bottom = self.calculate_padding_filler(size,
            focus)
        bottom_c = self.bottom_w.render(size)
        top_c = self.top_w.render(
            self.top_w_size(size, left, right, top, bottom), focus)
        top_c = CompositeCanvas(top_c)
        if left<0 or right<0:
            top_c.pad_trim_left_right(min(0,left), min(0,right))
        if top<0 or bottom<0:
            top_c.pad_trim_top_bottom(min(0,top), min(0,bottom))
        
        return CanvasOverlay(top_c, bottom_c, left, top)


    def mouse_event(self, size, event, button, col, row, focus):
        """Pass event to top_w, ignore if outside of top_w."""
        if not hasattr(self.top_w, 'mouse_event'):
            return False
        
        left, right, top, bottom = self.calculate_padding_filler(size,
            focus)
        maxcol, maxrow = size
        if ( col<left or col>=maxcol-right or
            row<top or row>=maxrow-bottom ):
            return False
            
        return self.top_w.mouse_event(
            self.top_w_size(size, left, right, top, bottom),
            event, button, col-left, row-top, focus )
    

class Frame(BoxWidget):
    def __init__(self, body, header=None, footer=None, focus_part='body'):
        """
        body -- a box widget for the body of the frame
        header -- a flow widget for above the body (or None)
        footer -- a flow widget for below the body (or None)
        focus_part -- 'header', 'footer' or 'body'
        """
        self.__super.__init__()

        self._header = header
        self._body = body
        self._footer = footer
        self.focus_part = focus_part
    
    def get_header(self):
        return self._header
    def set_header(self, header):
        self._header = header
        self._invalidate()
    header = property(get_header, set_header)
        
    def get_body(self):
        return self._body
    def set_body(self, body):
        self._body = body
        self._invalidate()
    body = property(get_body, set_body)

    def get_footer(self):
        return self._footer
    def set_footer(self, footer):
        self._footer = footer
        self._invalidate()
    footer = property(get_footer, set_footer)

    def set_focus(self, part):
        """Set the part of the frame that is in focus.

        part -- 'header', 'footer' or 'body'
        """
        assert part in ('header', 'footer', 'body')
        self.focus_part = part
        self._invalidate()

    def get_focus (self):
        """Return the part of the frame that is in focus.

        Will be one of 'header', 'footer' or 'body'.
        """
        return self.focus_part

    def frame_top_bottom(self, size, focus):
        """Calculate the number of rows for the header and footer.

        Returns (head rows, foot rows),(orig head, orig foot).
        orig head/foot are from rows() calls.
        """
        (maxcol, maxrow) = size
        frows = hrows = 0
        
        if self.header:
            hrows = self.header.rows((maxcol,),
                self.focus_part=='header' and focus)
        
        if self.footer:
            frows = self.footer.rows((maxcol,),
                self.focus_part=='footer' and focus)
        
        remaining = maxrow
        
        if self.focus_part == 'footer':
            if frows >= remaining:
                return (0, remaining),(hrows, frows)
                
            remaining -= frows
            if hrows >= remaining:
                return (remaining, frows),(hrows, frows)

        elif self.focus_part == 'header':
            if hrows >= maxrow:
                return (remaining, 0),(hrows, frows)
            
            remaining -= hrows
            if frows >= remaining:
                return (hrows, remaining),(hrows, frows)

        elif hrows + frows >= remaining:
            # self.focus_part == 'body'
            rless1 = max(0, remaining-1)
            if frows >= remaining-1:
                return (0, rless1),(hrows, frows)
            
            remaining -= frows
            rless1 = max(0, remaining-1)
            return (rless1,frows),(hrows, frows)
        
        return (hrows, frows),(hrows, frows)
        
    

    def render(self, size, focus=False):
        """Render frame and return it."""
        (maxcol, maxrow) = size
        (htrim, ftrim),(hrows, frows) = self.frame_top_bottom(
            (maxcol, maxrow), focus)
        
        combinelist = []
        depends_on = []
        
        head = None
        if htrim and htrim < hrows:
            head = Filler(self.header, 'top').render(
                (maxcol, htrim), 
                focus and self.focus_part == 'header')
        elif htrim:
            head = self.header.render((maxcol,),
                focus and self.focus_part == 'header')
            assert head.rows() == hrows, "rows, render mismatch"
        if head:
            combinelist.append((head, 'header', 
                self.focus_part == 'header'))
            depends_on.append(self.header)

        if ftrim+htrim < maxrow:
            body = self.body.render((maxcol, maxrow-ftrim-htrim),
                focus and self.focus_part == 'body')
            combinelist.append((body, 'body', 
                self.focus_part == 'body'))
            depends_on.append(self.body)
        
        foot = None    
        if ftrim and ftrim < frows:
            foot = Filler(self.footer, 'bottom').render(
                (maxcol, ftrim), 
                focus and self.focus_part == 'footer')
        elif ftrim:
            foot = self.footer.render((maxcol,),
                focus and self.focus_part == 'footer')
            assert foot.rows() == frows, "rows, render mismatch"
        if foot:
            combinelist.append((foot, 'footer', 
                self.focus_part == 'footer'))
            depends_on.append(self.footer)

        return CanvasCombine(combinelist)


    def keypress(self, size, key):
        """Pass keypress to widget in focus."""
        (maxcol, maxrow) = size
        
        if self.focus_part == 'header' and self.header is not None:
            if not self.header.selectable():
                return key
            return self.header.keypress((maxcol,),key) 
        if self.focus_part == 'footer' and self.footer is not None:
            if not self.footer.selectable():
                return key
            return self.footer.keypress((maxcol,),key)
        if self.focus_part != 'body':
            return key
        remaining = maxrow
        if self.header is not None:
            remaining -= self.header.rows((maxcol,))
        if self.footer is not None:
            remaining -= self.footer.rows((maxcol,))
        if remaining <= 0: return key
    
        if not self.body.selectable():
            return key
        return self.body.keypress( (maxcol, remaining), key )


    def mouse_event(self, size, event, button, col, row, focus):
        """
        Pass mouse event to appropriate part of frame.
        Focus may be changed on button 1 press.
        """
        (maxcol, maxrow) = size
        (htrim, ftrim),(hrows, frows) = self.frame_top_bottom(
            (maxcol, maxrow), focus)
        
        if row < htrim: # within header
            focus = focus and self.focus_part == 'header'
            if is_mouse_press(event) and button==1:
                if self.header.selectable():
                    self.set_focus('header')
            if not hasattr(self.header, 'mouse_event'):
                return False
            return self.header.mouse_event( (maxcol,), event,
                button, col, row, focus )
        
        if row >= maxrow-ftrim: # within footer
            focus = focus and self.focus_part == 'footer'
            if is_mouse_press(event) and button==1:
                if self.footer.selectable():
                    self.set_focus('footer')
            if not hasattr(self.footer, 'mouse_event'):
                return False
            return self.footer.mouse_event( (maxcol,), event,
                button, col, row-maxrow+frows, focus )
        
        # within body
        focus = focus and self.focus_part == 'body'
        if is_mouse_press(event) and button==1:
            if self.body.selectable():
                self.set_focus('body')
        
        if not hasattr(self.body, 'mouse_event'):
            return False
        return self.body.mouse_event( (maxcol, maxrow-htrim-ftrim),
            event, button, col, row-htrim, focus )

        

class PileError(Exception):
    pass
        
class Pile(Widget): # either FlowWidget or BoxWidget
    def __init__(self, widget_list, focus_item=None):
        """
        widget_list -- list of widgets
        focus_item -- widget or integer index, if None the first
            selectable widget will be chosen.

        widget_list may also contain tuples such as:
        ('flow', widget) always treat widget as a flow widget
        ('fixed', height, widget) give this box widget a fixed height
        ('weight', weight, widget) if the pile is treated as a box
            widget then treat widget as a box widget with a
            height based on its relative weight value, otherwise
            treat widget as a flow widget
        
        widgets not in a tuple are the same as ('weight', 1, widget)

        If the pile is treated as a box widget there must be at least
        one 'weight' tuple in widget_list.
        """
        self.__super.__init__()
        self.widget_list = MonitoredList(widget_list)
        self.item_types = []
        for i in range(len(widget_list)):
            w = widget_list[i]
            if type(w) != tuple:
                self.item_types.append(('weight',1))
            elif w[0] == 'flow':
                f, widget = w
                self.widget_list[i] = widget
                self.item_types.append((f,None))
                w = widget
            elif w[0] in ('fixed', 'weight'):
                f, height, widget = w
                self.widget_list[i] = widget
                self.item_types.append((f,height))
                w = widget
            else:
                raise PileError, "widget list item invalid %r" % (w,)
            if focus_item is None and w.selectable():
                focus_item = i
        self.widget_list.set_modified_callback(self._invalidate)
        
        if focus_item is None:
            focus_item = 0
        if self.widget_list:
            self.set_focus(focus_item)
        else:
            self.focus_item=None
        self.pref_col = 0

    @property
    def contents(self):
        for i, w in enumerate(self.widget_list):
            try:
                yield w, self.item_types[i]
            except IndexError:
                yield w, ('weight', 1)

    def _get_item_types(self, i):
        try:
            return self.item_types[i]
        except IndexError:
            return 'weight', 1

    def selectable(self):
        """Return True if the focus item is selectable."""
        return self.focus_item is not None and self.focus_item.selectable()

    def set_focus(self, item):
        """Set the item in focus.  
        
        item -- widget or integer index"""
        if type(item) == int:
            assert item>=0 and item<len(self.widget_list)
            self.focus_item = self.widget_list[item]
        else:
            assert item in self.widget_list
            self.focus_item = item
        self._invalidate()

    def get_focus(self):
        """Return the widget in focus."""
        return self.focus_item

    def get_pref_col(self, size):
        """Return the preferred column for the cursor, or None."""
        if not self.selectable():
            return None
        self._update_pref_col_from_focus(size)
        return self.pref_col
        
    def get_item_size(self, size, i, focus, item_rows=None):
        """
        Return a size appropriate for passing to self.widget_list[i]
        """
        maxcol = size[0]
        f, height = self._get_item_types(i)
        if f=='fixed':
            return (maxcol, height)
        elif f=='weight' and len(size)==2:
            if not item_rows:
                item_rows = self.get_item_rows(size, focus)
            return (maxcol, item_rows[i])
        else:
            return (maxcol,)

    def get_item_rows(self, size, focus):
        """
        Return a list of the number of rows used by each widget
        in self.item_list.
        """
        remaining = None
        maxcol = size[0]
        if len(size)==2:
            remaining = size[1]

        l = []

        if remaining is None:
            # pile is a flow widget
            for w, (f, height) in self.contents:
                if f == 'fixed':
                    l.append(height)
                else:
                    l.append(w.rows((maxcol,), focus=focus
                        and self.focus_item == w))
            return l

        # pile is a box widget
        # do an extra pass to calculate rows for each widget
        wtotal = 0
        for w, (f, height) in self.contents:
            if f == 'flow':
                rows = w.rows((maxcol,), focus=focus and
                    self.focus_item == w )
                l.append(rows)
                remaining -= rows
            elif f == 'fixed':
                l.append(height)
                remaining -= height
            else:
                l.append(None)
                wtotal += height

        if wtotal == 0:
            raise PileError, "No weighted widgets found for Pile treated as a box widget"

        if remaining < 0: 
            remaining = 0

        for i, (w, (f, height)) in enumerate(self.contents):
            li = l[i]
            if li is None:
                rows = int(float(remaining)*height
                    /wtotal+0.5)
                l[i] = rows
                remaining -= rows
                wtotal -= height
        return l

    def render(self, size, focus=False):
        """
        Render all widgets in self.widget_list and return the results
        stacked one on top of the next.
        """
        maxcol = size[0]
        item_rows = None

        combinelist = []
        for i, (w, (f, height)) in enumerate(self.contents):
            item_focus = self.focus_item == w
            canv = None
            if f == 'fixed':
                canv = w.render( (maxcol, height),
                    focus=focus and item_focus)
            elif f == 'flow' or len(size)==1:
                canv = w.render( (maxcol,), 
                    focus=focus and    item_focus)
            else:    
                if item_rows is None:
                    item_rows = self.get_item_rows(size, 
                        focus)
                rows = item_rows[i]
                if rows>0:
                    canv = w.render( (maxcol, rows),
                        focus=focus and    item_focus )
            if canv:
                combinelist.append((canv, i, item_focus))
        if not combinelist:
            return SolidCanvas(" ", size[0], (size[1:]+(0,))[0])

        out = CanvasCombine(combinelist)
        if len(size)==2 and size[1] < out.rows():
            # flow/fixed widgets rendered too large
            out = CompositeCanvas(out)
            out.pad_trim_top_bottom(0, size[1] - out.rows())
        return out
    
    def get_cursor_coords(self, size):
        """Return the cursor coordinates of the focus widget."""
        if not self.focus_item.selectable():
            return None
        if not hasattr(self.focus_item,'get_cursor_coords'):
            return None
        
        i = self.widget_list.index(self.focus_item)
        f, height = self._get_item_types(i)
        item_rows = None
        maxcol = size[0]
        if f == 'fixed' or (f=='weight' and len(size)==2):
            if f == 'fixed':
                maxrow = height
            else:
                if item_rows is None:
                    item_rows = self.get_item_rows(size, 
                    focus=True)
                maxrow = item_rows[i]
            coords = self.focus_item.get_cursor_coords(
                (maxcol,maxrow))
        else:
            coords = self.focus_item.get_cursor_coords((maxcol,))

        if coords is None:
            return None
        x,y = coords
        if i > 0:
            if item_rows is None:
                item_rows = self.get_item_rows(size, focus=True)
            for r in item_rows[:i]:
                y += r
        return x, y

    def rows(self, size, focus=False ):
        """Return the number of rows required for this widget."""
        return sum(self.get_item_rows(size, focus))

    def keypress(self, size, key ):
        """Pass the keypress to the widget in focus.
        Unhandled 'up' and 'down' keys may cause a focus change."""

        item_rows = None
        if len(size)==2:
            item_rows = self.get_item_rows( size, focus=True )

        i = self.widget_list.index(self.focus_item)
        f, height = self._get_item_types(i)
        if self.focus_item.selectable():
            tsize = self.get_item_size(size,i,True,item_rows)
            key = self.focus_item.keypress( tsize, key )
            if self._command_map[key] not in ('cursor up', 'cursor down'):
                return key

        if self._command_map[key] == 'cursor up':
            candidates = range(i-1, -1, -1) # count backwards to 0
        else: # self._command_map[key] == 'cursor down'
            candidates = range(i+1, len(self.widget_list))

        if not item_rows:
            item_rows = self.get_item_rows( size, focus=True )

        for j in candidates:
            if not self.widget_list[j].selectable():
                continue

            self._update_pref_col_from_focus(size)
            self.set_focus(j)
            if not hasattr(self.focus_item,'move_cursor_to_coords'):
                return

            f, height = self._get_item_types(i)
            rows = item_rows[j]
            if self._command_map[key] == 'cursor up':
                rowlist = range(rows-1, -1, -1)
            else: # self._command_map[key] == 'cursor down'
                rowlist = range(rows)
            for row in rowlist:
                tsize=self.get_item_size(size,j,True,item_rows)
                if self.focus_item.move_cursor_to_coords(
                        tsize,self.pref_col,row):
                    break
            return

        # nothing to select
        return key


    def _update_pref_col_from_focus(self, size ):
        """Update self.pref_col from the focus widget."""
        
        widget = self.focus_item

        if not hasattr(widget,'get_pref_col'):
            return
        i = self.widget_list.index(widget)
        tsize = self.get_item_size(size,i,True)
        pref_col = widget.get_pref_col(tsize)
        if pref_col is not None: 
            self.pref_col = pref_col

    def move_cursor_to_coords(self, size, col, row):
        """Capture pref col and set new focus."""
        self.pref_col = col
        
        #FIXME guessing focus==True
        focus=True
        wrow = 0 
        item_rows = self.get_item_rows(size,focus)
        for r,w in zip(item_rows, self.widget_list):
            if wrow+r > row:
                break
            wrow += r

        if not w.selectable():
            return False
        
        if hasattr(w,'move_cursor_to_coords'):
            i = self.widget_list.index(w)
            tsize = self.get_item_size(size, i, focus, item_rows)
            rval = w.move_cursor_to_coords(tsize,col,row-wrow)
            if rval is False:
                return False
            
        self.set_focus(w)
        return True
    
    def mouse_event(self, size, event, button, col, row, focus):
        """
        Pass the event to the contained widget.
        May change focus on button 1 press.
        """
        wrow = 0
        item_rows = self.get_item_rows(size,focus)
        for r,w in zip(item_rows, self.widget_list):
            if wrow+r > row:
                break
            wrow += r

        focus = focus and self.focus_item == w
        if is_mouse_press(event) and button==1:
            if w.selectable():
                self.set_focus(w)
        
        if not hasattr(w,'mouse_event'):
            return False

        i = self.widget_list.index(w)
        tsize = self.get_item_size(size, i, focus, item_rows)
        return w.mouse_event(tsize, event, button, col, row-wrow,
            focus)



class ColumnsError(Exception):
    pass


class Columns(Widget): # either FlowWidget or BoxWidget
    def __init__(self, widget_list, dividechars=0, focus_column=None,
        min_width=1, box_columns=None):
        """
        widget_list -- list of flow widgets or list of box widgets
        dividechars -- blank characters between columns
        focus_column -- index into widget_list of column in focus,
            if None the first selectable widget will be chosen.
        min_width -- minimum width for each column which is not
            designated as flow widget in widget_list.
        box_columns -- a list of column indexes containing box widgets
            whose maxrow is set to the maximum of the rows
            required by columns not listed in box_columns.

        widget_list may also contain tuples such as:
        ('flow', widget) always treat widget as a flow widget
        ('fixed', width, widget) give this column a fixed width
        ('weight', weight, widget) give this column a relative weight

        widgets not in a tuple are the same as ('weight', 1, widget)

        box_columns is ignored when this widget is being used as a
        box widget because in that case all columns are treated as box
        widgets.
        """
        self.__super.__init__()
        self.widget_list = MonitoredList(widget_list)
        self.column_types = []
        self.has_flow_type = False
        for i in range(len(widget_list)):
            w = widget_list[i]
            if type(w) != tuple:
                self.column_types.append(('weight',1))
            elif w[0] == 'flow':
                f, widget = w
                self.widget_list[i] = widget
                self.column_types.append((f,None))
                self.has_flow_type = True
                w = widget
            elif w[0] in ('fixed', 'weight'):
                f,width,widget = w
                self.widget_list[i] = widget
                self.column_types.append((f,width))
                w = widget
            else:
                raise ColumnsError, "widget list item invalid: %r" % (w,)
            if focus_column is None and w.selectable():
                focus_column = i

        self.widget_list.set_modified_callback(self._invalidate)

        self.dividechars = dividechars
        if focus_column is None:
            focus_column = 0
        self.focus_col = focus_column
        self.pref_col = None
        self.min_width = min_width
        self.box_columns = box_columns
        self._cache_maxcol = None

    @property
    def contents(self):
        for i, w in enumerate(self.widget_list):
            try:
                yield w, self.column_types[i]
            except IndexError:
                yield w, ('weight', 1)

    def _invalidate(self):
        self._cache_maxcol = None
        self.__super._invalidate()

    def set_focus_column( self, num ):
        """Set the column in focus by its index in self.widget_list."""
        self.focus_col = num
        self._invalidate()
    
    def get_focus_column( self ):
        """Return the focus column index."""
        return self.focus_col

    def set_focus(self, item):
        """Set the item in focus.  
        
        item -- widget or integer index"""
        if type(item) == int:
            assert item>=0 and item<len(self.widget_list)
            position = item
        else:
            position = self.widget_list.index(item)
        self.focus_col = position
        self._invalidate()
    
    def get_focus(self):
        """Return the widget in focus."""
        return self.widget_list[self.focus_col]

    def column_widths(self, size, focus=False):
        """Return a list of column widths.

        size -- (maxcol,) if self.widget_list contains flow widgets or
            (maxcol, maxrow) if it contains box widgets.
        """
        maxcol = size[0]
        if maxcol == self._cache_maxcol and not self.has_flow_type:
            return self._cache_column_widths

        widths=[]

        weighted = []
        shared = maxcol + self.dividechars

        for i, (w, (t, width)) in enumerate(self.contents):
            if t == 'fixed':
                static_w = width
            elif t == 'flow':
                static_w = w.pack((maxcol,), focus)[0]
            else:
                static_w = self.min_width

            if shared < static_w + self.dividechars:
                break

            widths.append(static_w)
            shared -= static_w + self.dividechars
            if t not in ('fixed', 'flow'):
                weighted.append((width,i))

        if shared:
            # divide up the remaining space between weighted cols
            weighted.sort()
            wtotal = sum([weight for weight,i in weighted])
            grow = shared + len(weighted)*self.min_width
            for weight, i in weighted:
                width = int(float(grow) * weight / wtotal + 0.5)
                width = max(self.min_width, width)
                widths[i] = width
                grow -= width
                wtotal -= weight

        self._cache_maxcol = maxcol
        self._cache_column_widths = widths
        return widths

    def render(self, size, focus=False):
        """Render columns and return canvas.

        size -- (maxcol,) if self.widget_list contains flow widgets or
            (maxcol, maxrow) if it contains box widgets.
        """
        widths = self.column_widths(size, focus)
        if not widths:
            return SolidCanvas(" ", size[0], (size[1:]+(1,))[0])
        
        box_maxrow = None
        if len(size)==1 and self.box_columns:
            box_maxrow = 1
            # two-pass mode to determine maxrow for box columns
            for i in range(len(widths)):
                if i in self.box_columns:
                    continue
                mc = widths[i]
                w = self.widget_list[i]
                rows = w.rows( (mc,), 
                    focus = focus and self.focus_col == i )
                box_maxrow = max(box_maxrow, rows)
        
        l = []
        for i in range(len(widths)):
            mc = widths[i]

            # if the widget has a width of 0, hide it
            if mc <= 0:
                continue

            w = self.widget_list[i]
            if box_maxrow and i in self.box_columns:
                sub_size = (mc, box_maxrow)
            else:
                sub_size = (mc,) + size[1:]
            
            canv = w.render(sub_size, 
                focus = focus and self.focus_col == i)

            if i < len(widths)-1:
                mc += self.dividechars
            l.append((canv, i, self.focus_col == i, mc))
                
        canv = CanvasJoin(l)
        if canv.cols() < size[0]:
            canv.pad_trim_left_right(0, size[0]-canv.cols())
        return canv

    def get_cursor_coords(self, size):
        """Return the cursor coordinates from the focus widget."""
        w = self.widget_list[self.focus_col]

        if not w.selectable():
            return None
        if not hasattr(w, 'get_cursor_coords'):
            return None

        widths = self.column_widths( size )
        if len(widths) < self.focus_col+1:
            return None
        colw = widths[self.focus_col]

        coords = w.get_cursor_coords( (colw,)+size[1:] )
        if coords is None:
            return None
        x,y = coords
        x += self.focus_col * self.dividechars
        x += sum( widths[:self.focus_col] )
        return x, y

    def move_cursor_to_coords(self, size, col, row):
        """Choose a selectable column to focus based on the coords."""
        widths = self.column_widths(size)
        
        best = None
        x = 0
        for i in range(len(widths)):
            w = self.widget_list[i]
            end = x + widths[i]
            if w.selectable():
                # sometimes, col == 'left' - that doesn't seem like its handled here, does it?
                # assert isinstance(x, int) and isinstance(col, int), (x, col)
                if x > col and best is None:
                    # no other choice
                    best = i, x, end
                    break
                if x > col and col-best[2] < x-col:
                    # choose one on left
                    break
                best = i, x, end
                if col < end:
                    # choose this one
                    break
            x = end + self.dividechars
            
        if best is None:
            return False
        i, x, end = best
        w = self.widget_list[i]
        if hasattr(w,'move_cursor_to_coords'):
            if type(col)==int:
                move_x = min(max(0,col-x),end-x-1)
            else:
                move_x = col
            rval = w.move_cursor_to_coords((end-x,)+size[1:],
                move_x, row)
            if rval is False:
                return False
                
        self.focus_col = i
        self.pref_col = col
        self._invalidate()
        return True

    def mouse_event(self, size, event, button, col, row, focus):
        """
        Send event to appropriate column.
        May change focus on button 1 press.
        """
        widths = self.column_widths(size)
        
        x = 0
        for i in range(len(widths)):
            if col < x:
                return False
            w = self.widget_list[i]
            end = x + widths[i]
            
            if col >= end:
                x = end + self.dividechars
                continue

            focus = focus and self.focus_col == i
            if is_mouse_press(event) and button == 1:
                if w.selectable():
                    self.set_focus(w)

            if not hasattr(w,'mouse_event'):
                return False

            return w.mouse_event((end-x,)+size[1:], event, button, 
                col - x, row, focus)
        return False
        
    def get_pref_col(self, size):
        """Return the pref col from the column in focus."""
        maxcol = size[0]
        widths = self.column_widths( (maxcol,) )
    
        w = self.widget_list[self.focus_col]
        if len(widths) < self.focus_col+1:
            return 0
        col = None
        if hasattr(w,'get_pref_col'):
            col = w.get_pref_col((widths[self.focus_col],)+size[1:])
            if type(col)==int:
                col += self.focus_col * self.dividechars
                col += sum( widths[:self.focus_col] )
        if col is None:
            col = self.pref_col
        if col is None and w.selectable():
            col = widths[self.focus_col] // 2
            col += self.focus_col * self.dividechars
            col += sum( widths[:self.focus_col] )
        return col

    def rows(self, size, focus=0 ):
        """Return the number of rows required by the columns.
        Only makes sense if self.widget_list contains flow widgets."""
        widths = self.column_widths(size, focus)
    
        rows = 1
        for i in range(len(widths)):
            if self.box_columns and i in self.box_columns:
                continue
            mc = widths[i]
            w = self.widget_list[i]
            rows = max( rows, w.rows( (mc,), 
                focus = focus and self.focus_col == i ) )
        return rows
            
    def keypress(self, size, key):
        """Pass keypress to the focus column.

        size -- (maxcol,) if self.widget_list contains flow widgets or
            (maxcol, maxrow) if it contains box widgets.
        """
        if self.focus_col is None: return key
        
        widths = self.column_widths( size )
        if self.focus_col < 0 or self.focus_col >= len(widths):
            return key

        i = self.focus_col
        mc = widths[i]
        w = self.widget_list[i]
        if self._command_map[key] not in ('cursor up', 'cursor down',
            'cursor page up', 'cursor page down'):
            self.pref_col = None
        key = w.keypress( (mc,)+size[1:], key )
        
        if self._command_map[key] not in ('cursor left', 'cursor right'):
            return key

        if self._command_map[key] == 'cursor left':
            candidates = range(i-1, -1, -1) # count backwards to 0
        else: # key == 'right'
            candidates = range(i+1, len(widths))

        for j in candidates:
            if not self.widget_list[j].selectable():
                continue

            self.set_focus_column( j )
            return
        return key
            

    def selectable(self):
        """Return the selectable value of the focus column."""
        return self.widget_list[self.focus_col].selectable()






def _test():
    import doctest
    doctest.testmod()

if __name__=='__main__':
    _test()
<file_sep>/soundcloud_player/soundcloud_player.py
# http://excess.org/urwid/reference.html
import vlc
# http://liris.cnrs.fr/advene/download/python-ctypes/doc/
import os
import sys

# close you eyes 
sys.path.append('%s/../' % sys.path[0] )

import urwid

class SoundCloudPlayer:
	
	
	def __init__(self, playlist):
		
		self.palette = [
			('banner', '', '', '', '#fff', '#333'),
			('streak', '', '', '', '#fff', '#333'),
			('bg', '', '', '', '', '#666'),
		]
		name = os.getlogin() or None
		txt = urwid.Text(('banner', u"Welcome %s!\n\n0xPr0xy Soundcloud Player!" %name), align='center')
		map1 = urwid.AttrMap(txt, 'streak')
		fill = urwid.Filler(map1)
		self.map2 = urwid.AttrMap(fill, 'bg')
		self.loop = urwid.MainLoop(self.map2, self.palette, unhandled_input=self.handle_input)
		self.loop.screen.set_terminal_properties(colors=256)
		self.create_player(playlist)


	def handle_input(self,input):
		
		if input in ('q', 'Q'):
			self.player.stop()
			raise urwid.ExitMainLoop()
		if input in ('p', 'P'):
			self.player.pause()
		if input in ('r', 'R'):
			self.player.stop()
			self.player.play()
		if input in (']','}'):
			self.player.next()
		if input in ('{', '['):
			self.player.previous()
	

	def create_player(self, playlist):
		
		instanceParameters = [
			'--quiet',
			'--ignore-config',
			'--sout-keep',
			'--sout-all',
			'--vout=caca'
		]
		self.instance=vlc.Instance(instanceParameters)
		self.medialist = self.instance.media_list_new()
		for item in playlist: 
			self.medialist.add_media(self.instance.media_new(item))
		self.player = self.instance.media_list_player_new()
		self.player.set_media_list(self.medialist)
		self.player.play()
		self.loop.run()<file_sep>/soundcloud.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import soundcloud
import sys
import os
import urwid
import requests
from soundcloud_player.soundcloud_player import SoundCloudPlayer

class ItemWidget (urwid.WidgetWrap):

	def __init__ (self, entry, url):
		""" Creates UI Element for every Entry"""
		if entry is not None:
			self._selectable = entry
			self.content = url.url
			self.item = [
				urwid.Padding(urwid.AttrWrap(
				urwid.Text('%s' % entry),  'body', 'focus')),
			]
		w = urwid.Columns(self.item)
		self.__super.__init__(w)

	def selectable (self):
		return True

	def keypress(self, size, key):
		return key


class SoundCloud:

	def __init__(self, query, track, playlist):
		
		self.playlist = []
		self.palette = [
			('body','dark cyan', '', 'standout'),
			('focus','dark red', '', 'standout'),
			('head','light red', 'black'),
		]
		self.client = soundcloud.Client(client_id='570f56acefe61658492d4ee040a0a0cd')
		if query: self.query = query
		if track: self.findTracks()
		if playlist: self.findPlaylist()


	def play(self, url):
		playlist = []
		try:
			r = requests.get(url)
			playlist.append(r.url)
			SoundCloudPlayer(playlist)
		except Exception as e:
			print e


		#os.system('./stream/__main__.py %s' %url)

	def download(self, url, title):
		try:
			print '\nDownloading: %s' %title
			r = requests.get(str(url))
			with open('%s.mp3' % title, 'wb')  as code:
				code.write(r.content)
			print('\nDownload Complete: %s.mp3' % title)
		except Exception as e:
			print e


	def initGui(self):
		self.listbox = urwid.ListBox(urwid.SimpleListWalker(self.playlist))
		self.view = urwid.Frame(urwid.AttrWrap(self.listbox, 'body'))
		self.loop = urwid.MainLoop(self.view, self.palette, unhandled_input=self.keystroke)
		self.loop.run()


	def findTracks(self):
		tracks = self.client.get('/tracks', q=self.query, licence='cc-by-sa')
		for track in tracks: 
			url = self.client.get(track.stream_url, allow_redirects=False)
			self.playlist.append(ItemWidget(track.title, url))
		self.initGui()


	def findPlaylist(self):
		playlist = self.client.get('/playlists/' + self.query)
		for track in playlist.tracks: 
			url = self.client.get(track['stream_url'], allow_redirects=False)
			self.playlist.append(ItemWidget(track['title'], url))
		self.initGui()


	def keystroke (self,input):
		""" Handle Keystrokes """
		
		if input in ('q', 'Q'):
			raise urwid.ExitMainLoop()

		if input is 'enter':
			try:
				self.focus = self.listbox.get_focus()[0].content
			except Exception as e:
				print('listbox get_focus failed:\nError: %s' % e)
			self.play(self.focus)

		if input is ' ':
			title = self.listbox.get_focus()[0]._selectable
			try:
				self.focus = self.listbox.get_focus()[0].content
			except Exception as e:
				print('listbox get_focus failed:\nError: %s' % e)
			self.download(self.focus, title)
			

if len(sys.argv) == 3 and sys.argv[2] == 'track': instance = SoundCloud(sys.argv[1], True, False)
if len(sys.argv) == 3 and sys.argv[2] == 'playlist': instance = SoundCloud(sys.argv[1], False, True)
<file_sep>/README.txt
This is a soundcloud client for the command line that supports searching and streaming soundcloud songs from the command line.


Requirements:
--------------
VLC media player
Python 2.6+


Commands:
---------
./soundcloud.py 'artist' track
./soundcloud.py 'artist-album' playlist

Controls:
---------
Enter = Stream
SPACE = Download
Q = Quit


Enjoy!

0xPr0xy


<file_sep>/urwid/decoration.py
#!/usr/bin/python
#
# Urwid widget decoration classes
#    Copyright (C) 2004-2011  <NAME>
#
#    This library 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.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: http://excess.org/urwid/


from urwid.util import int_scale
from urwid.widget import Widget, WidgetError, \
    BOX, FLOW, LEFT, CENTER, RIGHT, PACK, CLIP, GIVEN, RELATIVE, RELATIVE_100, \
    delegate_to_widget_mixin
from urwid.split_repr import remove_defaults
from urwid.canvas import CompositeCanvas, SolidCanvas
from urwid.widget import Divider, Edit, Text, SolidFill # doctests


class WidgetDecoration(Widget):  # "decorator" was already taken
    def __init__(self, original_widget):
        """
        original_widget -- the widget being decorated

        This is a base class for decoration widgets, widgets
        that contain one or more widgets and only ever have
        a single focus.  This type of widget will affect the
        display or behaviour of the original_widget but it is
        not part of determining a chain of focus.

        Don't actually do this -- use a WidgetDecoration subclass
        instead, these are not real widgets:
        >>> WidgetDecoration(Text(u"hi"))
        <WidgetDecoration flow widget <Text flow widget 'hi'>>
        """
        self._original_widget = original_widget
    def _repr_words(self):
        return self.__super._repr_words() + [repr(self._original_widget)]
    
    def _get_original_widget(self):
        return self._original_widget
    def _set_original_widget(self, original_widget):
        self._original_widget = original_widget
        self._invalidate()
    original_widget = property(_get_original_widget, _set_original_widget)

    def _get_base_widget(self):
        """
        Return the widget without decorations.  If there is only one
        Decoration then this is the same as original_widget.

        >>> t = Text('hello')
        >>> wd1 = WidgetDecoration(t)
        >>> wd2 = WidgetDecoration(wd1)
        >>> wd3 = WidgetDecoration(wd2)
        >>> wd3.original_widget is wd2
        True
        >>> wd3.base_widget is t
        True
        """
        w = self
        while hasattr(w, '_original_widget'):
            w = w._original_widget
        return w
    
    base_widget = property(_get_base_widget)

    def selectable(self):
        return self._original_widget.selectable()

    def sizing(self):
        return self._original_widget.sizing()


class WidgetPlaceholder(delegate_to_widget_mixin('_original_widget'),
        WidgetDecoration):
    """
    This is a do-nothing decoration widget that can be used for swapping
    between widgets without modifying the container of this widget.

    This can be useful for making an interface with a number of distinct
    pages or for showing and hiding menu or status bars.

    The widget displayed is stored as a .original_widget property and
    can be changed by assigning a new widget to it.
    """
    pass


class AttrMapError(WidgetError):
    pass

class AttrMap(delegate_to_widget_mixin('_original_widget'), WidgetDecoration):
    """
    AttrMap is a decoration that maps one set of attributes to another for
    a FlowWidget or BoxWidget
    """
    def __init__(self, w, attr_map, focus_map=None):
        """
        w -- widget to wrap (stored as self.original_widget)
        attr_map -- attribute to apply to w, or dictionary of attribute mappings
        focus_map -- attribute to apply when in focus or dictionary of
            attribute mappings, if None use attr

        This object will pass all function calls and variable references
        to the wrapped widget.

        >>> AttrMap(Divider(u"!"), 'bright')
        <AttrMap flow widget <Divider flow widget '!'> attr_map={None: 'bright'}>
        >>> AttrMap(Edit(), 'notfocus', 'focus')
        <AttrMap selectable flow widget <Edit selectable flow widget '' edit_pos=0> attr_map={None: 'notfocus'} focus_map={None: 'focus'}>
        >>> size = (5,)
        >>> am = AttrMap(Text(u"hi"), 'greeting', 'fgreet')
        >>> am.render(size, focus=False).content().next() # ... = b in Python 3
        [('greeting', None, ...'hi   ')]
        >>> am.render(size, focus=True).content().next()
        [('fgreet', None, ...'hi   ')]
        >>> am2 = AttrMap(Text(('word', u"hi")), {'word':'greeting', None:'bg'})
        >>> am2
        <AttrMap flow widget <Text flow widget 'hi'> attr_map={'word': 'greeting', None: 'bg'}>
        >>> am2.render(size).content().next()
        [('greeting', None, ...'hi'), ('bg', None, ...'   ')]
        """
        self.__super.__init__(w)

        if type(attr_map) != dict:
            self.set_attr_map({None: attr_map})
        else:
            self.set_attr_map(attr_map)

        if focus_map is not None and type(focus_map) != dict:
            self.set_focus_map({None: focus_map})
        else:
            self.set_focus_map(focus_map)
    
    def _repr_attrs(self):
        # only include the focus_attr when it takes effect (not None)
        d = dict(self.__super._repr_attrs(), attr_map=self._attr_map)
        if self._focus_map is not None:
            d['focus_map'] = self._focus_map
        return d
    
    def get_attr_map(self):
        # make a copy so ours is not accidentally modified
        # FIXME: a dictionary that detects modifications would be better
        return dict(self._attr_map)
    def set_attr_map(self, attr_map):
        """
        Set the attribute mapping dictionary {from_attr: to_attr, ...}

        Note this function does not accept a single attribute the way the
        constructor does.  You must specify {None: attribute} instead.

        >> w = AttrMap(Text("hi"), None)
        >> w.set_attr({'a':'b'})
        >> w
        <AttrMap flow widget <Text flow widget 'hi'> attr_map={'a': 'b'}>
        """
        for from_attr, to_attr in attr_map.items():
            if not from_attr.__hash__ or not to_attr.__hash__:
                raise AttrMapError("%r:%r attribute mapping is invalid.  "
                    "Attributes must be hashable" % (from_attr, to_attr))
        self._attr_map = attr_map
        self._invalidate()
    attr_map = property(get_attr_map, set_attr_map)
    
    def get_focus_map(self):
        # make a copy so ours is not accidentally modified
        # FIXME: a dictionary that detects modifications would be better
        if self._focus_map:
            return dict(self._focus_map)
    def set_focus_map(self, focus_map):
        """
        Set the focus attribute mapping dictionary 
        {from_attr: to_attr, ...}
        
        If None this widget will use the attr mapping instead (no change 
        when in focus).
        
        Note this function does not accept a single attribute the way the
        constructor does.  You must specify {None: attribute} instead.

        >> w = AttrMap(Text("hi"), {})
        >> w.set_focus_map({'a':'b'})
        >> w
        <AttrMap flow widget <Text flow widget 'hi'> attr_map={} focus_map={'a': 'b'}>
        >> w.set_focus_map(None)
        >> w
        <AttrMap flow widget <Text flow widget 'hi'> attr_map={}>
        """
        if focus_map is not None:
            for from_attr, to_attr in focus_map.items():
                if not from_attr.__hash__ or not to_attr.__hash__:
                    raise AttrMapError("%r:%r attribute mapping is invalid.  "
                        "Attributes must be hashable" % (from_attr, to_attr))
        self._focus_map = focus_map
        self._invalidate()
    focus_map = property(get_focus_map, set_focus_map)
        
    def render(self, size, focus=False):
        """
        Render wrapped widget and apply attribute. Return canvas.
        """
        attr_map = self._attr_map
        if focus and self._focus_map is not None:
            attr_map = self._focus_map
        canv = self._original_widget.render(size, focus=focus)
        canv = CompositeCanvas(canv)
        canv.fill_attr_apply(attr_map)
        return canv



class AttrWrap(AttrMap):
    def __init__(self, w, attr, focus_attr=None):
        """
        w -- widget to wrap (stored as self.original_widget)
        attr -- attribute to apply to w
        focus_attr -- attribute to apply when in focus, if None use attr

        This widget is a special case of the new AttrMap widget, and it
        will pass all function calls and variable references to the wrapped
        widget.  This class is maintained for backwards compatibility only,
        new code should use AttrMap instead.

        >>> AttrWrap(Divider(u"!"), 'bright')
        <AttrWrap flow widget <Divider flow widget '!'> attr='bright'>
        >>> AttrWrap(Edit(), 'notfocus', 'focus')
        <AttrWrap selectable flow widget <Edit selectable flow widget '' edit_pos=0> attr='notfocus' focus_attr='focus'>
        >>> size = (5,)
        >>> aw = AttrWrap(Text(u"hi"), 'greeting', 'fgreet')
        >>> aw.render(size, focus=False).content().next()
        [('greeting', None, ...'hi   ')]
        >>> aw.render(size, focus=True).content().next()
        [('fgreet', None, ...'hi   ')]
        """
        self.__super.__init__(w, attr, focus_attr)

    def _repr_attrs(self):
        # only include the focus_attr when it takes effect (not None)
        d = dict(self.__super._repr_attrs(), attr=self.attr)
        del d['attr_map']
        if 'focus_map' in d:
            del d['focus_map']
        if self.focus_attr is not None:
            d['focus_attr'] = self.focus_attr
        return d
    
    # backwards compatibility, widget used to be stored as w
    get_w = WidgetDecoration._get_original_widget
    set_w = WidgetDecoration._set_original_widget
    w = property(get_w, set_w)
    
    def get_attr(self):
        return self.attr_map[None]
    def set_attr(self, attr):
        """
        Set the attribute to apply to the wrapped widget

        >> w = AttrWrap(Divider("-"), None)
        >> w.set_attr('new_attr')
        >> w
        <AttrWrap flow widget <Divider flow widget '-'> attr='new_attr'>
        """
        self.set_attr_map({None: attr})
    attr = property(get_attr, set_attr)
    
    def get_focus_attr(self):
        focus_map = self.focus_map
        if focus_map:
            return focus_map[None]
    def set_focus_attr(self, focus_attr):
        """
        Set the attribute to apply to the wapped widget when it is in
        focus
        
        If None this widget will use the attr instead (no change when in 
        focus).

        >> w = AttrWrap(Divider("-"), 'old')
        >> w.set_focus_attr('new_attr')
        >> w
        <AttrWrap flow widget <Divider flow widget '-'> attr='old' focus_attr='new_attr'>
        >> w.set_focus_attr(None)
        >> w
        <AttrWrap flow widget <Divider flow widget '-'> attr='old'>
        """
        self.set_focus_map({None: focus_attr})
    focus_attr = property(get_focus_attr, set_focus_attr)

    def __getattr__(self,name):
        """
        Call getattr on wrapped widget.  This has been the longstanding
        behaviour of AttrWrap, but is discouraged.  New code should be
        using AttrMap and .base_widget or .original_widget instad.
        """
        return getattr(self._original_widget, name)


    def sizing(self):
        return self._original_widget.sizing()


class BoxAdapterError(Exception):
    pass

class BoxAdapter(WidgetDecoration):
    """
    Adapter for using a box widget where a flow widget would usually go
    """
    no_cache = ["rows"]

    def __init__(self, box_widget, height):
        """
        Create a flow widget that contains a box widget

        box_widget -- box widget (stored as self.original_widget)
        height -- number of rows for box widget

        >>> BoxAdapter(SolidFill(u"x"), 5) # 5-rows of x's
        <BoxAdapter flow widget <SolidFill box widget 'x'> height=5>
        """
        if hasattr(box_widget, 'sizing') and BOX not in box_widget.sizing():
            raise BoxAdapterError("%r is not a box widget" % 
                box_widget)
        WidgetDecoration.__init__(self,box_widget)
        
        self.height = height
    
    def _repr_attrs(self):
        return dict(self.__super._repr_attrs(), height=self.height)

    # originally stored as box_widget, keep for compatibility
    box_widget = property(WidgetDecoration._get_original_widget, 
        WidgetDecoration._set_original_widget)

    def sizing(self):
        return set([FLOW])

    def rows(self, size, focus=False):
        """
        Return the predetermined height (behave like a flow widget)
        
        >>> BoxAdapter(SolidFill(u"x"), 5).rows((20,))
        5
        """
        return self.height

    # The next few functions simply tack-on our height and pass through
    # to self._original_widget
    def get_cursor_coords(self, size):
        (maxcol,) = size
        if not hasattr(self._original_widget,'get_cursor_coords'):
            return None
        return self._original_widget.get_cursor_coords((maxcol, self.height))
    
    def get_pref_col(self, size):
        (maxcol,) = size
        if not hasattr(self._original_widget,'get_pref_col'):
            return None
        return self._original_widget.get_pref_col((maxcol, self.height))
    
    def keypress(self, size, key):
        (maxcol,) = size
        return self._original_widget.keypress((maxcol, self.height), key)
    
    def move_cursor_to_coords(self, size, col, row):
        (maxcol,) = size
        if not hasattr(self._original_widget,'move_cursor_to_coords'):
            return True
        return self._original_widget.move_cursor_to_coords((maxcol,
            self.height), col, row )
    
    def mouse_event(self, size, event, button, col, row, focus):
        (maxcol,) = size
        if not hasattr(self._original_widget,'mouse_event'):
            return False
        return self._original_widget.mouse_event((maxcol, self.height),
            event, button, col, row, focus)
    
    def render(self, size, focus=False):
        (maxcol,) = size
        canv = self._original_widget.render((maxcol, self.height), focus)
        canv = CompositeCanvas(canv)
        return canv
    
    def __getattr__(self, name):
        """
        Pass calls to box widget.
        """
        return getattr(self.box_widget, name)



class PaddingError(Exception):
    pass

class Padding(WidgetDecoration):
    def __init__(self, w, align=LEFT, width=PACK, min_width=None, 
        left=0, right=0):
        r"""
        w -- a box, flow or fixed widget to pad on the left and/or right
            this widget is stored as self.original_widget
        align -- one of:
            'left', 'center', 'right'
            ('relative', percentage 0=left 100=right)
        width -- one of:
            fixed number of columns for self.original_widget 
            'pack'   try to pack self.original_widget to its ideal size
            ('relative', percentage of total width)
            'clip'   to enable clipping mode for a fixed widget
        min_width -- the minimum number of columns for 
            self.original_widget or None
        left -- a fixed number of columns to pad on the left
        right -- a fixed number of columns to pad on thr right
            
        Clipping Mode: (width='clip')
        In clipping mode this padding widget will behave as a flow
        widget and self.original_widget will be treated as a fixed 
        widget.  self.original_widget will will be clipped to fit
        the available number of columns.  For example if align is 
        'left' then self.original_widget may be clipped on the right.

        >>> size = (7,)
        >>> Padding(Text(u"Head"), ('relative', 20)).render(size).text # ... = b in Python 3
        [...' Head  ']
        >>> Padding(Divider(u"-"), left=2, right=1).render(size).text
        [...'  ---- ']
        >>> Padding(Divider(u"*"), 'center', 3).render(size).text
        [...'  ***  ']
        >>> p=Padding(Text(u"1234"), 'left', 2, None, 1, 1)
        >>> p
        <Padding flow widget <Text flow widget '1234'> left=1 right=1 width=2>
        >>> p.render(size).text   # align against left
        [...' 12    ', ...' 34    ']
        >>> p.align = 'right'
        >>> p.render(size).text   # align against right
        [...'    12 ', ...'    34 ']
        >>> Padding(Text(u"hi\nthere"), 'right').render(size).text
        [...'  hi   ', ...'  there']

        """
        self.__super.__init__(w)

        # convert obsolete parameters 'fixed left' and 'fixed right':
        if type(align) == tuple and align[0] in ('fixed left', 
            'fixed right'):
            if align[0]=='fixed left':
                left = align[1]
                align = LEFT
            else:
                right = align[1]
                align = RIGHT
        if type(width) == tuple and width[0] in ('fixed left', 
            'fixed right'):
            if width[0]=='fixed left':
                left = width[1]
            else:
                right = width[1]
            width = RELATIVE_100

        # convert old clipping mode width=None to width='clip'
        if width is None:
            width = CLIP

        self.left = left
        self.right = right
        self._align_type, self._align_amount = normalize_align(align,
            PaddingError)
        self._width_type, self._width_amount = normalize_width(width,
            PaddingError)
        self.min_width = min_width
    
    def sizing(self):
        if self._width_type == CLIP:
            return set([FLOW])
        return self.original_widget.sizing()

    def _repr_attrs(self):
        attrs = dict(self.__super._repr_attrs(),
            align=self.align,
            width=self.width,
            left=self.left,
            right=self.right,
            min_width=self.min_width)
        return remove_defaults(attrs, Padding.__init__)
    
    def _get_align(self):
        """
        Return the padding alignment setting.
        """
        return simplify_align(self._align_type, self._align_amount)
    def _set_align(self, align):
        """
        Set the padding alignment.
        """
        self._align_type, self._align_amount = normalize_align(align,
            PaddingError)
    align = property(_get_align, _set_align)

    def _get_width(self):
        """
        Return the padding widthment setting.
        """
        return simplify_width(self._width_type, self._width_amount)
    def _set_width(self, width):
        """
        Set the padding width.
        """
        self._width_type, self._width_amount = normalize_width(width,
            PaddingError)
    width = property(_get_width, _set_width)
        
    def render(self, size, focus=False):    
        left, right = self.padding_values(size, focus)
        
        maxcol = size[0]
        maxcol -= left+right

        if self._width_type == CLIP:
            canv = self._original_widget.render((), focus)
        else:
            canv = self._original_widget.render((maxcol,)+size[1:], focus)
        if canv.cols() == 0:
            canv = SolidCanvas(' ', size[0], canv.rows())
            canv = CompositeCanvas(canv)
            canv.set_depends([self._original_widget])
            return canv
        canv = CompositeCanvas(canv)
        canv.set_depends([self._original_widget])
        if left != 0 or right != 0:
            canv.pad_trim_left_right(left, right)

        return canv

    def padding_values(self, size, focus):
        """Return the number of columns to pad on the left and right.
        
        Override this method to define custom padding behaviour."""
        maxcol = size[0]
        if self._width_type == CLIP:
            width, ignore = self._original_widget.pack((), focus=focus)
            return calculate_left_right_padding(maxcol,
                self._align_type, self._align_amount, 
                CLIP, width, None, self.left, self.right)
        if self._width_type == PACK:
            maxwidth = max(maxcol - self.left - self.right, 
                self.min_width or 0)
            (width, ignore) = self._original_widget.pack((maxwidth,),
                focus=focus)
            return calculate_left_right_padding(maxcol,
                self._align_type, self._align_amount, 
                GIVEN, width, self.min_width, 
                self.left, self.right) 
        return calculate_left_right_padding(maxcol, 
            self._align_type, self._align_amount,
            self._width_type, self._width_amount,
            self.min_width, self.left, self.right)

    def rows(self, size, focus=False):
        """Return the rows needed for self.original_widget."""
        (maxcol,) = size
        left, right = self.padding_values(size, focus)
        if self._width_type == PACK:
            pcols, prows = self._original_widget.pack((maxcol-left-right,), 
                focus)
            return prows
        if self._width_type == CLIP:
            fcols, frows = self._original_widget.pack((), focus)
            return frows
        return self._original_widget.rows((maxcol-left-right,), focus=focus)
    
    def keypress(self, size, key):
        """Pass keypress to self._original_widget."""
        maxcol = size[0]
        left, right = self.padding_values(size, True)
        maxvals = (maxcol-left-right,)+size[1:] 
        return self._original_widget.keypress(maxvals, key)

    def get_cursor_coords(self,size):
        """Return the (x,y) coordinates of cursor within self._original_widget."""
        if not hasattr(self._original_widget,'get_cursor_coords'):
            return None
        left, right = self.padding_values(size, True)
        maxcol = size[0]
        maxvals = (maxcol-left-right,)+size[1:] 
        coords = self._original_widget.get_cursor_coords(maxvals)
        if coords is None: 
            return None
        x, y = coords
        return x+left, y

    def move_cursor_to_coords(self, size, x, y):
        """Set the cursor position with (x,y) coordinates of self._original_widget.

        Returns True if move succeeded, False otherwise.
        """
        if not hasattr(self._original_widget,'move_cursor_to_coords'):
            return True
        left, right = self.padding_values(size, True)
        maxcol = size[0]
        maxvals = (maxcol-left-right,)+size[1:] 
        if type(x)==int:
            if x < left: 
                x = left
            elif x >= maxcol-right: 
                x = maxcol-right-1
            x -= left
        return self._original_widget.move_cursor_to_coords(maxvals, x, y)
    
    def mouse_event(self, size, event, button, x, y, focus):
        """Send mouse event if position is within self._original_widget."""
        if not hasattr(self._original_widget,'mouse_event'):
            return False
        left, right = self.padding_values(size, focus)
        maxcol = size[0]
        if x < left or x >= maxcol-right: 
            return False
        maxvals = (maxcol-left-right,)+size[1:] 
        return self._original_widget.mouse_event(maxvals, event, button, x-left, y,
            focus)
        

    def get_pref_col(self, size):
        """Return the preferred column from self._original_widget, or None."""
        if not hasattr(self._original_widget,'get_pref_col'):
            return None
        left, right = self.padding_values(size, True)
        maxcol = size[0]
        maxvals = (maxcol-left-right,)+size[1:] 
        x = self._original_widget.get_pref_col(maxvals)
        if type(x) == int:
            return x+left
        return x
        

class FillerError(Exception):
    pass

class Filler(WidgetDecoration):

    def __init__(self, body, valign="middle", height=None, min_height=None):
        """
        body -- a flow widget or box widget to be filled around (stored 
            as self.original_widget)
        valign -- one of:
            'top', 'middle', 'bottom'
            ('fixed top', rows)
            ('fixed bottom', rows)
            ('relative', percentage 0=top 100=bottom)
        height -- one of:
            None if body is a flow widget
            number of rows high 
            ('fixed bottom', rows)  Only if valign is 'fixed top'
            ('fixed top', rows)  Only if valign is 'fixed bottom'
            ('relative', percentage of total height)
        min_height -- one of:
            None if no minimum or if body is a flow widget
            minimum number of rows for the widget when height not fixed
        
        If body is a flow widget then height and min_height must be set
        to None.
        
        Filler widgets will try to satisfy height argument first by
        reducing the valign amount when necessary.  If height still 
        cannot be satisfied it will also be reduced.
        """
        self.__super.__init__(body)
        vt,va,ht,ha=decompose_valign_height(valign,height,FillerError)
        
        self.valign_type, self.valign_amount = vt, va
        self.height_type, self.height_amount = ht, ha
        if self.height_type not in ('fixed', None):
            self.min_height = min_height
        else:
            self.min_height = None
    
    def sizing(self):
        return set([BOX]) # always a box widget

    # backwards compatibility, widget used to be stored as body
    get_body = WidgetDecoration._get_original_widget
    set_body = WidgetDecoration._set_original_widget
    body = property(get_body, set_body)
    
    def selectable(self):
        """Return selectable from body."""
        return self._original_widget.selectable()
    
    def filler_values(self, size, focus):
        """Return the number of rows to pad on the top and bottom.
        
        Override this method to define custom padding behaviour."""
        (maxcol, maxrow) = size
        
        if self.height_type is None:
            height = self._original_widget.rows((maxcol,),focus=focus)
            return calculate_filler( self.valign_type,
                self.valign_amount, 'fixed', height, 
                None, maxrow )
            
        return calculate_filler( self.valign_type, self.valign_amount,
            self.height_type, self.height_amount,
            self.min_height, maxrow)

    
    def render(self, size, focus=False):
        """Render self.original_widget with space above and/or below."""
        (maxcol, maxrow) = size
        top, bottom = self.filler_values(size, focus)
        
        if self.height_type is None:
            canv = self._original_widget.render((maxcol,), focus)
        else:
            canv = self._original_widget.render((maxcol,maxrow-top-bottom),focus)
        canv = CompositeCanvas(canv)
        
        if maxrow and canv.rows() > maxrow and canv.cursor is not None:
            cx, cy = canv.cursor
            if cy >= maxrow:
                canv.trim(cy-maxrow+1,maxrow-top-bottom)
        if canv.rows() > maxrow:
            canv.trim(0, maxrow)
            return canv
        canv.pad_trim_top_bottom(top, bottom)
        return canv


    def keypress(self, size, key):
        """Pass keypress to self.original_widget."""
        (maxcol, maxrow) = size
        if self.height_type is None:
            return self._original_widget.keypress((maxcol,), key)

        top, bottom = self.filler_values((maxcol,maxrow), True)
        return self._original_widget.keypress((maxcol,maxrow-top-bottom), key)

    def get_cursor_coords(self, size):
        """Return cursor coords from self.original_widget if any."""
        (maxcol, maxrow) = size
        if not hasattr(self._original_widget, 'get_cursor_coords'):
            return None
            
        top, bottom = self.filler_values(size, True)
        if self.height_type is None:
            coords = self._original_widget.get_cursor_coords((maxcol,))
        else:
            coords = self._original_widget.get_cursor_coords(
                (maxcol,maxrow-top-bottom))
        if not coords:
            return None
        x, y = coords
        if y >= maxrow:
            y = maxrow-1
        return x, y+top

    def get_pref_col(self, size):
        """Return pref_col from self.original_widget if any."""
        (maxcol, maxrow) = size
        if not hasattr(self._original_widget, 'get_pref_col'):
            return None
        
        if self.height_type is None:
            x = self._original_widget.get_pref_col((maxcol,))
        else:
            top, bottom = self.filler_values(size, True)
            x = self._original_widget.get_pref_col(
                (maxcol, maxrow-top-bottom))

        return x
    
    def move_cursor_to_coords(self, size, col, row):
        """Pass to self.original_widget."""
        (maxcol, maxrow) = size
        if not hasattr(self._original_widget, 'move_cursor_to_coords'):
            return True
        
        top, bottom = self.filler_values(size, True)
        if row < top or row >= maxcol-bottom:
            return False

        if self.height_type is None:
            return self._original_widget.move_cursor_to_coords((maxcol,),
                col, row-top)
        return self._original_widget.move_cursor_to_coords(
            (maxcol, maxrow-top-bottom), col, row-top)
    
    def mouse_event(self, size, event, button, col, row, focus):
        """Pass to self.original_widget."""
        (maxcol, maxrow) = size
        if not hasattr(self._original_widget, 'mouse_event'):
            return False
        
        top, bottom = self.filler_values(size, True)
        if row < top or row >= maxrow-bottom:
            return False

        if self.height_type is None:
            return self._original_widget.mouse_event((maxcol,),
                event, button, col, row-top, focus)
        return self._original_widget.mouse_event((maxcol, maxrow-top-bottom), 
            event, button,col, row-top, focus)

def normalize_align(align, err):
    """
    Split align into (align_type, align_amount).  Raise exception err
    if align doesn't match a valid alignment.
    """
    if align in (LEFT, CENTER, RIGHT):
        return (align, 0)
    elif type(align) == tuple and len(align) == 2 and align[0] == RELATIVE:
        return align
    raise err("align value %r is not one of 'left', 'center', "
        "'right', ('relative', percentage 0=left 100=right)" 
        % (align,))

def simplify_align(align_type, align_amount):
    """
    Recombine (align_type, align_amount) into an align value.
    Inverse of normalize_align.
    """
    if align_type == RELATIVE:
        return (align_type, align_amount)
    return align_type

def normalize_width(width, err):
    """
    Split width into (width_type, width_amount).  Raise exception err
    if width doesn't match a valid alignment.
    """
    if width in (CLIP, PACK):
        return (width, 0)
    elif type(width) == int:
        return (GIVEN, width)
    elif type(width) == tuple and len(width) == 2 and width[0] == RELATIVE:
        return width
    raise err("width value %r is not one of fixed number of columns, "
        "'pack', ('relative', percentage of total width), 'clip'" 
        % (width,))

def simplify_width(width_type, width_amount):
    """
    Recombine (width_type, width_amount) into an width value.
    Inverse of normalize_width.
    """
    if width_type in (CLIP, PACK):
        return width_type
    elif width_type == GIVEN:
        return width_amount
    return (width_type, width_amount)
        
def decompose_align_width( align, width, err ):
    # FIXME: remove this once it is no longer called from Overlay
    try:
        if align in ('left','center','right'):
            align = (align,0)
        align_type, align_amount = align
        assert align_type in ('left','center','right','fixed left',
            'fixed right','relative')
    except (AssertionError, ValueError, TypeError):
        raise err("align value %r is not one of 'left', 'center', "
            "'right', ('fixed left', columns), ('fixed right', "
            "columns), ('relative', percentage 0=left 100=right)" 
            % (align,))

    try:
        if width is None:
            width = None, None
        elif type(width) == int:
            width = 'fixed', width
        width_type, width_amount = width
        assert width_type in ('fixed','fixed right','fixed left',
            'relative', None)
    except (AssertionError, ValueError, TypeError):
        raise err("width value %r is not one of ('fixed', columns "
            "width), ('fixed right', columns), ('relative', "
            "percentage of total width), None" % (width,))
        
    if width_type == 'fixed left' and align_type != 'fixed right':
        raise err("fixed left width may only be used with fixed "
            "right align")
    if width_type == 'fixed right' and align_type != 'fixed left':
        raise err("fixed right width may only be used with fixed "
            "left align")

    return align_type, align_amount, width_type, width_amount


def decompose_valign_height( valign, height, err ):
    try:
        if valign in ('top','middle','bottom'):
            valign = (valign,0)
        valign_type, valign_amount = valign
        assert valign_type in ('top','middle','bottom','fixed top','fixed bottom','relative')
    except (AssertionError, ValueError, TypeError):
        raise err, "Invalid valign: %r" % (valign,)

    try:
        if height is None:
            height = None, None
        elif type(height) == int:
            height=('fixed',height)
        height_type, height_amount = height
        assert height_type in (None, 'fixed','fixed bottom','fixed top','relative')
    except (AssertionError, ValueError, TypeError):
        raise err, "Invalid height: %r"%(height,)
        
    if height_type == 'fixed top' and valign_type != 'fixed bottom':
        raise err, "fixed top height may only be used with fixed bottom valign"
    if height_type == 'fixed bottom' and valign_type != 'fixed top':
        raise err, "fixed bottom height may only be used with fixed top valign"
        
    return valign_type, valign_amount, height_type, height_amount


def calculate_filler( valign_type, valign_amount, height_type, height_amount, 
              min_height, maxrow ):
    if height_type == 'fixed':
        height = height_amount
    elif height_type == 'relative':
        height = int(height_amount*maxrow // 100)
        if min_height is not None:
                height = max(height, min_height)
    else:
        assert height_type in ('fixed bottom','fixed top')
        height = maxrow-height_amount-valign_amount
        if min_height is not None:
                height = max(height, min_height)
    
    if height >= maxrow:
        # use the full space (no padding)
        return 0, 0
        
    if valign_type == 'fixed top':
        top = valign_amount
        if top+height <= maxrow:
            return top, maxrow-top-height
        # need to shrink top
        return maxrow-height, 0
    elif valign_type == 'fixed bottom':
        bottom = valign_amount
        if bottom+height <= maxrow:
            return maxrow-bottom-height, bottom
        # need to shrink bottom
        return 0, maxrow-height        
    elif valign_type == 'relative':
        top = int( (maxrow-height)*valign_amount // 100)
    elif valign_type == 'bottom':
        top = maxrow-height    
    elif valign_type == 'middle':
        top = int( (maxrow-height)/2 )
    else: #self.valign_type == 'top'
        top = 0
    
    if top+height > maxrow: top = maxrow-height
    if top < 0: top = 0
    
    bottom = maxrow-height-top
    return top, bottom     


def calculate_left_right_padding(maxcol, align_type, align_amount, 
    width_type, width_amount, min_width, left, right):
    """
    Return the amount of padding (or clipping) on the left and
    right part of maxcol columns to satisfy the following:

    align_type -- 'left', 'center', 'right', 'relative'
    align_amount -- a percentage when align_type=='relative'
    width_type -- 'fixed', 'relative', 'clip'
    width_amount -- a percentage when width_type=='relative'
        otherwise equal to the width of the widget
    min_width -- a desired minimum width for the widget or None
    left -- a fixed number of columns to pad on the left
    right -- a fixed number of columns to pad on the right

    >>> clrp = calculate_left_right_padding
    >>> clrp(15, 'left', 0, 'fixed', 10, None, 2, 0)
    (2, 3)
    >>> clrp(15, 'relative', 0, 'fixed', 10, None, 2, 0)
    (2, 3)
    >>> clrp(15, 'relative', 100, 'fixed', 10, None, 2, 0)
    (5, 0)
    >>> clrp(15, 'center', 0, 'fixed', 4, None, 2, 0)
    (6, 5)
    >>> clrp(15, 'left', 0, 'clip', 18, None, 0, 0)
    (0, -3)
    >>> clrp(15, 'right', 0, 'clip', 18, None, 0, -1)
    (-2, -1)
    >>> clrp(15, 'center', 0, 'fixed', 18, None, 2, 0)
    (0, 0)
    >>> clrp(20, 'left', 0, 'relative', 60, None, 0, 0)
    (0, 8)
    >>> clrp(20, 'relative', 30, 'relative', 60, None, 0, 0)
    (2, 6)
    >>> clrp(20, 'relative', 30, 'relative', 60, 14, 0, 0)
    (2, 4)
    """
    if width_type == RELATIVE:
        maxwidth = max(maxcol - left - right, 0)
        width = int_scale(width_amount, 101, maxwidth + 1)
        if min_width is not None:
            width = max(width, min_width)
    else:
        width = width_amount
    
    standard_alignments = {LEFT:0, CENTER:50, RIGHT:100}
    align = standard_alignments.get(align_type, align_amount)

    # add the remainder of left/right the padding
    padding = maxcol - width - left - right
    right += int_scale(100 - align, 101, padding + 1) 
    left = maxcol - width - right

    # reduce padding if we are clipping an edge
    if right < 0 and left > 0:
        shift = min(left, -right)
        left -= shift
        right += shift
    elif left < 0 and right > 0:
        shift = min(right, -left)
        right -= shift
        left += shift
    
    # only clip if width_type == 'clip'
    if width_type != CLIP and (left < 0 or right < 0):
        left = max(left, 0)
        right = max(right, 0)
    
    return left, right


def calculate_padding( align_type, align_amount, width_type, width_amount,
        min_width, maxcol, clip=False ):
    # FIXME: remove this when Overlay is no longer calling it
    if width_type == 'fixed':
        width = width_amount
    elif width_type == 'relative':
        width = int(width_amount*maxcol/100+.5)
        if min_width is not None:
                width = max(width, min_width)
    else: 
        assert width_type in ('fixed right', 'fixed left')
        width = maxcol-width_amount-align_amount
        if min_width is not None:
                width = max(width, min_width)
    
    if width == maxcol or (width > maxcol and not clip):
        # use the full space (no padding)
        return 0, 0
        
    if align_type == 'fixed left':
        left = align_amount
        if left+width <= maxcol:
            return left, maxcol-left-width
        # need to shrink left
        return maxcol-width, 0
    elif align_type == 'fixed right':
        right = align_amount
        if right+width <= maxcol:
            return maxcol-right-width, right
        # need to shrink right
        return 0, maxcol-width        
    elif align_type == 'relative':
        left = int( (maxcol-width)*align_amount/100+.5 )
    elif align_type == 'right':
        left = maxcol-width    
    elif align_type == 'center':
        left = int( (maxcol-width)/2 )
    else: 
        assert align_type == 'left'
        left = 0
    
    if width < maxcol:
        if left+width > maxcol: left = maxcol-width
        if left < 0: left = 0
    
    right = maxcol-width-left
    return left, right     


def _test():
    import doctest
    doctest.testmod()

if __name__=='__main__':
    _test()
<file_sep>/urwid/main_loop.py
#!/usr/bin/python
#
# Urwid main loop code
#    Copyright (C) 2004-2011  <NAME>
#    Copyright (C) 2008 <NAME>
#    Copyright (C) 2009 <NAME>
#
#    This library 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.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: http://excess.org/urwid/


import time
import heapq
import select
import fcntl
import os

from urwid.util import is_mouse_event
from urwid.compat import PYTHON3, bytes
from urwid.command_map import command_map
from urwid.wimp import PopUpTarget
from urwid import signals
from urwid.display_common import INPUT_DESCRIPTORS_CHANGED

PIPE_BUFFER_READ_SIZE = 4096 # can expect this much on Linux, so try for that

class ExitMainLoop(Exception):
    pass

class MainLoop(object):
    def __init__(self, widget, palette=[], screen=None, 
        handle_mouse=True, input_filter=None, unhandled_input=None,
        event_loop=None, pop_ups=False):
        """
        Simple main loop implementation.

        widget -- topmost widget used for painting the screen,
            stored as self.widget and may be modified
        palette -- initial palette for screen
        screen -- screen object or None to use raw_display.Screen,
            stored as self.screen
        handle_mouse -- True to process mouse events, passed to
            self.screen
        input_filter -- a function to filter input before sending
            it to self.widget, called from self.input_filter
        unhandled_input -- a function called when input is not
            handled by self.widget, called from self.unhandled_input
        event_loop -- if screen supports external an event loop it
            may be given here, or leave as None to use
            SelectEventLoop, stored as self.event_loop
        pop_ups -- True to wrap self.widget with a PopUpTarget
            instance to allow any widget to open a pop-up anywhere on
            the screen

        This is the standard main loop implementation with a single
        screen.

        The widget passed must be a box widget.
        """
        self._widget = widget
        self.handle_mouse = handle_mouse
        self.pop_ups = pop_ups # triggers property setting side-effect

        if not screen:
            from urwid import raw_display
            screen = raw_display.Screen()

        if palette:
            screen.register_palette(palette)

        self.screen = screen
        self.screen_size = None

        self._unhandled_input = unhandled_input
        self._input_filter = input_filter

        if not hasattr(screen, 'get_input_descriptors'
                ) and event_loop is not None:
            raise NotImplementedError("screen object passed "
                "%r does not support external event loops" % (screen,))
        if event_loop is None:
            event_loop = SelectEventLoop()
        self.event_loop = event_loop

        self._input_timeout = None
        self._watch_pipes = {}

    def _set_widget(self, widget):
        self._widget = widget
        if self.pop_ups:
            self._topmost_widget.original_widget = self._widget
        else:
            self._topmost_widget = self._widget
    widget = property(lambda self:self._widget, _set_widget)

    def _set_pop_ups(self, pop_ups):
        self._pop_ups = pop_ups
        if pop_ups:
            self._topmost_widget = PopUpTarget(self._widget)
        else:
            self._topmost_widget = self._widget
    pop_ups = property(lambda self:self._pop_ups, _set_pop_ups)

    def set_alarm_in(self, sec, callback, user_data=None):
        """
        Schedule an alarm in sec seconds that will call
        callback(main_loop, user_data) from the within the run()
        function.

        sec -- floating point seconds until alarm
        callback -- callback(main_loop, user_data) callback function
        user_data -- object to pass to callback
        """
        def cb():
            callback(self, user_data)
        return self.event_loop.alarm(sec, cb)

    def set_alarm_at(self, tm, callback, user_data=None):
        """
        Schedule at tm time that will call 
        callback(main_loop, user_data) from the within the run()
        function.

        Returns a handle that may be passed to remove_alarm()

        tm -- floating point local time of alarm
        callback -- callback(main_loop, user_data) callback function
        user_data -- object to pass to callback
        """
        def cb():
            callback(self, user_data)
        return self.event_loop.alarm(tm - time.time(), cb)

    def remove_alarm(self, handle):
        """
        Remove an alarm.

        Return True if the handle was found, False otherwise.
        """
        return self.event_loop.remove_alarm(handle)

    def watch_pipe(self, callback):
        """
        Create a pipe for use by a subprocess or thread to trigger
        a callback in the process/thread running the MainLoop.

        callback -- function to call MainLoop.run thread/process

        This function returns a file descriptor attached to the
        write end of a pipe.  The read end of the pipe is added to
        the list of files the event loop is watching. When
        data is written to the pipe the callback function will be
        called and passed a single value containing data read.

        This method should be used any time you want to update
        widgets from another thread or subprocess.

        Data may be written to the returned file descriptor with
        os.write(fd, data).  Ensure that data is less than 512
        bytes (or 4K on Linux) so that the callback will be
        triggered just once with the complete value of data
        passed in.

        If the callback returns False then the watch will be
        removed and the read end of the pipe will be closed.
        You are responsible for closing the write end of the pipe.
        """
        pipe_rd, pipe_wr = os.pipe()
        fcntl.fcntl(pipe_rd, fcntl.F_SETFL, os.O_NONBLOCK)
        watch_handle = None

        def cb():
            data = os.read(pipe_rd, PIPE_BUFFER_READ_SIZE)
            rval = callback(data)
            if rval is False:
                self.event_loop.remove_watch_file(watch_handle)
                os.close(pipe_rd)

        watch_handle = self.event_loop.watch_file(pipe_rd, cb)
        self._watch_pipes[pipe_wr] = (watch_handle, pipe_rd)
        return pipe_wr

    def remove_watch_pipe(self, write_fd):
        """
        Close the read end of the pipe and remove the watch created
        by watch_pipe().  You are responsible for closing the write
        end of the pipe.

        Returns True if the watch pipe exists, False otherwise
        """
        try:
            watch_handle, pipe_rd = self._watch_pipes.remove(write_fd)
        except KeyError:
            return False

        if not self.event_loop.remove_watch_file(watch_handle):
            return False
        os.close(pipe_rd)
        return True

    def watch_file(self, fd, callback):
        """
        Call callback() when fd has some data to read.  No parameters
        are passed to callback.

        Returns a handle that may be passed to remove_watch_file()

        fd -- file descriptor to watch for input
        callback -- function to call when input is available
        """
        return self.event_loop.watch_file(fd, callback)

    def remove_watch_file(self, handle):
        """
        Remove a watch file.

        Returns True if the watch file exists, False otherwise.
        """
        return self.event_loop.remove_watch_file(handle)


    def run(self):
        """
        Start the main loop handling input events and updating 
        the screen.  The loop will continue until an ExitMainLoop 
        exception is raised.  
        
        This function will call screen.run_wrapper() if screen.start() 
        has not already been called.

        >>> w = _refl("widget")   # _refl prints out function calls
        >>> w.render_rval = "fake canvas"  # *_rval is used for return values
        >>> scr = _refl("screen")
        >>> scr.get_input_descriptors_rval = [42]
        >>> scr.get_cols_rows_rval = (20, 10)
        >>> scr.started = True
        >>> scr._urwid_signals = {}
        >>> evl = _refl("event_loop")
        >>> evl.enter_idle_rval = 1
        >>> evl.watch_file_rval = 2
        >>> ml = MainLoop(w, [], scr, event_loop=evl)
        >>> ml.run()    # doctest:+ELLIPSIS
        screen.set_mouse_tracking()
        screen.get_cols_rows()
        widget.render((20, 10), focus=True)
        screen.draw_screen((20, 10), 'fake canvas')
        screen.get_input_descriptors()
        event_loop.watch_file(42, <bound method ...>)
        event_loop.enter_idle(<bound method ...>)
        event_loop.run()
        event_loop.remove_enter_idle(1)
        event_loop.remove_watch_file(2)
        >>> scr.started = False
        >>> ml.run()    # doctest:+ELLIPSIS
        screen.run_wrapper(<bound method ...>)
        """
        try:
            if self.screen.started:
                self._run()
            else:
                self.screen.run_wrapper(self._run)
        except ExitMainLoop:
            pass
    
    def _run(self):
        if self.handle_mouse:
            self.screen.set_mouse_tracking()

        if not hasattr(self.screen, 'get_input_descriptors'):
            return self._run_screen_event_loop()

        self.draw_screen()

        fd_handles = []
        def reset_input_descriptors(only_remove=False):
            for handle in fd_handles:
                self.event_loop.remove_watch_file(handle)
            if only_remove:
                return
            fd_handles[:] = [
                self.event_loop.watch_file(fd, self._update)
                for fd in self.screen.get_input_descriptors()]

        try:
            signals.connect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED,
                reset_input_descriptors)
        except NameError:
            pass
        # watch our input descriptors
        reset_input_descriptors()
        idle_handle = self.event_loop.enter_idle(self.entering_idle)

        # Go..
        self.event_loop.run()

        # tidy up
        self.event_loop.remove_enter_idle(idle_handle)
        reset_input_descriptors(True)
        signals.disconnect_signal(self.screen, INPUT_DESCRIPTORS_CHANGED,
            reset_input_descriptors)

    def _update(self, timeout=False):
        """
        >>> w = _refl("widget")
        >>> w.selectable_rval = True
        >>> w.mouse_event_rval = True
        >>> scr = _refl("screen")
        >>> scr.get_cols_rows_rval = (15, 5)
        >>> scr.get_input_nonblocking_rval = 1, ['y'], [121]
        >>> evl = _refl("event_loop")
        >>> ml = MainLoop(w, [], scr, event_loop=evl)
        >>> ml._input_timeout = "old timeout"
        >>> ml._update()    # doctest:+ELLIPSIS
        event_loop.remove_alarm('old timeout')
        screen.get_input_nonblocking()
        event_loop.alarm(1, <function ...>)
        screen.get_cols_rows()
        widget.selectable()
        widget.keypress((15, 5), 'y')
        >>> scr.get_input_nonblocking_rval = None, [("mouse press", 1, 5, 4)
        ... ], []
        >>> ml._update()
        screen.get_input_nonblocking()
        widget.mouse_event((15, 5), 'mouse press', 1, 5, 4, focus=True)
        >>> scr.get_input_nonblocking_rval = None, [], []
        >>> ml._update()
        screen.get_input_nonblocking()
        """
        if self._input_timeout is not None and not timeout:
            # cancel the timeout, something else triggered the update
            self.event_loop.remove_alarm(self._input_timeout)
        self._input_timeout = None

        max_wait, keys, raw = self.screen.get_input_nonblocking()
        
        if max_wait is not None:
            # if get_input_nonblocking wants to be called back
            # make sure it happens with an alarm
            self._input_timeout = self.event_loop.alarm(max_wait, 
                lambda: self._update(timeout=True)) 

        keys = self.input_filter(keys, raw)

        if keys:
            self.process_input(keys)
            if 'window resize' in keys:
                self.screen_size = None

    def _run_screen_event_loop(self):
        """
        This method is used when the screen does not support using
        external event loops.

        The alarms stored in the SelectEventLoop in self.event_loop 
        are modified by this method.
        """
        next_alarm = None

        while True:
            self.draw_screen()

            if not next_alarm and self.event_loop._alarms:
                next_alarm = heapq.heappop(self.event_loop._alarms)

            keys = None
            while not keys:
                if next_alarm:
                    sec = max(0, next_alarm[0] - time.time())
                    self.screen.set_input_timeouts(sec)
                else:
                    self.screen.set_input_timeouts(None)
                keys, raw = self.screen.get_input(True)
                if not keys and next_alarm: 
                    sec = next_alarm[0] - time.time()
                    if sec <= 0:
                        break

            keys = self.input_filter(keys, raw)
            
            if keys:
                self.process_input(keys)
            
            while next_alarm:
                sec = next_alarm[0] - time.time()
                if sec > 0:
                    break
                tm, callback, user_data = next_alarm
                callback(self, user_data)
                
                if self._alarms:
                    next_alarm = heapq.heappop(self.event_loop._alarms)
                else:
                    next_alarm = None
            
            if 'window resize' in keys:
                self.screen_size = None

    def process_input(self, keys):
        """
        This function will pass keyboard input and mouse events
        to self.widget.  This function is called automatically
        from the run() method when there is input, but may also be
        called to simulate input from the user.

        keys -- list of input returned from self.screen.get_input()

        Returns True if any key was handled by a widget or the
        unhandled_input() method.         

        >>> w = _refl("widget")
        >>> w.selectable_rval = True
        >>> scr = _refl("screen")
        >>> scr.get_cols_rows_rval = (10, 5)
        >>> ml = MainLoop(w, [], scr)
        >>> ml.process_input(['enter', ('mouse drag', 1, 14, 20)])
        screen.get_cols_rows()
        widget.selectable()
        widget.keypress((10, 5), 'enter')
        widget.mouse_event((10, 5), 'mouse drag', 1, 14, 20, focus=True)
        True
        """
        if not self.screen_size:
            self.screen_size = self.screen.get_cols_rows()

        something_handled = False

        for k in keys:
            if is_mouse_event(k):
                event, button, col, row = k
                if self._topmost_widget.mouse_event(self.screen_size,
                    event, button, col, row, focus=True ):
                    k = None
            elif self._topmost_widget.selectable():
                k = self._topmost_widget.keypress(self.screen_size, k)
            if k:
                if command_map[k] == 'redraw screen':
                    self.screen.clear()
                    something_handled = True
                else:
                    something_handled |= bool(self.unhandled_input(k))
            else:
                something_handled = True

        return something_handled


    def input_filter(self, keys, raw):
        """
        This function is passed each all the input events and raw
        keystroke values.  These values are passed to the
        input_filter function passed to the constructor.  That
        function must return a list of keys to be passed to the
        widgets to handle.  If no input_filter was defined this
        implementation will return all the input events.

        input -- keyboard or mouse input
        """
        if self._input_filter:
            return self._input_filter(keys, raw)
        return keys

    def unhandled_input(self, input):
        """
        This function is called with any input that was not handled
        by the widgets, and calls the unhandled_input function passed
        to the constructor.  If no unhandled_input was defined then
        the input will be ignored.

        input -- keyboard or mouse input

        The unhandled_input method should return True if it handled
        the input.
        """
        if self._unhandled_input:
            return self._unhandled_input(input)

    def entering_idle(self):
        """
        This function is called whenever the event loop is about
        to enter the idle state.  self.draw_screen() is called here
        to update the screen if anything has changed.
        """
        if self.screen.started:
            self.draw_screen()

    def draw_screen(self):
        """
        Renter the widgets and paint the screen.  This function is
        called automatically from run() but may be called additional
        times if repainting is required without also processing input.
        """
        if not self.screen_size:
            self.screen_size = self.screen.get_cols_rows()

        canvas = self._topmost_widget.render(self.screen_size, focus=True)
        self.screen.draw_screen(self.screen_size, canvas)





class SelectEventLoop(object):
    def __init__(self):
        """
        Event loop based on select.select()

        >>> import os
        >>> rd, wr = os.pipe()
        >>> evl = SelectEventLoop()
        >>> def step1():
        ...     print "writing"
        ...     os.write(wr, "hi".encode('ascii'))
        >>> def step2():
        ...     print os.read(rd, 2).decode('ascii')
        ...     raise ExitMainLoop
        >>> handle = evl.alarm(0, step1)
        >>> handle = evl.watch_file(rd, step2)
        >>> evl.run()
        writing
        hi
        """
        self._alarms = []
        self._watch_files = {}
        self._idle_handle = 0
        self._idle_callbacks = {}

    def alarm(self, seconds, callback):
        """
        Call callback() given time from from now.  No parameters are
        passed to callback.

        Returns a handle that may be passed to remove_alarm()

        seconds -- floating point time to wait before calling callback
        callback -- function to call from event loop
        """ 
        tm = time.time() + seconds
        heapq.heappush(self._alarms, (tm, callback))
        return (tm, callback)

    def remove_alarm(self, handle):
        """
        Remove an alarm.

        Returns True if the alarm exists, False otherwise

        >>> evl = SelectEventLoop()
        >>> handle = evl.alarm(50, lambda: None)
        >>> evl.remove_alarm(handle)
        True
        >>> evl.remove_alarm(handle)
        False
        """
        try:
            self._alarms.remove(handle)
            heapq.heapify(self._alarms)
            return True
        except ValueError:
            return False

    def watch_file(self, fd, callback):
        """
        Call callback() when fd has some data to read.  No parameters
        are passed to callback.

        Returns a handle that may be passed to remove_watch_file()

        fd -- file descriptor to watch for input
        callback -- function to call when input is available
        """
        self._watch_files[fd] = callback
        return fd

    def remove_watch_file(self, handle):
        """
        Remove an input file.

        Returns True if the input file exists, False otherwise

        >>> evl = SelectEventLoop()
        >>> handle = evl.watch_file(5, lambda: None)
        >>> evl.remove_watch_file(handle)
        True
        >>> evl.remove_watch_file(handle)
        False
        """
        if handle in self._watch_files:
            del self._watch_files[handle]
            return True
        return False

    def enter_idle(self, callback):
        """
        Add a callback for entering idle.  
        
        Returns a handle that may be passed to remove_idle()
        """
        self._idle_handle += 1
        self._idle_callbacks[self._idle_handle] = callback
        return self._idle_handle

    def remove_enter_idle(self, handle):
        """
        Remove an idle callback.

        Returns True if the handle was removed.
        """
        try:
            del self._idle_callbacks[handle]
        except KeyError:
            return False
        return True

    def _entering_idle(self):
        """
        Call all the registered idle callbacks.
        """
        for callback in self._idle_callbacks.values():
            callback()

    def run(self):
        """
        Start the event loop.  Exit the loop when any callback raises
        an exception.  If ExitMainLoop is raised, exit cleanly.

        >>> import os
        >>> rd, wr = os.pipe()
        >>> os.write(wr, "data".encode('ascii')) # something to read from rd
        4
        >>> evl = SelectEventLoop()
        >>> def say_hello():
        ...     print "hello"
        >>> def say_waiting():
        ...     print "waiting"
        >>> def exit_clean():
        ...     print "clean exit"
        ...     raise ExitMainLoop
        >>> def exit_error():
        ...     1/0
        >>> handle = evl.alarm(0.01, exit_clean)
        >>> handle = evl.alarm(0.005, say_hello)
        >>> evl.enter_idle(say_waiting)
        1
        >>> evl.run()
        waiting
        hello
        waiting
        clean exit
        >>> handle = evl.watch_file(rd, exit_clean)
        >>> evl.run()
        clean exit
        >>> evl.remove_watch_file(handle)
        True
        >>> handle = evl.alarm(0, exit_error)
        >>> evl.run()
        Traceback (most recent call last):
           ...
        ZeroDivisionError: integer division or modulo by zero
        >>> handle = evl.watch_file(rd, exit_error)
        >>> evl.run()
        Traceback (most recent call last):
           ...
        ZeroDivisionError: integer division or modulo by zero
        """
        try:
            self._did_something = True
            while True:
                try:
                    self._loop()
                except select.error, e:
                    if e.args[0] != 4:
                        # not just something we need to retry
                        raise
        except ExitMainLoop:
            pass
        

    def _loop(self):
        """
        A single iteration of the event loop
        """
        fds = self._watch_files.keys()
        if self._alarms or self._did_something:
            if self._alarms:
                tm = self._alarms[0][0]
                timeout = max(0, tm - time.time())
            if self._did_something and (not self._alarms or 
                    (self._alarms and timeout > 0)):
                timeout = 0
                tm = 'idle'
            ready, w, err = select.select(fds, [], fds, timeout)
        else:
            tm = None
            ready, w, err = select.select(fds, [], fds)

        if not ready:
            if tm == 'idle':
                self._entering_idle()
                self._did_something = False
            elif tm is not None:
                # must have been a timeout
                tm, alarm_callback = self._alarms.pop(0)
                alarm_callback()
                self._did_something = True

        for fd in ready:
            self._watch_files[fd]()
            self._did_something = True


if not PYTHON3:
    class GLibEventLoop(object):
        def __init__(self):
            """
            Event loop based on gobject.MainLoop

            >>> import os
            >>> rd, wr = os.pipe()
            >>> evl = GLibEventLoop()
            >>> def step1():
            ...     print "writing"
            ...     os.write(wr, "hi")
            >>> def step2():
            ...     print os.read(rd, 2)
            ...     raise ExitMainLoop
            >>> handle = evl.alarm(0, step1)
            >>> handle = evl.watch_file(rd, step2)
            >>> evl.run()
            writing
            hi
            """
            import gobject
            self.gobject = gobject
            self._alarms = []
            self._watch_files = {}
            self._idle_handle = 0
            self._glib_idle_enabled = False # have we called glib.idle_add?
            self._idle_callbacks = {}
            self._loop = self.gobject.MainLoop()
            self._exc_info = None
            self._enable_glib_idle()

        def alarm(self, seconds, callback):
            """
            Call callback() given time from from now.  No parameters are
            passed to callback.

            Returns a handle that may be passed to remove_alarm()

            seconds -- floating point time to wait before calling callback
            callback -- function to call from event loop
            """
            @self.handle_exit
            def ret_false():
                callback()
                self._enable_glib_idle()
                return False
            fd = self.gobject.timeout_add(int(seconds*1000), ret_false)
            self._alarms.append(fd)
            return (fd, callback)

        def remove_alarm(self, handle):
            """
            Remove an alarm.

            Returns True if the alarm exists, False otherwise

            >>> evl = GLibEventLoop()
            >>> handle = evl.alarm(50, lambda: None)
            >>> evl.remove_alarm(handle)
            True
            >>> evl.remove_alarm(handle)
            False
            """
            try:
                self._alarms.remove(handle[0])
                self.gobject.source_remove(handle[0])
                return True
            except ValueError:
                return False

        def watch_file(self, fd, callback):
            """
            Call callback() when fd has some data to read.  No parameters
            are passed to callback.

            Returns a handle that may be passed to remove_watch_file()

            fd -- file descriptor to watch for input
            callback -- function to call when input is available
            """
            @self.handle_exit
            def io_callback(source, cb_condition):
                callback()
                self._enable_glib_idle()
                return True
            self._watch_files[fd] = \
                 self.gobject.io_add_watch(fd,self.gobject.IO_IN,io_callback)
            return fd

        def remove_watch_file(self, handle):
            """
            Remove an input file.

            Returns True if the input file exists, False otherwise

            >>> evl = GLibEventLoop()
            >>> handle = evl.watch_file(1, lambda: None)
            >>> evl.remove_watch_file(handle)
            True
            >>> evl.remove_watch_file(handle)
            False
            """
            if handle in self._watch_files:
                self.gobject.source_remove(self._watch_files[handle])
                del self._watch_files[handle]
                return True
            return False

        def enter_idle(self, callback):
            """
            Add a callback for entering idle.

            Returns a handle that may be passed to remove_enter_idle()
            """
            self._idle_handle += 1
            self._idle_callbacks[self._idle_handle] = callback
            return self._idle_handle

        def _enable_glib_idle(self):
            if self._glib_idle_enabled:
                return
            self.gobject.idle_add(self._glib_idle_callback)
            self._glib_idle_enabled = True

        def _glib_idle_callback(self):
            for callback in self._idle_callbacks.values():
                callback()
            self._glib_idle_enabled = False
            return False # ask glib not to call again (or we would be called

        def remove_enter_idle(self, handle):
            """
            Remove an idle callback.

            Returns True if the handle was removed.
            """
            try:
                del self._idle_callbacks[handle]
            except KeyError:
                return False
            return True


        def run(self):
            """
            Start the event loop.  Exit the loop when any callback raises
            an exception.  If ExitMainLoop is raised, exit cleanly.
            
            >>> import os
            >>> rd, wr = os.pipe()
            >>> os.write(wr, "data") # something to read from rd
            4
            >>> evl = GLibEventLoop()
            >>> def say_hello():
            ...     print "hello"
            >>> def say_waiting():
            ...     print "waiting"
            >>> def exit_clean():
            ...     print "clean exit"
            ...     raise ExitMainLoop
            >>> def exit_error():
            ...     1/0
            >>> handle = evl.alarm(0.01, exit_clean)
            >>> handle = evl.alarm(0.005, say_hello)
            >>> evl.enter_idle(say_waiting)
            1
            >>> evl.run()
            waiting
            hello
            waiting
            clean exit
            >>> handle = evl.watch_file(rd, exit_clean)
            >>> evl.run()
            clean exit
            >>> evl.remove_watch_file(handle)
            True
            >>> handle = evl.alarm(0, exit_error)
            >>> evl.run()
            Traceback (most recent call last):
               ...
            ZeroDivisionError: integer division or modulo by zero
            >>> handle = evl.watch_file(rd, exit_error)
            >>> evl.run()
            Traceback (most recent call last):
               ...
            ZeroDivisionError: integer division or modulo by zero
            """
            try:
                self._loop.run()
            finally:
                if self._loop.is_running():
                    self._loop.quit()
            if self._exc_info:
                # An exception caused us to exit, raise it now
                exc_info = self._exc_info
                self._exc_info = None
                raise exc_info[0], exc_info[1], exc_info[2]

        def handle_exit(self,f):
            """
            Decorator that cleanly exits the GLibEventLoop if ExitMainLoop is
            thrown inside of the wrapped function.  Store the exception info if 
            some other exception occurs, it will be reraised after the loop quits.
            f -- function to be wrapped

            """
            def wrapper(*args,**kargs):
                try:
                    return f(*args,**kargs)
                except ExitMainLoop:
                    self._loop.quit()
                except:
                    import sys
                    self._exc_info = sys.exc_info()
                    if self._loop.is_running():
                        self._loop.quit()
                return False
            return wrapper


    try:
        from twisted.internet.abstract import FileDescriptor
    except ImportError:
        FileDescriptor = object

    class TwistedInputDescriptor(FileDescriptor):
        def __init__(self, reactor, fd, cb):
            self._fileno = fd
            self.cb = cb
            FileDescriptor.__init__(self, reactor)

        def fileno(self):
            return self._fileno

        def doRead(self):
            return self.cb()



    class TwistedEventLoop(object):
        _idle_emulation_delay = 1.0/256 # a short time (in seconds)

        def __init__(self, reactor=None, manage_reactor=True):
            """
            Event loop based on Twisted

            reactor -- reactor object to use, if None defaults to
                    twisted.internet.reactor
            manage_reactor -- True if you want this event loop to run
                    and stop the reactor

            *** WARNING ***
            Twisted's reactor doesn't like to be stopped and run again.
            If you need to stop and run your MainLoop, consider setting
            manage_reactor=False and take care of running/stopping
            the reactor at the beginning/ending of your program yourself.
            """
            if reactor is None:
                import twisted.internet.reactor
                reactor = twisted.internet.reactor
            self.reactor = reactor
            self._alarms = []
            self._watch_files = {}
            self._idle_handle = 0
            self._twisted_idle_enabled = False
            self._idle_callbacks = {}
            self._exc_info = None
            self.manage_reactor = manage_reactor
            self._enable_twisted_idle()

        def alarm(self, seconds, callback):
            """
            Call callback() given time from from now.  No parameters are
            passed to callback.

            Returns a handle that may be passed to remove_alarm()

            seconds -- floating point time to wait before calling callback
            callback -- function to call from event loop
            """
            handle = self.reactor.callLater(seconds, self.handle_exit(callback))
            return handle

        def remove_alarm(self, handle):
            """
            Remove an alarm.

            Returns True if the alarm exists, False otherwise

            >>> evl = TwistedEventLoop()
            >>> handle = evl.alarm(50, lambda: None)
            >>> evl.remove_alarm(handle)
            True
            >>> evl.remove_alarm(handle)
            False
            """
            from twisted.internet.error import AlreadyCancelled, AlreadyCalled
            try:
                handle.cancel()
                return True
            except AlreadyCancelled:
                return False
            except AlreadyCalled:
                return False

        def watch_file(self, fd, callback):
            """
            Call callback() when fd has some data to read.  No parameters
            are passed to callback.

            Returns a handle that may be passed to remove_watch_file()

            fd -- file descriptor to watch for input
            callback -- function to call when input is available
            """
            ind = TwistedInputDescriptor(self.reactor, fd,
                self.handle_exit(callback))
            self._watch_files[fd] = ind
            self.reactor.addReader(ind)
            return fd

        def remove_watch_file(self, handle):
            """
            Remove an input file.

            Returns True if the input file exists, False otherwise

            >>> evl = TwistedEventLoop()
            >>> handle = evl.watch_file(1, lambda: None)
            >>> evl.remove_watch_file(handle)
            True
            >>> evl.remove_watch_file(handle)
            False
            """
            if handle in self._watch_files:
                self.reactor.removeReader(self._watch_files[handle])
                del self._watch_files[handle]
                return True
            return False

        def enter_idle(self, callback):
            """
            Add a callback for entering idle.

            Returns a handle that may be passed to remove_enter_idle()
            """
            self._idle_handle += 1
            self._idle_callbacks[self._idle_handle] = callback
            return self._idle_handle

        def _enable_twisted_idle(self):
            """
            Twisted's reactors don't have an idle or enter-idle callback
            so the best we can do for now is to set a timer event in a very
            short time to approximate an enter-idle callback.

            XXX: This will perform worse than the other event loops until we
            can find a fix or workaround
            """
            if self._twisted_idle_enabled:
                return
            self.reactor.callLater(self._idle_emulation_delay,
                self.handle_exit(self._twisted_idle_callback, enable_idle=False))
            self._twisted_idle_enabled = True

        def _twisted_idle_callback(self):
            for callback in self._idle_callbacks.values():
                callback()
            self._twisted_idle_enabled = False

        def remove_enter_idle(self, handle):
            """
            Remove an idle callback.

            Returns True if the handle was removed.
            """
            try:
                del self._idle_callbacks[handle]
            except KeyError:
                return False
            return True

        def run(self):
            """
            Start the event loop.  Exit the loop when any callback raises
            an exception.  If ExitMainLoop is raised, exit cleanly.

            >>> import os
            >>> rd, wr = os.pipe()
            >>> os.write(wr, "data") # something to read from rd
            4
            >>> evl = TwistedEventLoop()
            >>> def say_hello_data():
            ...     print "hello data"
            ...     os.read(rd, 4)
            >>> def say_waiting():
            ...     print "waiting"
            >>> def say_hello():
            ...     print "hello"
            >>> handle = evl.watch_file(rd, say_hello_data)
            >>> def say_being_twisted():
            ...     print "oh I'm messed up"
            ...     raise ExitMainLoop
            >>> handle = evl.alarm(0.0625, say_being_twisted)
            >>> handle = evl.alarm(0.03125, say_hello)
            >>> evl.enter_idle(say_waiting)
            1
            >>> evl.run()
            hello data
            waiting
            hello
            waiting
            oh I'm messed up
            """
            if not self.manage_reactor:
                return
            self.reactor.run()
            if self._exc_info:
                # An exception caused us to exit, raise it now
                exc_info = self._exc_info
                self._exc_info = None
                raise exc_info[0], exc_info[1], exc_info[2]

        def handle_exit(self, f, enable_idle=True):
            """
            Decorator that cleanly exits the TwistedEventLoop if ExitMainLoop is
            thrown inside of the wrapped function.  Store the exception info if
            some other exception occurs, it will be reraised after the loop quits.
            f -- function to be wrapped

            """
            def wrapper(*args,**kargs):
                rval = None
                try:
                    rval = f(*args,**kargs)
                except ExitMainLoop:
                    if self.manage_reactor:
                        self.reactor.stop()
                except:
                    import sys
                    print sys.exc_info()
                    self._exc_info = sys.exc_info()
                    if self.manage_reactor:
                        self.reactor.crash()
                if enable_idle:
                    self._enable_twisted_idle()
                return rval
            return wrapper



def _refl(name, rval=None, exit=False):
    """
    This function is used to test the main loop classes.

    >>> scr = _refl("screen")
    >>> scr.function("argument")
    screen.function('argument')
    >>> scr.callme(when="now")
    screen.callme(when='now')
    >>> scr.want_something_rval = 42
    >>> x = scr.want_something()
    screen.want_something()
    >>> x
    42
    """
    class Reflect(object):
        def __init__(self, name, rval=None):
            self._name = name
            self._rval = rval
        def __call__(self, *argl, **argd):
            args = ", ".join([repr(a) for a in argl])
            if args and argd:
                args = args + ", "
            args = args + ", ".join([k+"="+repr(v) for k,v in argd.items()])
            print self._name+"("+args+")"
            if exit: 
                raise ExitMainLoop()
            return self._rval
        def __getattr__(self, attr):
            if attr.endswith("_rval"):
                raise AttributeError()
            #print self._name+"."+attr
            if hasattr(self, attr+"_rval"):
                return Reflect(self._name+"."+attr, getattr(self, attr+"_rval"))
            return Reflect(self._name+"."+attr)
    return Reflect(name)

def _test():
    import doctest
    doctest.testmod()

if __name__=='__main__':
    _test()
<file_sep>/soundcloud/tests/test_requests.py
from contextlib import contextmanager

import fudge
import soundcloud

from nose.tools import raises, assert_raises
from requests.exceptions import HTTPError

from soundcloud.tests.utils import MockResponse


@contextmanager
def response_status(fake_http_request, status):
    response = MockResponse('{}', status_code=status)
    fake_http_request.expects_call().returns(response)
    yield


@fudge.patch('requests.get')
def test_bad_responses(fake):
    """Anything in the 400 or 500 range should raise an exception."""
    client = soundcloud.Client(client_id='foo', client_secret='foo')
    for status in range(400, 423):
        with response_status(fake, status):
            assert_raises(HTTPError, lambda: client.get('/me'))
    for status in (500, 501, 502, 503, 504, 505):
        with response_status(fake, status):
            assert_raises(HTTPError, lambda: client.get('/me'))

@fudge.patch('requests.get')
def test_ok_response(fake):
    """A 200 range response should be fine."""
    client = soundcloud.Client(client_id='foo', client_secret='foo')
    for status in (200, 201, 202, 203, 204, 205, 206):
        with response_status(fake, status):
            user = client.get('/me')

@fudge.patch('requests.get')
def test_redirects(fake):
    """Make sure 300 responses raise an exception.

    Note: ```requests``` transparently attempts redirects so if we get
    this back it means the caller has disabled redirects or the max
    number has been reached.
    """
    client = soundcloud.Client(client_id='foo', client_secret='foo')
    for status in (300, 301, 302, 303, 304, 305, 307):
        with response_status(fake, status):
            assert_raises(HTTPError, lambda: client.get('/me'))
<file_sep>/urwid/monitored_list.py
#!/usr/bin/python
#
# Urwid MonitoredList class
#    Copyright (C) 2004-2011  <NAME>
#
#    This library 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.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: http://excess.org/urwid/


from urwid.compat import PYTHON3


def _call_modified(fn):
    def call_modified_wrapper(self, *args, **kwargs):
        rval = fn(self, *args, **kwargs)
        self._modified()
        return rval
    return call_modified_wrapper

class MonitoredList(list):
    """
    This class can trigger a callback any time its contents are changed
    with the usual list operations append, extend, etc.
    """
    def _modified(self):
        pass
    
    def set_modified_callback(self, callback):
        """
        Assign a callback function in with no parameters.
        Callback's return value is ignored.

        >>> import sys
        >>> ml = MonitoredList([1,2,3])
        >>> ml.set_modified_callback(lambda: sys.stdout.write("modified\\n"))
        >>> ml
        MonitoredList([1, 2, 3])
        >>> ml.append(10)
        modified
        >>> len(ml)
        4
        >>> ml += [11, 12, 13]
        modified
        >>> ml[:] = ml[:2] + ml[-2:]
        modified
        >>> ml
        MonitoredList([1, 2, 12, 13])
        """
        self._modified = callback

    def __repr__(self):
        return "%s(%r)" % (self.__class__.__name__, list(self))

    __add__ = _call_modified(list.__add__)
    __delitem__ = _call_modified(list.__delitem__)
    if not PYTHON3:
        __delslice__ = _call_modified(list.__delslice__)
    __iadd__ = _call_modified(list.__iadd__)
    __imul__ = _call_modified(list.__imul__)
    __rmul__ = _call_modified(list.__rmul__)
    __setitem__ = _call_modified(list.__setitem__)
    if not PYTHON3:
        __setslice__ = _call_modified(list.__setslice__)
    append = _call_modified(list.append)
    extend = _call_modified(list.extend)
    insert = _call_modified(list.insert)
    pop = _call_modified(list.pop)
    remove = _call_modified(list.remove)
    reverse = _call_modified(list.reverse)
    sort = _call_modified(list.sort)


class MonitoredFocusList(MonitoredList):
    """
    This class can trigger a callback any time its contents are changed
    and any time the item "in focus" is modified or removed
    """
    def __init__(self, *argl, **argd):
        """
        This is a list that tracks one item as the focus item.  If items
        are inserted or removed it will update the focus.

        >>> ml = MonitoredFocusList([10, 11, 12, 13, 14], focus=3)
        >>> ml
        MonitoredFocusList([10, 11, 12, 13, 14], focus=3)
        >>> del(ml[1])
        >>> ml
        MonitoredFocusList([10, 12, 13, 14], focus=2)
        >>> ml[:2] = [50, 51, 52, 53]
        >>> ml
        MonitoredFocusList([50, 51, 52, 53, 13, 14], focus=4)
        >>> ml[4] = 99
        >>> ml
        MonitoredFocusList([50, 51, 52, 53, 99, 14], focus=4)
        >>> ml[:] = []
        >>> ml
        MonitoredFocusList([], focus=None)
        """
        focus = 0
        if 'focus' in argd:
            focus = argd['focus']
            del argd['focus']

        super(MonitoredFocusList, self).__init__(*argl, **argd)

        self.set_focus(focus)
        self._focus_modified = lambda ml, indices, new_items: None

    def __repr__(self):
        return "%s(%r, focus=%r)" % (
            self.__class__.__name__, list(self), self.get_focus())

    def get_focus(self):
        """
        Return the index of the item "in focus" or None if
        the list is empty.  May also be accessed as .focus

        >>> MonitoredFocusList([1,2,3], focus=2).get_focus()
        2
        >>> MonitoredFocusList().get_focus()
        >>> MonitoredFocusList([1,2,3], focus=1).focus
        1
        """
        if not self:
            return None
        if self._focus >= len(self):
            # should't happen.. but just in case
            return len(self)-1
        return self._focus

    def set_focus(self, index):
        """
        index -- index into self.widget_list, negative indexes count from
            the end, any index out of range will raise an IndexError
        
        Negative indexes work the same way they do in slicing.

        May also be set using .focus
        
        >>> ml = MonitoredFocusList([9, 10, 11])
        >>> ml.set_focus(2); ml.get_focus()
        2
        >>> ml.set_focus(-2); ml.get_focus()
        1
        >>> ml.focus = 0; ml.get_focus()
        0
        """
        if not self:
            self._focus = 0
            return
        if index < 0:
            index += len(self)
        if index < 0 or index >= len(self):
            raise IndexError('list index out of range')
        self._focus = int(index)

    focus = property(get_focus, set_focus)

    def set_focus_modified_callback(self, callback):
        """
        Assign a function to handle updating the focus when the item
        in focus is about to be changed.  The callback is in the form:

        callback(monitored_list, slc, new_items)
        indices -- a (start, stop, step) tuple whose range covers the 
            items being modified
        new_items -- a list of items replacing those at range(*indices)
        
        The only valid action for the callback is to call set_focus().
        Modifying the list in the callback has undefined behaviour.
        """
        self._focus_modified = callback

    def _handle_possible_focus_modified(self, slc, new_items=[]):
        """
        Default behaviour is to move the focus to the item following
        any removed items, or the last item in the list if that doesn't
        exist.
        """
        num_new_items = len(new_items)
        start, stop, step = indices = slc.indices(len(self))
        if step == 1:
            if start <= self._focus < stop:
                # call user handler, which might modify focus
                self._focus_modified(self, indices, new_items)

            if start + num_new_items <= self._focus < stop:
                self._focus = stop
            # adjust for added/removed items
            if stop <= self._focus:
                self._focus += num_new_items - (stop - start)

        else:
            removed = range(start, stop, step)
            if self._focus in removed:
                # call user handler, which might modify focus
                self._focus_modified(self, indices, new_items)

            if not num_new_items:
                # extended slice being removed
                if self._focus in removed:
                    self._focus += 1

                # adjust for removed items
                self._focus -= len(range(start, self._focus, step))

    def _clamp_focus(self):
        """
        adjust the focus if it is out of range
        """
        if self._focus >= len(self):
            self._focus = len(self)-1
        if self._focus < 0:
            self._focus = 0

    # override all the list methods that might affect our focus
    
    def __delitem__(self, y):
        """
        >>> ml = MonitoredFocusList([0,1,2,3], focus=2)
        >>> del ml[3]; ml
        MonitoredFocusList([0, 1, 2], focus=2)
        >>> del ml[0]; ml
        MonitoredFocusList([1, 2], focus=1)
        >>> del ml[1]; ml
        MonitoredFocusList([1], focus=0)
        >>> del ml[0]; ml
        MonitoredFocusList([], focus=None)
        >>> ml = MonitoredFocusList([5,4,6,4,5,4,6,4,5], focus=4)
        >>> del ml[1::2]; ml
        MonitoredFocusList([5, 6, 5, 6, 5], focus=2)
        >>> del ml[::2]; ml
        MonitoredFocusList([6, 6], focus=1)
        """
        if isinstance(y, slice):
            self._handle_possible_focus_modified(y)
        else:
            self._handle_possible_focus_modified(slice(y, y+1))
        rval = super(MonitoredFocusList, self).__delitem__(y)
        self._clamp_focus()
        return rval

    def __setitem__(self, i, y):
        """
        >>> def modified(monitored_list, indices, new_items):
        ...     print "range%r <- %r" % (indices, new_items)
        >>> ml = MonitoredFocusList([0,1,2,3], focus=2)
        >>> ml.set_focus_modified_callback(modified)
        >>> ml[0] = 9
        >>> ml[2] = 6
        range(2, 3, 1) <- [6]
        >>> ml[-1] = 8; ml
        MonitoredFocusList([9, 1, 6, 8], focus=2)
        >>> ml[1::2] = [12, 13]
        >>> ml[::2] = [10, 11]
        range(0, 4, 2) <- [10, 11]
        """
        if isinstance(i, slice):
            self._handle_possible_focus_modified(i, y)
        else:
            self._handle_possible_focus_modified(slice(i, i+1 or None), [y])
        return super(MonitoredFocusList, self).__setitem__(i, y)

    def __delslice__(self, i, j):
        """
        >>> def modified(monitored_list, indices, new_items):
        ...     print "range%r <- %r" % (indices, new_items)
        >>> ml = MonitoredFocusList([0,1,2,3,4], focus=2)
        >>> ml.set_focus_modified_callback(modified)
        >>> del ml[3:5]; ml
        MonitoredFocusList([0, 1, 2], focus=2)
        >>> del ml[:1]; ml
        MonitoredFocusList([1, 2], focus=1)
        >>> del ml[1:]; ml
        range(1, 2, 1) <- []
        MonitoredFocusList([1], focus=0)
        >>> del ml[:]; ml
        range(0, 1, 1) <- []
        MonitoredFocusList([], focus=None)
        """
        self._handle_possible_focus_modified(slice(i, j))
        rval = super(MonitoredFocusList, self).__delslice__(i, j)
        self._clamp_focus()
        return rval

    def __setslice__(self, i, j, y):
        """
        >>> ml = MonitoredFocusList([0,1,2,3,4], focus=2)
        >>> ml[3:5] = [-1]; ml
        MonitoredFocusList([0, 1, 2, -1], focus=2)
        >>> ml[0:1] = []; ml
        MonitoredFocusList([1, 2, -1], focus=1)
        >>> ml[1:] = [3, 4]; ml
        MonitoredFocusList([1, 3, 4], focus=1)
        >>> ml[1:] = [2]; ml
        MonitoredFocusList([1, 2], focus=1)
        >>> ml[0:1] = [9,9,9]; ml
        MonitoredFocusList([9, 9, 9, 2], focus=3)
        >>> ml[:] = []; ml
        MonitoredFocusList([], focus=None)
        """
        self._handle_possible_focus_modified(slice(i, j), y)
        rval = super(MonitoredFocusList, self).__setslice__(i, j, y)
        self._clamp_focus()
        return rval
    
    def insert(self, index, object):
        """
        >>> ml = MonitoredFocusList([0,1,2,3], focus=2)
        >>> ml.insert(-1, -1); ml
        MonitoredFocusList([0, 1, 2, -1, 3], focus=2)
        >>> ml.insert(0, -2); ml
        MonitoredFocusList([-2, 0, 1, 2, -1, 3], focus=3)
        >>> ml.insert(3, -3); ml
        MonitoredFocusList([-2, 0, 1, -3, 2, -1, 3], focus=4)
        """
        self._handle_possible_focus_modified(slice(index, index), [object])
        return super(MonitoredFocusList, self).insert(index, object)

    def pop(self, index=-1):
        """
        >>> ml = MonitoredFocusList([-2,0,1,-3,2,3], focus=4)
        >>> ml.pop(3); ml
        -3
        MonitoredFocusList([-2, 0, 1, 2, 3], focus=3)
        >>> ml.pop(0); ml
        -2
        MonitoredFocusList([0, 1, 2, 3], focus=2)
        >>> ml.pop(-1); ml
        3
        MonitoredFocusList([0, 1, 2], focus=2)
        >>> ml.pop(2); ml
        2
        MonitoredFocusList([0, 1], focus=1)
        """
        self._handle_possible_focus_modified(slice(index, index+1 or None))
        return super(MonitoredFocusList, self).pop(index)

    def remove(self, value):
        """
        >>> ml = MonitoredFocusList([-2,0,1,-3,2,-1,3], focus=4)
        >>> ml.remove(-3); ml
        MonitoredFocusList([-2, 0, 1, 2, -1, 3], focus=3)
        >>> ml.remove(-2); ml
        MonitoredFocusList([0, 1, 2, -1, 3], focus=2)
        >>> ml.remove(3); ml
        MonitoredFocusList([0, 1, 2, -1], focus=2)
        """
        index = self.index(value)
        self._handle_possible_focus_modified(slice(index, index+1 or None))
        return super(MonitoredFocusList, self).remove(value)

    def reverse(self):
        """
        >>> ml = MonitoredFocusList([0,1,2,3,4], focus=1)
        >>> ml.reverse(); ml
        MonitoredFocusList([4, 3, 2, 1, 0], focus=3)
        """
        self._focus = max(0, len(self) - self._focus - 1)
        return super(MonitoredFocusList, self).reverse()

    def sort(self):
        """
        >>> ml = MonitoredFocusList([-2,0,1,-3,2,-1,3], focus=4)
        >>> ml.sort(); ml
        MonitoredFocusList([-3, -2, -1, 0, 1, 2, 3], focus=5)
        """
        if not self:
            return
        value = self[self._focus]
        rval = super(MonitoredFocusList, self).sort()
        self._focus = self.index(value)
        return rval





def _test():
    import doctest
    doctest.testmod()

if __name__=='__main__':
    _test()

<file_sep>/urwid/tests.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Urwid unit testing .. ok, ok, ok
#    Copyright (C) 2004-2011  <NAME>
#
#    This library 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.
#
#    This library is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
#    Lesser General Public License for more details.
#
#    You should have received a copy of the GNU Lesser General Public
#    License along with this library; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
#
# Urwid web site: http://excess.org/urwid/

import unittest
try:
    from test import test_support
except ImportError:
    from test import support as test_support
from doctest import DocTestSuite, ELLIPSIS, IGNORE_EXCEPTION_DETAIL

import urwid
from urwid.util import bytes, B
from urwid.vterm_test import TermTest
from urwid.text_layout import calc_pos, calc_coords, CanNotDisplayText
from urwid.canvas import (shard_body, shard_body_tail, shards_trim_top,
    shards_trim_sides, shards_join, shards_trim_rows, shard_body_row)
from urwid.graphics import calculate_bargraph_display



class DecodeOneTest(unittest.TestCase):
    def gwt(self, ch, exp_ord, exp_pos):
        ch = B(ch)
        o, pos = urwid.str_util.decode_one(ch,0)
        assert o==exp_ord, " got:%r expected:%r" % (o, exp_ord)
        assert pos==exp_pos, " got:%r expected:%r" % (pos, exp_pos)

    def test1byte(self):
        self.gwt("ab", ord("a"), 1)
        self.gwt("\xc0a", ord("?"), 1) # error

    def test2byte(self):
        self.gwt("\xc2", ord("?"), 1) # error
        self.gwt("\xc0\x80", ord("?"), 1) # error
        self.gwt("\xc2\x80", 0x80, 2)
        self.gwt("\xdf\xbf", 0x7ff, 2)

    def test3byte(self):
        self.gwt("\xe0", ord("?"), 1) # error
        self.gwt("\xe0\xa0", ord("?"), 1) # error
        self.gwt("\xe0\x90\x80", ord("?"), 1) # error
        self.gwt("\xe0\xa0\x80", 0x800, 3)
        self.gwt("\xef\xbf\xbf", 0xffff, 3)

    def test4byte(self):
        self.gwt("\xf0", ord("?"), 1) # error
        self.gwt("\xf0\x90", ord("?"), 1) # error
        self.gwt("\xf0\x90\x80", ord("?"), 1) # error
        self.gwt("\xf0\x80\x80\x80", ord("?"), 1) # error
        self.gwt("\xf0\x90\x80\x80", 0x10000, 4)
        self.gwt("\xf3\xbf\xbf\xbf", 0xfffff, 4)



class CalcWidthTest(unittest.TestCase):
    def wtest(self, desc, s, exp):
        s = B(s)
        result = urwid.calc_width( s, 0, len(s))
        assert result==exp, "%s got:%r expected:%r" % (desc, result, exp)
    def test1(self):
        urwid.set_encoding("utf-8")
        self.wtest("narrow", "hello", 5)
        self.wtest("wide char", '\xe6\x9b\xbf', 2)
        self.wtest("invalid", '\xe6', 1)
        self.wtest("zero width", '\xcc\x80', 0)
        self.wtest("mixed", 'hello\xe6\x9b\xbf\xe6\x9b\xbf', 9)

    def test2(self):
        urwid.set_encoding("euc-jp")
        self.wtest("narrow", "hello", 5)
        self.wtest("wide", "\xA1\xA1\xA1\xA1", 4)
        self.wtest("invalid", "\xA1", 1)


class ConvertDecSpecialTest(unittest.TestCase):
    def ctest(self, desc, s, exp, expcs):
        exp = B(exp)
        urwid.set_encoding('ascii')
        c = urwid.Text(s).render((5,))
        result = c._text[0]
        assert result==exp, "%s got:%r expected:%r" % (desc, result, exp)
        resultcs = c._cs[0]
        assert resultcs==expcs, "%s got:%r expected:%r" % (desc,
                                                           resultcs, expcs)


    def test1(self):
        self.ctest("no conversion", u"hello", "hello", [(None,5)])
        self.ctest("only special", u"£££££", "}}}}}", [("0",5)])
        self.ctest("mix left", u"££abc", "}}abc", [("0",2),(None,3)])
        self.ctest("mix right", u"abc££", "abc}}", [(None,3),("0",2)])
        self.ctest("mix inner", u"a££bc", "a}}bc",
            [(None,1),("0",2),(None,2)] )
        self.ctest("mix well", u"£a£b£", "}a}b}",
            [("0",1),(None,1),("0",1),(None,1),("0",1)] )


class WithinDoubleByteTest(unittest.TestCase):
    def setUp(self):
        urwid.set_encoding("euc-jp")
    def wtest(self, s, ls, pos, expected, desc):
        result = urwid.within_double_byte(B(s), ls, pos)
        assert result==expected, "%s got:%r expected: %r" % (desc,
                                                             result, expected)
    def test1(self):
        self.wtest("mnopqr",0,2,0,'simple no high bytes')
        self.wtest("mn\xA1\xA1qr",0,2,1,'simple 1st half')
        self.wtest("mn\xA1\xA1qr",0,3,2,'simple 2nd half')
        self.wtest("m\xA1\xA1\xA1\xA1r",0,3,1,'subsequent 1st half')
        self.wtest("m\xA1\xA1\xA1\xA1r",0,4,2,'subsequent 2nd half')
        self.wtest("mn\xA1@qr",0,3,2,'simple 2nd half lo')
        self.wtest("mn\xA1\xA1@r",0,4,0,'subsequent not 2nd half lo')
        self.wtest("m\xA1\xA1\xA1@r",0,4,2,'subsequent 2nd half lo')

    def test2(self):
        self.wtest("\xA1\xA1qr",0,0,1,'begin 1st half')
        self.wtest("\xA1\xA1qr",0,1,2,'begin 2nd half')
        self.wtest("\xA1@qr",0,1,2,'begin 2nd half lo')
        self.wtest("\xA1\xA1\xA1\xA1r",0,2,1,'begin subs. 1st half')
        self.wtest("\xA1\xA1\xA1\xA1r",0,3,2,'begin subs. 2nd half')
        self.wtest("\xA1\xA1\xA1@r",0,3,2,'begin subs. 2nd half lo')
        self.wtest("\xA1@\xA1@r",0,3,2,'begin subs. 2nd half lo lo')
        self.wtest("@\xA1\xA1@r",0,3,0,'begin subs. not 2nd half lo')

    def test3(self):
        self.wtest("abc \xA1\xA1qr",4,4,1,'newline 1st half')
        self.wtest("abc \xA1\xA1qr",4,5,2,'newline 2nd half')
        self.wtest("abc \xA1@qr",4,5,2,'newline 2nd half lo')
        self.wtest("abc \xA1\xA1\xA1\xA1r",4,6,1,'newl subs. 1st half')
        self.wtest("abc \xA1\xA1\xA1\xA1r",4,7,2,'newl subs. 2nd half')
        self.wtest("abc \xA1\xA1\xA1@r",4,7,2,'newl subs. 2nd half lo')
        self.wtest("abc \xA1@\xA1@r",4,7,2,'newl subs. 2nd half lo lo')
        self.wtest("abc @\xA1\xA1@r",4,7,0,'newl subs. not 2nd half lo')



class CalcTextPosTest(unittest.TestCase):
    def ctptest(self, text, tests):
        text = B(text)
        for s,e,p, expected in tests:
            got = urwid.calc_text_pos( text, s, e, p )
            assert got == expected, "%r got:%r expected:%r" % ((s,e,p),
                                                               got, expected)
    def test1(self):
        text = "hello world out there"
        tests = [
            (0,21,0, (0,0)),
            (0,21,5, (5,5)),
            (0,21,21, (21,21)),
            (0,21,50, (21,21)),
            (2,15,50, (15,13)),
            (6,21,0, (6,0)),
            (6,21,3, (9,3)),
            ]
        self.ctptest(text, tests)

    def test2_wide(self):
        urwid.set_encoding("euc-jp")
        text = "hel\xA1\xA1 world out there"
        tests = [
            (0,21,0, (0,0)),
            (0,21,4, (3,3)),
            (2,21,2, (3,1)),
            (2,21,3, (5,3)),
            (6,21,0, (6,0)),
            ]
        self.ctptest(text, tests)

    def test3_utf8(self):
        urwid.set_encoding("utf-8")
        text = "hel\xc4\x83 world \xe2\x81\x81 there"
        tests = [
            (0,21,0, (0,0)),
            (0,21,4, (5,4)),
            (2,21,1, (3,1)),
            (2,21,2, (5,2)),
            (2,21,3, (6,3)),
            (6,21,7, (15,7)),
            (6,21,8, (16,8)),
            ]
        self.ctptest(text, tests)

    def test4_utf8(self):
        urwid.set_encoding("utf-8")
        text = "he\xcc\x80llo \xe6\x9b\xbf world"
        tests = [
            (0,15,0, (0,0)),
            (0,15,1, (1,1)),
            (0,15,2, (4,2)),
            (0,15,4, (6,4)),
            (8,15,0, (8,0)),
            (8,15,1, (8,0)),
            (8,15,2, (11,2)),
            (8,15,5, (14,5)),
            ]
        self.ctptest(text, tests)

class CalcBreaksTest(unittest.TestCase):
    def cbtest(self, width, exp):
        result = urwid.default_layout.calculate_text_segments(
            B(self.text), width, self.mode )
        assert len(result) == len(exp), repr((result, exp))
        for l,e in zip(result, exp):
            end = l[-1][-1]
            assert end == e, repr((result,exp))

    def test(self):
        for width, exp in self.do:
            self.cbtest( width, exp )

class CalcBreaksCharTest(CalcBreaksTest):
    mode = 'any'
    text = "abfghsdjf askhtrvs\naltjhgsdf ljahtshgf"
    # tests
    do = [
        ( 100, [18,38] ),
        ( 6, [6, 12, 18, 25, 31, 37, 38] ),
        ( 10, [10, 18, 29, 38] ),
    ]

class CalcBreaksDBCharTest(CalcBreaksTest):
    def setUp(self):
        urwid.set_encoding("euc-jp")
    mode = 'any'
    text = "abfgh\xA1\xA1j\xA1\xA1xskhtrvs\naltjhgsdf\xA1\xA1jahtshgf"
    # tests
    do = [
        ( 10, [10, 18, 28, 38] ),
        ( 6, [5, 11, 17, 18, 25, 31, 37, 38] ),
        ( 100, [18, 38]),
    ]

class CalcBreaksWordTest(CalcBreaksTest):
    mode = 'space'
    text = "hello world\nout there. blah"
    # tests
    do = [
        ( 10, [5, 11, 22, 27] ),
        ( 5, [5, 11, 17, 22, 27] ),
        ( 100, [11, 27] ),
    ]

class CalcBreaksWordTest2(CalcBreaksTest):
    mode = 'space'
    text = "A simple set of words, really...."
    do = [
        ( 10, [8, 15, 22, 33]),
        ( 17, [15, 33]),
        ( 13, [12, 22, 33]),
    ]

class CalcBreaksDBWordTest(CalcBreaksTest):
    def setUp(self):
        urwid.set_encoding("euc-jp")
    mode = 'space'
    text = "hel\xA1\xA1 world\nout-\xA1\xA1tre blah"
    # tests
    do = [
        ( 10, [5, 11, 21, 26] ),
        ( 5, [5, 11, 16, 21, 26] ),
        ( 100, [11, 26] ),
    ]

class CalcBreaksUTF8Test(CalcBreaksTest):
    def setUp(self):
        urwid.set_encoding("utf-8")
    mode = 'space'
    text = '\xe6\x9b\xbf\xe6\xb4\xbc\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba'
    do = [
        (4, [6, 12, 15] ),
        (10, [15] ),
        (5, [6, 12, 15] ),
    ]

class CalcBreaksCantDisplayTest(unittest.TestCase):
    def test(self):
        urwid.set_encoding("euc-jp")
        self.assertRaises(CanNotDisplayText,
            urwid.default_layout.calculate_text_segments,
            B('\xA1\xA1'), 1, 'space' )
        urwid.set_encoding("utf-8")
        self.assertRaises(CanNotDisplayText,
            urwid.default_layout.calculate_text_segments,
            B('\xe9\xa2\x96'), 1, 'space' )

class SubsegTest(unittest.TestCase):
    def setUp(self):
        urwid.set_encoding("euc-jp")

    def st(self, seg, text, start, end, exp):
        text = B(text)
        s = urwid.LayoutSegment(seg)
        result = s.subseg( text, start, end )
        assert result == exp, "Expected %r, got %r"%(exp,result)

    def test1_padding(self):
        self.st( (10, None), "", 0, 8,    [(8, None)] )
        self.st( (10, None), "", 2, 10, [(8, None)] )
        self.st( (10, 0), "", 3, 7,     [(4, 0)] )
        self.st( (10, 0), "", 0, 20,     [(10, 0)] )

    def test2_text(self):
        self.st( (10, 0, B("1234567890")), "", 0, 8,  [(8,0,B("12345678"))] )
        self.st( (10, 0, B("1234567890")), "", 2, 10, [(8,0,B("34567890"))] )
        self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 2, 8,
            [(6, 0, B("\xA1\xA156\xA1\xA1"))] )
        self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 3, 8,
            [(5, 0, B(" 56\xA1\xA1"))] )
        self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 2, 7,
            [(5, 0, B("\xA1\xA156 "))] )
        self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 3, 7,
            [(4, 0, B(" 56 "))] )
        self.st( (10, 0, B("12\xA1\xA156\xA1\xA190")), "", 0, 20,
            [(10, 0, B("12\xA1\xA156\xA1\xA190"))] )

    def test3_range(self):
        t = "1234567890"
        self.st( (10, 0, 10), t, 0, 8,    [(8, 0, 8)] )
        self.st( (10, 0, 10), t, 2, 10, [(8, 2, 10)] )
        self.st( (6, 2, 8), t, 1, 6,     [(5, 3, 8)] )
        self.st( (6, 2, 8), t, 0, 5,     [(5, 2, 7)] )
        self.st( (6, 2, 8), t, 1, 5,     [(4, 3, 7)] )
        t = "12\xA1\xA156\xA1\xA190"
        self.st( (10, 0, 10), t, 0, 8,    [(8, 0, 8)] )
        self.st( (10, 0, 10), t, 2, 10, [(8, 2, 10)] )
        self.st( (6, 2, 8), t, 1, 6,     [(1, 3), (4, 4, 8)] )
        self.st( (6, 2, 8), t, 0, 5,     [(4, 2, 6), (1, 6)] )
        self.st( (6, 2, 8), t, 1, 5,     [(1, 3), (2, 4, 6), (1, 6)] )



class CalcTranslateTest(unittest.TestCase):
    def setUp(self):
        urwid.set_encoding("utf-8")
    def test1_left(self):
        result = urwid.default_layout.layout( self.text,
            self.width, 'left', self.mode)
        assert result == self.result_left, result
    def test2_right(self):
        result = urwid.default_layout.layout( self.text,
            self.width, 'right', self.mode)
        assert result == self.result_right, result
    def test3_center(self):
        result = urwid.default_layout.layout( self.text,
            self.width, 'center', self.mode)
        assert result == self.result_center, result

class CalcTranslateCharTest(CalcTranslateTest):
    text = "It's out of control!\nYou've got to"
    mode = 'any'
    width = 15
    result_left = [
        [(15, 0, 15)],
        [(5, 15, 20), (0, 20)],
        [(13, 21, 34), (0, 34)]]
    result_right = [
        [(15, 0, 15)],
        [(10, None), (5, 15, 20), (0,20)],
        [(2, None), (13, 21, 34), (0,34)]]
    result_center = [
        [(15, 0, 15)],
        [(5, None), (5, 15, 20), (0,20)],
        [(1, None), (13, 21, 34), (0,34)]]

class CalcTranslateWordTest(CalcTranslateTest):
    text = "It's out of control!\nYou've got to"
    mode = 'space'
    width = 14
    result_left = [
        [(11, 0, 11), (0, 11)],
        [(8, 12, 20), (0, 20)],
        [(13, 21, 34), (0, 34)]]
    result_right = [
        [(3, None), (11, 0, 11), (0, 11)],
        [(6, None), (8, 12, 20), (0, 20)],
        [(1, None), (13, 21, 34), (0, 34)]]
    result_center = [
        [(2, None), (11, 0, 11), (0, 11)],
        [(3, None), (8, 12, 20), (0, 20)],
        [(1, None), (13, 21, 34), (0, 34)]]

class CalcTranslateWordTest2(CalcTranslateTest):
    text = "It's out of control!\nYou've got to "
    mode = 'space'
    width = 14
    result_left = [
        [(11, 0, 11), (0, 11)],
        [(8, 12, 20), (0, 20)],
        [(14, 21, 35), (0, 35)]]
    result_right = [
        [(3, None), (11, 0, 11), (0, 11)],
        [(6, None), (8, 12, 20), (0, 20)],
        [(14, 21, 35), (0, 35)]]
    result_center = [
        [(2, None), (11, 0, 11), (0, 11)],
        [(3, None), (8, 12, 20), (0, 20)],
        [(14, 21, 35), (0, 35)]]

class CalcTranslateWordTest3(CalcTranslateTest):
    def setUp(self):
        urwid.set_encoding('utf-8')
    text = B('\xe6\x9b\xbf\xe6\xb4\xbc\n\xe6\xb8\x8e\xe6\xba\x8f\xe6\xbd\xba')
    width = 10
    mode = 'space'
    result_left = [
        [(4, 0, 6), (0, 6)],
        [(6, 7, 16), (0, 16)]]
    result_right = [
        [(6, None), (4, 0, 6), (0, 6)],
        [(4, None), (6, 7, 16), (0, 16)]]
    result_center = [
        [(3, None), (4, 0, 6), (0, 6)],
        [(2, None), (6, 7, 16), (0, 16)]]

class CalcTranslateWordTest4(CalcTranslateTest):
    text = ' <NAME>'
    width = 3
    mode = 'space'
    result_left = [
        [(0, 0)],
        [(3, 1, 4), (0, 4)],
        [(3, 5, 8)],
        [(3, 8, 11), (0, 11)]]
    result_right = [
        [(3, None), (0, 0)],
        [(3, 1, 4), (0, 4)],
        [(3, 5, 8)],
        [(3, 8, 11), (0, 11)]]
    result_center = [
        [(2, None), (0, 0)],
        [(3, 1, 4), (0, 4)],
        [(3, 5, 8)],
        [(3, 8, 11), (0, 11)]]

class CalcTranslateWordTest5(CalcTranslateTest):
    text = ' Word.'
    width = 3
    mode = 'space'
    result_left = [[(3, 0, 3)], [(3, 3, 6), (0, 6)]]
    result_right = [[(3, 0, 3)], [(3, 3, 6), (0, 6)]]
    result_center = [[(3, 0, 3)], [(3, 3, 6), (0, 6)]]
class CalcTranslateClipTest(CalcTranslateTest):
    text = "It's out of control!\nYou've got to\n\nturn it off!!!"
    mode = 'clip'
    width = 14
    result_left = [
        [(20, 0, 20), (0, 20)],
        [(13, 21, 34), (0, 34)],
        [(0, 35)],
        [(14, 36, 50), (0, 50)]]
    result_right = [
        [(-6, None), (20, 0, 20), (0, 20)],
        [(1, None), (13, 21, 34), (0, 34)],
        [(14, None), (0, 35)],
        [(14, 36, 50), (0, 50)]]
    result_center = [
        [(-3, None), (20, 0, 20), (0, 20)],
        [(1, None), (13, 21, 34), (0, 34)],
        [(7, None), (0, 35)],
        [(14, 36, 50), (0, 50)]]

class CalcTranslateCantDisplayTest(CalcTranslateTest):
    text = B('Hello\xe9\xa2\x96')
    mode = 'space'
    width = 1
    result_left = [[]]
    result_right = [[]]
    result_center = [[]]


class CalcPosTest(unittest.TestCase):
    def setUp(self):
        self.text = "A" * 27
        self.trans = [
            [(2,None),(7,0,7),(0,7)],
            [(13,8,21),(0,21)],
            [(3,None),(5,22,27),(0,27)]]
        self.mytests = [(1,0, 0), (2,0, 0), (11,0, 7),
            (-3,1, 8), (-2,1, 8), (1,1, 9), (31,1, 21),
            (1,2, 22), (11,2, 27) ]

    def tests(self):
        for x,y, expected in self.mytests:
            got = calc_pos( self.text, self.trans, x, y )
            assert got == expected, "%r got:%r expected:%r" % ((x, y), got,
                                                               expected)


class Pos2CoordsTest(unittest.TestCase):
    pos_list = [5, 9, 20, 26]
    text = "1234567890" * 3
    mytests = [
        ( [[(15,0,15)], [(15,15,30),(0,30)]],
            [(5,0),(9,0),(5,1),(11,1)] ),
        ( [[(9,0,9)], [(12,9,21)], [(9,21,30),(0,30)]],
            [(5,0),(0,1),(11,1),(5,2)] ),
        ( [[(2,None), (15,0,15)], [(2,None), (15,15,30),(0,30)]],
            [(7,0),(11,0),(7,1),(13,1)] ),
        ( [[(3, 6, 9),(0,9)], [(5, 20, 25),(0,25)]],
            [(0,0),(3,0),(0,1),(5,1)] ),
        ( [[(10, 0, 10),(0,10)]],
            [(5,0),(9,0),(10,0),(10,0)] ),

        ]

    def test(self):
        for t, answer in self.mytests:
            for pos,a in zip(self.pos_list,answer) :
                r = calc_coords( self.text, t, pos)
                assert r==a, "%r got: %r expected: %r"%(t,r,a)


class CanvasCacheTest(unittest.TestCase):
    def setUp(self):
        # purge the cache
        urwid.CanvasCache._widgets.clear()

    def cct(self, widget, size, focus, expected):
        got = urwid.CanvasCache.fetch(widget, urwid.Widget, size, focus)
        assert expected==got, "got: %s expected: %s"%(got, expected)

    def test1(self):
        a = urwid.Text("")
        b = urwid.Text("")
        blah = urwid.TextCanvas()
        blah.finalize(a, (10,1), False)
        blah2 = urwid.TextCanvas()
        blah2.finalize(a, (15,1), False)
        bloo = urwid.TextCanvas()
        bloo.finalize(b, (20,2), True)

        urwid.CanvasCache.store(urwid.Widget, blah)
        urwid.CanvasCache.store(urwid.Widget, blah2)
        urwid.CanvasCache.store(urwid.Widget, bloo)

        self.cct(a, (10,1), False, blah)
        self.cct(a, (15,1), False, blah2)
        self.cct(a, (15,1), True, None)
        self.cct(a, (10,2), False, None)
        self.cct(b, (20,2), True, bloo)
        self.cct(b, (21,2), True, None)
        urwid.CanvasCache.invalidate(a)
        self.cct(a, (10,1), False, None)
        self.cct(a, (15,1), False, None)
        self.cct(b, (20,2), True, bloo)


class CanvasTest(unittest.TestCase):
    def ct(self, text, attr, exp_content):
        c = urwid.TextCanvas([B(t) for t in text], attr)
        content = list(c.content())
        assert content == exp_content, "got: %r expected: %r" % (content,
                                                                 exp_content)

    def ct2(self, text, attr, left, top, cols, rows, def_attr, exp_content):
        c = urwid.TextCanvas([B(t) for t in text], attr)
        content = list(c.content(left, top, cols, rows, def_attr))
        assert content == exp_content, "got: %r expected: %r" % (content,
                                                                 exp_content)

    def test1(self):
        self.ct(["Hello world"], None, [[(None, None, B("Hello world"))]])
        self.ct(["Hello world"], [[("a",5)]],
            [[("a", None, B("Hello")), (None, None, B(" world"))]])
        self.ct(["Hi","There"], None,
            [[(None, None, B("Hi   "))], [(None, None, B("There"))]])

    def test2(self):
        self.ct2(["Hello"], None, 0, 0, 5, 1, None,
            [[(None, None, B("Hello"))]])
        self.ct2(["Hello"], None, 1, 0, 4, 1, None,
            [[(None, None, B("ello"))]])
        self.ct2(["Hello"], None, 0, 0, 4, 1, None,
            [[(None, None, B("Hell"))]])
        self.ct2(["Hi","There"], None, 1, 0, 3, 2, None,
            [[(None, None, B("i  "))], [(None, None, B("her"))]])
        self.ct2(["Hi","There"], None, 0, 0, 5, 1, None,
            [[(None, None, B("Hi   "))]])
        self.ct2(["Hi","There"], None, 0, 1, 5, 1, None,
            [[(None, None, B("There"))]])

class ShardBodyTest(unittest.TestCase):
    def sbt(self, shards, shard_tail, expected):
        result = shard_body(shards, shard_tail, False)
        assert result == expected, "got: %r expected: %r" % (result, expected)

    def sbttail(self, num_rows, sbody, expected):
        result = shard_body_tail(num_rows, sbody)
        assert result == expected, "got: %r expected: %r" % (result, expected)

    def sbtrow(self, sbody, expected):
        result = list(shard_body_row(sbody))
        assert result == expected, "got: %r expected: %r" % (result, expected)


    def test1(self):
        cviews = [(0,0,10,5,None,"foo"),(0,0,5,5,None,"bar")]
        self.sbt(cviews, [],
            [(0, None, (0,0,10,5,None,"foo")),
            (0, None, (0,0,5,5,None,"bar"))])
        self.sbt(cviews, [(0, 3, None, (0,0,5,8,None,"baz"))],
            [(3, None, (0,0,5,8,None,"baz")),
            (0, None, (0,0,10,5,None,"foo")),
            (0, None, (0,0,5,5,None,"bar"))])
        self.sbt(cviews, [(10, 3, None, (0,0,5,8,None,"baz"))],
            [(0, None, (0,0,10,5,None,"foo")),
            (3, None, (0,0,5,8,None,"baz")),
            (0, None, (0,0,5,5,None,"bar"))])
        self.sbt(cviews, [(15, 3, None, (0,0,5,8,None,"baz"))],
            [(0, None, (0,0,10,5,None,"foo")),
            (0, None, (0,0,5,5,None,"bar")),
            (3, None, (0,0,5,8,None,"baz"))])

    def test2(self):
        sbody = [(0, None, (0,0,10,5,None,"foo")),
            (0, None, (0,0,5,5,None,"bar")),
            (3, None, (0,0,5,8,None,"baz"))]
        self.sbttail(5, sbody, [])
        self.sbttail(3, sbody,
            [(0, 3, None, (0,0,10,5,None,"foo")),
                    (0, 3, None, (0,0,5,5,None,"bar")),
            (0, 6, None, (0,0,5,8,None,"baz"))])

        sbody = [(0, None, (0,0,10,3,None,"foo")),
                        (0, None, (0,0,5,5,None,"bar")),
                        (3, None, (0,0,5,9,None,"baz"))]
        self.sbttail(3, sbody,
            [(10, 3, None, (0,0,5,5,None,"bar")),
            (0, 6, None, (0,0,5,9,None,"baz"))])

    def test3(self):
        self.sbtrow([(0, None, (0,0,10,5,None,"foo")),
            (0, None, (0,0,5,5,None,"bar")),
            (3, None, (0,0,5,8,None,"baz"))],
            [20])
        self.sbtrow([(0, iter("foo"), (0,0,10,5,None,"foo")),
            (0, iter("bar"), (0,0,5,5,None,"bar")),
            (3, iter("zzz"), (0,0,5,8,None,"baz"))],
            ["f","b","z"])



class ShardsTrimTest(unittest.TestCase):
    def sttop(self, shards, top, expected):
        result = shards_trim_top(shards, top)
        assert result == expected, "got: %r expected: %r" (result, expected)

    def strows(self, shards, rows, expected):
        result = shards_trim_rows(shards, rows)
        assert result == expected, "got: %r expected: %r" (result, expected)

    def stsides(self, shards, left, cols, expected):
        result = shards_trim_sides(shards, left, cols)
        assert result == expected, "got: %r expected: %r" (result, expected)


    def test1(self):
        shards = [(5, [(0,0,10,5,None,"foo"),(0,0,5,5,None,"bar")])]
        self.sttop(shards, 2,
            [(3, [(0,2,10,3,None,"foo"),(0,2,5,3,None,"bar")])])
        self.strows(shards, 2,
            [(2, [(0,0,10,2,None,"foo"),(0,0,5,2,None,"bar")])])

        shards = [(5, [(0,0,10,5,None,"foo")]),(3,[(0,0,10,3,None,"bar")])]
        self.sttop(shards, 2,
            [(3, [(0,2,10,3,None,"foo")]),(3,[(0,0,10,3,None,"bar")])])
        self.sttop(shards, 5,
            [(3, [(0,0,10,3,None,"bar")])])
        self.sttop(shards, 7,
            [(1, [(0,2,10,1,None,"bar")])])
        self.strows(shards, 7,
            [(5, [(0,0,10,5,None,"foo")]),(2, [(0,0,10,2,None,"bar")])])
        self.strows(shards, 5,
            [(5, [(0,0,10,5,None,"foo")])])
        self.strows(shards, 4,
            [(4, [(0,0,10,4,None,"foo")])])

        shards = [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]),
            (3,[(0,0,10,3,None,"bar")])]
        self.sttop(shards, 2,
            [(3, [(0,2,10,3,None,"foo"), (0,2,5,6,None,"baz")]),
            (3,[(0,0,10,3,None,"bar")])])
        self.sttop(shards, 5,
            [(3, [(0,0,10,3,None,"bar"), (0,5,5,3,None,"baz")])])
        self.sttop(shards, 7,
            [(1, [(0,2,10,1,None,"bar"), (0,7,5,1,None,"baz")])])
        self.strows(shards, 7,
            [(5, [(0,0,10,5,None,"foo"), (0,0,5,7,None,"baz")]),
            (2, [(0,0,10,2,None,"bar")])])
        self.strows(shards, 5,
            [(5, [(0,0,10,5,None,"foo"), (0,0,5,5,None,"baz")])])
        self.strows(shards, 4,
            [(4, [(0,0,10,4,None,"foo"), (0,0,5,4,None,"baz")])])


    def test2(self):
        shards = [(5, [(0,0,10,5,None,"foo"),(0,0,5,5,None,"bar")])]
        self.stsides(shards, 0, 15,
            [(5, [(0,0,10,5,None,"foo"),(0,0,5,5,None,"bar")])])
        self.stsides(shards, 6, 9,
            [(5, [(6,0,4,5,None,"foo"),(0,0,5,5,None,"bar")])])
        self.stsides(shards, 6, 6,
            [(5, [(6,0,4,5,None,"foo"),(0,0,2,5,None,"bar")])])
        self.stsides(shards, 0, 10,
            [(5, [(0,0,10,5,None,"foo")])])
        self.stsides(shards, 10, 5,
            [(5, [(0,0,5,5,None,"bar")])])
        self.stsides(shards, 1, 7,
            [(5, [(1,0,7,5,None,"foo")])])

        shards = [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]),
            (3,[(0,0,10,3,None,"bar")])]
        self.stsides(shards, 0, 15,
            [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]),
            (3,[(0,0,10,3,None,"bar")])])
        self.stsides(shards, 2, 13,
            [(5, [(2,0,8,5,None,"foo"), (0,0,5,8,None,"baz")]),
            (3,[(2,0,8,3,None,"bar")])])
        self.stsides(shards, 2, 10,
            [(5, [(2,0,8,5,None,"foo"), (0,0,2,8,None,"baz")]),
            (3,[(2,0,8,3,None,"bar")])])
        self.stsides(shards, 2, 8,
            [(5, [(2,0,8,5,None,"foo")]),
            (3,[(2,0,8,3,None,"bar")])])
        self.stsides(shards, 2, 6,
            [(5, [(2,0,6,5,None,"foo")]),
            (3,[(2,0,6,3,None,"bar")])])
        self.stsides(shards, 10, 5,
            [(8, [(0,0,5,8,None,"baz")])])
        self.stsides(shards, 11, 3,
            [(8, [(1,0,3,8,None,"baz")])])

class ShardsJoinTest(unittest.TestCase):
    def sjt(self, shard_lists, expected):
        result = shards_join(shard_lists)
        assert result == expected, "got: %r expected: %r" (result, expected)

    def test(self):
        shards1 = [(5, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz")]),
            (3,[(0,0,10,3,None,"bar")])]
        shards2 = [(3, [(0,0,10,3,None,"aaa")]),
            (5,[(0,0,10,5,None,"bbb")])]
        shards3 = [(3, [(0,0,10,3,None,"111")]),
            (2,[(0,0,10,3,None,"222")]),
            (3,[(0,0,10,3,None,"333")])]

        self.sjt([shards1], shards1)
        self.sjt([shards1, shards2],
            [(3, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz"),
                (0,0,10,3,None,"aaa")]),
            (2, [(0,0,10,5,None,"bbb")]),
            (3, [(0,0,10,3,None,"bar")])])
        self.sjt([shards1, shards3],
            [(3, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz"),
                (0,0,10,3,None,"111")]),
            (2, [(0,0,10,3,None,"222")]),
            (3, [(0,0,10,3,None,"bar"), (0,0,10,3,None,"333")])])
        self.sjt([shards1, shards2, shards3],
            [(3, [(0,0,10,5,None,"foo"), (0,0,5,8,None,"baz"),
                (0,0,10,3,None,"aaa"), (0,0,10,3,None,"111")]),
            (2, [(0,0,10,5,None,"bbb"), (0,0,10,3,None,"222")]),
            (3, [(0,0,10,3,None,"bar"), (0,0,10,3,None,"333")])])


class TagMarkupTest(unittest.TestCase):
    mytests = [
        ("simple one", "simple one", []),
        (('blue',"john"), "john", [('blue',4)]),
        (["a ","litt","le list"], "a little list", []),
        (["mix",('high',[" it ",('ital',"up a")])," little"],
            "mix it up a little",
            [(None,3),('high',4),('ital',4)]),
        ([u"££", u"x££"], u"££x££", []),
        ([B("\xc2\x80"), B("\xc2\x80")], B("\xc2\x80\xc2\x80"), []),
        ]
    def test(self):
        for input, text, attr in self.mytests:
            restext,resattr = urwid.decompose_tagmarkup( input )
            assert restext == text, "got: %r expected: %r" % (restext, text)
            assert resattr == attr, "got: %r expected: %r" % (resattr, attr)
    def test_bad_tuple(self):
        try:
            urwid.decompose_tagmarkup((1,2,3))
        except urwid.TagMarkupException, e:
            pass
        else:
            assert 0, "should have thrown exception!"

    def test_bad_type(self):
        try:
            urwid.decompose_tagmarkup(5)
        except urwid.TagMarkupException, e:
            pass
        else:
            assert 0, "should have thrown exception!"



class TextTest(unittest.TestCase):
    def setUp(self):
        self.t = urwid.Text("I walk the\ncity in the night")

    def test1_wrap(self):
        expected = [B(t) for t in "I walk the","city in   ","the night "]
        got = self.t.render((10,))._text
        assert got == expected, "got: %r expected: %r" % (got, expected)

    def test2_left(self):
        self.t.set_align_mode('left')
        expected = [B(t) for t in "I walk the        ","city in the night "]
        got = self.t.render((18,))._text
        assert got == expected, "got: %r expected: %r" % (got, expected)

    def test3_right(self):
        self.t.set_align_mode('right')
        expected = [B(t) for t in "        I walk the"," city in the night"]
        got = self.t.render((18,))._text
        assert got == expected, "got: %r expected: %r" % (got, expected)

    def test4_center(self):
        self.t.set_align_mode('center')
        expected = [B(t) for t in "    I walk the    "," city in the night"]
        got = self.t.render((18,))._text
        assert got == expected, "got: %r expected: %r" % (got, expected)



class EditTest(unittest.TestCase):
    def setUp(self):
        self.t1 = urwid.Edit("","blah blah")
        self.t2 = urwid.Edit("stuff:", "blah blah")
        self.t3 = urwid.Edit("junk:\n","blah blah\n\nbloo",1)

    def ktest(self, e, key, expected, pos, desc):
        got= e.keypress((12,),key)
        assert got == expected, "%s.  got: %r expected:%r" % (desc, got,
                                                              expected)
        assert e.edit_pos == pos, "%s. pos: %r expected pos: " % (
            desc, e.edit_pos, pos)

    def test1_left(self):
        self.t1.set_edit_pos(0)
        self.ktest(self.t1,'left','left',0,"left at left edge")

        self.ktest(self.t2,'left',None,8,"left within text")

        self.t3.set_edit_pos(10)
        self.ktest(self.t3,'left',None,9,"left after newline")

    def test2_right(self):
        self.ktest(self.t1,'right','right',9,"right at right edge")

        self.t2.set_edit_pos(8)
        self.ktest(self.t2,'right',None,9,"right at right edge-1")
        self.t3.set_edit_pos(0)
        self.t3.keypress((12,),'right')
        assert self.t3.get_pref_col((12,)) == 1


    def test3_up(self):
        self.ktest(self.t1,'up','up',9,"up at top")
        self.t2.set_edit_pos(2)
        self.t2.keypress((12,),"left")
        assert self.t2.get_pref_col((12,)) == 7
        self.ktest(self.t2,'up','up',1,"up at top again")
        assert self.t2.get_pref_col((12,)) == 7
        self.t3.set_edit_pos(10)
        self.ktest(self.t3,'up',None,0,"up at top+1")

    def test4_down(self):
        self.ktest(self.t1,'down','down',9,"down single line")
        self.t3.set_edit_pos(5)
        self.ktest(self.t3,'down',None,10,"down line 1 to 2")
        self.ktest(self.t3,'down',None,15,"down line 2 to 3")
        self.ktest(self.t3,'down','down',15,"down at bottom")

class EditRenderTest(unittest.TestCase):
    def rtest(self, w, expected_text, expected_cursor):
        expected_text = [B(t) for t in expected_text]
        get_cursor = w.get_cursor_coords((4,))
        assert get_cursor == expected_cursor, "got: %r expected: %r" % (
            get_cursor, expected_cursor)
        r = w.render((4,), focus = 1)
        text = [t for a, cs, t in [ln[0] for ln in r.content()]]
        assert text == expected_text, "got: %r expected: %r" % (text,
                                                                expected_text)
        assert r.cursor == expected_cursor, "got: %r expected: %r" % (
            r.cursor, expected_cursor)


    def test1_SpaceWrap(self):
        w = urwid.Edit("","blah blah")
        w.set_edit_pos(0)
        self.rtest(w,["blah","blah"],(0,0))

        w.set_edit_pos(4)
        self.rtest(w,["lah ","blah"],(3,0))

        w.set_edit_pos(5)
        self.rtest(w,["blah","blah"],(0,1))

        w.set_edit_pos(9)
        self.rtest(w,["blah","lah "],(3,1))

    def test2_ClipWrap(self):
        w = urwid.Edit("","blah\nblargh",1)
        w.set_wrap_mode('clip')
        w.set_edit_pos(0)
        self.rtest(w,["blah","blar"],(0,0))

        w.set_edit_pos(10)
        self.rtest(w,["blah","argh"],(3,1))

        w.set_align_mode('right')
        w.set_edit_pos(6)
        self.rtest(w,["blah","larg"],(0,1))

    def test3_AnyWrap(self):
        w = urwid.Edit("","blah blah")
        w.set_wrap_mode('any')

        self.rtest(w,["blah"," bla","h   "],(1,2))

    def test4_CursorNudge(self):
        w = urwid.Edit("","hi",align='right')
        w.keypress((4,),'end')

        self.rtest(w,[" hi "],(3,0))

        w.keypress((4,),'left')
        self.rtest(w,["  hi"],(3,0))



class SelectableText(urwid.Text):
    def selectable(self):
        return 1

    def keypress(self, size, key):
        return key

class ListBoxCalculateVisibleTest(unittest.TestCase):

    def cvtest(self, desc, body, focus, offset_rows, inset_fraction,
        exp_offset_inset, exp_cur ):

        lbox = urwid.ListBox(body)
        lbox.body.set_focus( focus )
        lbox.offset_rows = offset_rows
        lbox.inset_fraction = inset_fraction

        middle, top, bottom = lbox.calculate_visible((4,5),focus=1)
        offset_inset, focus_widget, focus_pos, _ign, cursor = middle

        if cursor is not None:
            x, y = cursor
            y += offset_inset
            cursor = x, y

        assert offset_inset == exp_offset_inset, "%s got: %r expected: %r" %(desc,offset_inset,exp_offset_inset)
        assert cursor == exp_cur, "%s (cursor) got: %r expected: %r" %(desc,cursor,exp_cur)

    def test1_simple(self):
        T = urwid.Text

        l = [T(""),T(""),T("\n"),T("\n\n"),T("\n"),T(""),T("")]

        self.cvtest( "simple top position",
            l, 3, 0, (0,1), 0, None )

        self.cvtest( "simple middle position",
            l, 3, 1, (0,1), 1, None )

        self.cvtest( "simple bottom postion",
            l, 3, 2, (0,1), 2, None )

        self.cvtest( "straddle top edge",
            l, 3, 0, (1,2), -1, None )

        self.cvtest( "straddle bottom edge",
            l, 3, 4, (0,1), 4, None )

        self.cvtest( "off bottom edge",
            l, 3, 5, (0,1), 4, None )

        self.cvtest( "way off bottom edge",
            l, 3, 100, (0,1), 4, None )

        self.cvtest( "gap at top",
            l, 0, 2, (0,1), 0, None )

        self.cvtest( "gap at top and off bottom edge",
            l, 2, 5, (0,1), 2, None )

        self.cvtest( "gap at bottom",
            l, 6, 1, (0,1), 4, None )

        self.cvtest( "gap at bottom and straddling top edge",
            l, 4, 0, (1,2), 1, None )

        self.cvtest( "gap at bottom cannot completely fill",
            [T(""),T(""),T("")], 1, 0, (0,1), 1, None )

        self.cvtest( "gap at top and bottom",
            [T(""),T(""),T("")], 1, 2, (0,1), 1, None )


    def test2_cursor(self):
        T, E = urwid.Text, urwid.Edit

        l1 = [T(""),T(""),T("\n"),E("","\n\nX"),T("\n"),T(""),T("")]
        l2 = [T(""),T(""),T("\n"),E("","YY\n\n"),T("\n"),T(""),T("")]

        l2[3].set_edit_pos(2)

        self.cvtest( "plain cursor in view",
            l1, 3, 1, (0,1), 1, (1,3) )

        self.cvtest( "cursor off top",
            l2, 3, 0, (1,3), 0, (2, 0) )

        self.cvtest( "cursor further off top",
            l2, 3, 0, (2,3), 0, (2, 0) )

        self.cvtest( "cursor off bottom",
            l1, 3, 3, (0,1), 2, (1, 4) )

        self.cvtest( "cursor way off bottom",
            l1, 3, 100, (0,1), 2, (1, 4) )



class ListBoxChangeFocusTest(unittest.TestCase):

    def cftest(self, desc, body, pos, offset_inset,
            coming_from, cursor, snap_rows,
            exp_offset_rows, exp_inset_fraction, exp_cur ):

        lbox = urwid.ListBox(body)

        lbox.change_focus( (4,5), pos, offset_inset, coming_from,
            cursor, snap_rows )

        exp = exp_offset_rows, exp_inset_fraction
        act = lbox.offset_rows, lbox.inset_fraction

        cursor = None
        focus_widget, focus_pos = lbox.body.get_focus()
        if focus_widget.selectable():
            if hasattr(focus_widget,'get_cursor_coords'):
                cursor=focus_widget.get_cursor_coords((4,))

        assert act == exp, "%s got: %s expected: %s" %(desc, act, exp)
        assert cursor == exp_cur, "%s (cursor) got: %r expected: %r" %(desc,cursor,exp_cur)


    def test1unselectable(self):
        T = urwid.Text
        l = [T("\n"),T("\n\n"),T("\n\n"),T("\n\n"),T("\n")]

        self.cftest( "simple unselectable",
            l, 2, 0, None, None, None, 0, (0,1), None )

        self.cftest( "unselectable",
            l, 2, 1, None, None, None, 1, (0,1), None )

        self.cftest( "unselectable off top",
            l, 2, -2, None, None, None, 0, (2,3), None )

        self.cftest( "unselectable off bottom",
            l, 3, 2, None, None, None, 2, (0,1), None )

    def test2selectable(self):
        T, S = urwid.Text, SelectableText
        l = [T("\n"),T("\n\n"),S("\n\n"),T("\n\n"),T("\n")]

        self.cftest( "simple selectable",
            l, 2, 0, None, None, None, 0, (0,1), None )

        self.cftest( "selectable",
            l, 2, 1, None, None, None, 1, (0,1), None )

        self.cftest( "selectable at top",
            l, 2, 0, 'below', None, None, 0, (0,1), None )

        self.cftest( "selectable at bottom",
            l, 2, 2, 'above', None, None, 2, (0,1), None )

        self.cftest( "selectable off top snap",
            l, 2, -1, 'below', None, None, 0, (0,1), None )

        self.cftest( "selectable off bottom snap",
            l, 2, 3, 'above', None, None, 2, (0,1), None )

        self.cftest( "selectable off top no snap",
            l, 2, -1, 'above', None, None, 0, (1,3), None )

        self.cftest( "selectable off bottom no snap",
            l, 2, 3, 'below', None, None, 3, (0,1), None )

    def test3large_selectable(self):
        T, S = urwid.Text, SelectableText
        l = [T("\n"),S("\n\n\n\n\n\n"),T("\n")]
        self.cftest( "large selectable no snap",
            l, 1, -1, None, None, None, 0, (1,7), None )

        self.cftest( "large selectable snap up",
            l, 1, -2, 'below', None, None, 0, (0,1), None )

        self.cftest( "large selectable snap up2",
            l, 1, -2, 'below', None, 2, 0, (0,1), None )

        self.cftest( "large selectable almost snap up",
            l, 1, -2, 'below', None, 1, 0, (2,7), None )

        self.cftest( "large selectable snap down",
            l, 1, 0, 'above', None, None, 0, (2,7), None )

        self.cftest( "large selectable snap down2",
            l, 1, 0, 'above', None, 2, 0, (2,7), None )

        self.cftest( "large selectable almost snap down",
            l, 1, 0, 'above', None, 1, 0, (0,1), None )

        m = [T("\n\n\n\n"), S("\n\n\n\n\n"), T("\n\n\n\n")]
        self.cftest( "large selectable outside view down",
            m, 1, 4, 'above', None, None, 0, (0,1), None )

        self.cftest( "large selectable outside view up",
            m, 1, -5, 'below', None, None, 0, (1,6), None )

    def test4cursor(self):
        T,E = urwid.Text, urwid.Edit
        #...



class ListBoxRenderTest(unittest.TestCase):

    def ltest(self,desc,body,focus,offset_inset_rows,exp_text,exp_cur):
        exp_text = [B(t) for t in exp_text]
        lbox = urwid.ListBox(body)
        lbox.body.set_focus( focus )
        lbox.shift_focus((4,10), offset_inset_rows )
        canvas = lbox.render( (4,5), focus=1 )

        text = [bytes().join([t for at, cs, t in ln]) for ln in canvas.content()]

        cursor = canvas.cursor

        assert text == exp_text, "%s (text) got: %r expected: %r" %(desc,text,exp_text)
        assert cursor == exp_cur, "%s (cursor) got: %r expected: %r" %(desc,cursor,exp_cur)


    def test1Simple(self):
        T = urwid.Text

        self.ltest( "simple one text item render",
            [T("1\n2")], 0, 0,
            ["1   ","2   ","    ","    ","    "],None)

        self.ltest( "simple multi text item render off bottom",
            [T("1"),T("2"),T("3\n4"),T("5"),T("6")], 2, 2,
            ["1   ","2   ","3   ","4   ","5   "],None)

        self.ltest( "simple multi text item render off top",
            [T("1"),T("2"),T("3\n4"),T("5"),T("6")], 2, 1,
            ["2   ","3   ","4   ","5   ","6   "],None)

    def test2Trim(self):
        T = urwid.Text

        self.ltest( "trim unfocused bottom",
            [T("1\n2"),T("3\n4"),T("5\n6")], 1, 2,
            ["1   ","2   ","3   ","4   ","5   "],None)

        self.ltest( "trim unfocused top",
            [T("1\n2"),T("3\n4"),T("5\n6")], 1, 1,
            ["2   ","3   ","4   ","5   ","6   "],None)

        self.ltest( "trim none full focus",
            [T("1\n2\n3\n4\n5")], 0, 0,
            ["1   ","2   ","3   ","4   ","5   "],None)

        self.ltest( "trim focus bottom",
            [T("1\n2\n3\n4\n5\n6")], 0, 0,
            ["1   ","2   ","3   ","4   ","5   "],None)

        self.ltest( "trim focus top",
            [T("1\n2\n3\n4\n5\n6")], 0, -1,
            ["2   ","3   ","4   ","5   ","6   "],None)

        self.ltest( "trim focus top and bottom",
            [T("1\n2\n3\n4\n5\n6\n7")], 0, -1,
            ["2   ","3   ","4   ","5   ","6   "],None)

    def test3Shift(self):
        T,E = urwid.Text, urwid.Edit

        self.ltest( "shift up one fit",
            [T("1\n2"),T("3"),T("4"),T("5"),T("6")], 4, 5,
            ["2   ","3   ","4   ","5   ","6   "],None)

        e = E("","ab\nc",1)
        e.set_edit_pos( 2 )
        self.ltest( "shift down one cursor over edge",
            [e,T("3"),T("4"),T("5\n6")], 0, -1,
            ["ab  ","c   ","3   ","4   ","5   "], (2,0))

        self.ltest( "shift up one cursor over edge",
            [T("1\n2"),T("3"),T("4"),E("","d\ne")], 3, 4,
            ["2   ","3   ","4   ","d   ","e   "], (1,4))

        self.ltest( "shift none cursor top focus over edge",
            [E("","ab\n"),T("3"),T("4"),T("5\n6")], 0, -1,
            ["    ","3   ","4   ","5   ","6   "], (0,0))

        e = E("","abc\nd")
        e.set_edit_pos( 3 )
        self.ltest( "shift none cursor bottom focus over edge",
            [T("1\n2"),T("3"),T("4"),e], 3, 4,
            ["1   ","2   ","3   ","4   ","abc "], (3,4))


class ListBoxKeypressTest(unittest.TestCase):

    def ktest(self, desc, key, body, focus, offset_inset,
        exp_focus, exp_offset_inset, exp_cur, lbox = None):

        if lbox is None:
            lbox = urwid.ListBox(body)
            lbox.body.set_focus( focus )
            lbox.shift_focus((4,10), offset_inset )

        ret_key = lbox.keypress((4,5),key)
        middle, top, bottom = lbox.calculate_visible((4,5),focus=1)
        offset_inset, focus_widget, focus_pos, _ign, cursor = middle

        if cursor is not None:
            x, y = cursor
            y += offset_inset
            cursor = x, y

        exp = exp_focus, exp_offset_inset
        act = focus_pos, offset_inset
        assert act == exp, "%s got: %r expected: %r" %(desc,act,exp)
        assert cursor == exp_cur, "%s (cursor) got: %r expected: %r" %(desc,cursor,exp_cur)
        return ret_key,lbox


    def test1_up(self):
        T,S,E = urwid.Text, SelectableText, urwid.Edit

        self.ktest( "direct selectable both visible", 'up',
            [S(""),S("")], 1, 1,
            0, 0, None )

        self.ktest( "selectable skip one all visible", 'up',
            [S(""),T(""),S("")], 2, 2,
            0, 0, None )

        key,lbox = self.ktest( "nothing above no scroll", 'up',
            [S("")], 0, 0,
            0, 0, None )
        assert key == 'up'

        key, lbox = self.ktest( "unselectable above no scroll", 'up',
            [T(""),T(""),S("")], 2, 2,
            2, 2, None )
        assert key == 'up'

        self.ktest( "unselectable above scroll 1", 'up',
            [T(""),S(""),T("\n\n\n")], 1, 0,
            1, 1, None )

        self.ktest( "selectable above scroll 1", 'up',
            [S(""),S(""),T("\n\n\n")], 1, 0,
            0, 0, None )

        self.ktest( "selectable above too far", 'up',
            [S(""),T(""),S(""),T("\n\n\n")], 2, 0,
            2, 1, None )

        self.ktest( "selectable above skip 1 scroll 1", 'up',
            [S(""),T(""),S(""),T("\n\n\n")], 2, 1,
            0, 0, None )

        self.ktest( "tall selectable above scroll 2", 'up',
            [S(""),S("\n"),S(""),T("\n\n\n")], 2, 0,
            1, 0, None )

        self.ktest( "very tall selectable above scroll 5", 'up',
            [S(""),S("\n\n\n\n"),S(""),T("\n\n\n\n")], 2, 0,
            1, 0, None )

        self.ktest( "very tall selected scroll within 1", 'up',
            [S(""),S("\n\n\n\n\n")], 1, -1,
            1, 0, None )

        self.ktest( "edit above pass cursor", 'up',
            [E("","abc"),E("","de")], 1, 1,
            0, 0, (2, 0) )

        key,lbox = self.ktest( "edit too far above pass cursor A", 'up',
            [E("","abc"),T("\n\n\n\n"),E("","de")], 2, 4,
            1, 0, None )

        self.ktest( "edit too far above pass cursor B", 'up',
            None, None, None,
            0, 0, (2,0), lbox )

        self.ktest( "within focus cursor made not visible", 'up',
            [T("\n\n\n"),E("hi\n","ab")], 1, 3,
            0, 0, None )

        self.ktest( "within focus cursor made not visible (2)", 'up',
            [T("\n\n\n\n"),E("hi\n","ab")], 1, 3,
            0, -1, None )

        self.ktest( "force focus unselectable" , 'up',
            [T("\n\n\n\n"),S("")], 1, 4,
            0, 0, None )

        self.ktest( "pathological cursor widget", 'up',
            [T("\n"),E("\n\n\n\n\n","a")], 1, 4,
            0, -1, None )

        self.ktest( "unselectable to unselectable", 'up',
            [T(""),T(""),T(""),T(""),T(""),T(""),T("")], 2, 0,
            1, 0, None )

        self.ktest( "unselectable over edge to same", 'up',
            [T(""),T("12\n34"),T(""),T(""),T(""),T("")],1,-1,
            1, 0, None )

        key,lbox = self.ktest( "edit short between pass cursor A", 'up',
            [E("","abcd"),E("","a"),E("","def")], 2, 2,
            1, 1, (1,1) )

        self.ktest( "edit short between pass cursor B", 'up',
            None, None, None,
            0, 0, (3,0), lbox )

        e = E("","\n\n\n\n\n")
        e.set_edit_pos(1)
        key,lbox = self.ktest( "edit cursor force scroll", 'up',
            [e], 0, -1,
            0, 0, (0,0) )
        assert lbox.inset_fraction[0] == 0

    def test2_down(self):
        T,S,E = urwid.Text, SelectableText, urwid.Edit

        self.ktest( "direct selectable both visible", 'down',
            [S(""),S("")], 0, 0,
            1, 1, None )

        self.ktest( "selectable skip one all visible", 'down',
            [S(""),T(""),S("")], 0, 0,
            2, 2, None )

        key,lbox = self.ktest( "nothing below no scroll", 'down',
            [S("")], 0, 0,
            0, 0, None )
        assert key == 'down'

        key, lbox = self.ktest( "unselectable below no scroll", 'down',
            [S(""),T(""),T("")], 0, 0,
            0, 0, None )
        assert key == 'down'

        self.ktest( "unselectable below scroll 1", 'down',
            [T("\n\n\n"),S(""),T("")], 1, 4,
            1, 3, None )

        self.ktest( "selectable below scroll 1", 'down',
            [T("\n\n\n"),S(""),S("")], 1, 4,
            2, 4, None )

        self.ktest( "selectable below too far", 'down',
            [T("\n\n\n"),S(""),T(""),S("")], 1, 4,
            1, 3, None )

        self.ktest( "selectable below skip 1 scroll 1", 'down',
            [T("\n\n\n"),S(""),T(""),S("")], 1, 3,
            3, 4, None )

        self.ktest( "tall selectable below scroll 2", 'down',
            [T("\n\n\n"),S(""),S("\n"),S("")], 1, 4,
            2, 3, None )

        self.ktest( "very tall selectable below scroll 5", 'down',
            [T("\n\n\n\n"),S(""),S("\n\n\n\n"),S("")], 1, 4,
            2, 0, None )

        self.ktest( "very tall selected scroll within 1", 'down',
            [S("\n\n\n\n\n"),S("")], 0, 0,
            0, -1, None )

        self.ktest( "edit below pass cursor", 'down',
            [E("","de"),E("","abc")], 0, 0,
            1, 1, (2, 1) )

        key,lbox=self.ktest( "edit too far below pass cursor A", 'down',
            [E("","de"),T("\n\n\n\n"),E("","abc")], 0, 0,
            1, 0, None )

        self.ktest( "edit too far below pass cursor B", 'down',
            None, None, None,
            2, 4, (2,4), lbox )

        odd_e = E("","hi\nab")
        odd_e.set_edit_pos( 2 )
        # disble cursor movement in odd_e object
        odd_e.move_cursor_to_coords = lambda s,c,xy: 0
        self.ktest( "within focus cursor made not visible", 'down',
            [odd_e,T("\n\n\n\n")], 0, 0,
            1, 1, None )

        self.ktest( "within focus cursor made not visible (2)", 'down',
            [odd_e,T("\n\n\n\n"),], 0, 0,
            1, 1, None )

        self.ktest( "force focus unselectable" , 'down',
            [S(""),T("\n\n\n\n")], 0, 0,
            1, 0, None )

        odd_e.set_edit_text( "hi\n\n\n\n\n" )
        self.ktest( "pathological cursor widget", 'down',
            [odd_e,T("\n")], 0, 0,
            1, 4, None )

        self.ktest( "unselectable to unselectable", 'down',
            [T(""),T(""),T(""),T(""),T(""),T(""),T("")], 4, 4,
            5, 4, None )

        self.ktest( "unselectable over edge to same", 'down',
            [T(""),T(""),T(""),T(""),T("12\n34"),T("")],4,4,
            4, 3, None )

        key,lbox=self.ktest( "edit short between pass cursor A", 'down',
            [E("","abc"),E("","a"),E("","defg")], 0, 0,
            1, 1, (1,1) )

        self.ktest( "edit short between pass cursor B", 'down',
            None, None, None,
            2, 2, (3,2), lbox )

        e = E("","\n\n\n\n\n")
        e.set_edit_pos(4)
        key,lbox = self.ktest( "edit cursor force scroll", 'down',
            [e], 0, 0,
            0, -1, (0,4) )
        assert lbox.inset_fraction[0] == 1

    def test3_page_up(self):
        T,S,E = urwid.Text, SelectableText, urwid.Edit

        self.ktest( "unselectable aligned to aligned", 'page up',
            [T(""),T("\n"),T("\n\n"),T(""),T("\n"),T("\n\n")], 3, 0,
            1, 0, None )

        self.ktest( "unselectable unaligned to aligned", 'page up',
            [T(""),T("\n"),T("\n"),T("\n"),T("\n"),T("\n\n")], 3,-1,
            1, 0, None )

        self.ktest( "selectable to unselectable", 'page up',
            [T(""),T("\n"),T("\n"),T("\n"),S("\n"),T("\n\n")], 4, 1,
            1, -1, None )

        self.ktest( "selectable to cut off selectable", 'page up',
            [S("\n\n"),T("\n"),T("\n"),S("\n"),T("\n\n")], 3, 1,
            0, -1, None )

        self.ktest( "seletable to selectable", 'page up',
            [T("\n\n"),S("\n"),T("\n"),S("\n"),T("\n\n")], 3, 1,
            1, 1, None )

        self.ktest( "within very long selectable", 'page up',
            [S(""),S("\n\n\n\n\n\n\n\n"),T("\n")], 1, -6,
            1, -1, None )

        e = E("","\n\nab\n\n\n\n\ncd\n")
        e.set_edit_pos(11)
        self.ktest( "within very long cursor widget", 'page up',
            [S(""),e,T("\n")], 1, -6,
            1, -2, (2, 0) )

        self.ktest( "pathological cursor widget", 'page up',
            [T(""),E("\n\n\n\n\n\n\n\n","ab"),T("")], 1, -5,
            0, 0, None )

        e = E("","\nab\n\n\n\n\ncd\n")
        e.set_edit_pos(10)
        self.ktest( "very long cursor widget snap", 'page up',
            [T(""),e,T("\n")], 1, -5,
            1, 0, (2, 1) )

        self.ktest( "slight scroll selectable", 'page up',
            [T("\n"),S("\n"),T(""),S(""),T("\n\n\n"),S("")], 5, 4,
            3, 0, None )

        self.ktest( "scroll into snap region", 'page up',
            [T("\n"),S("\n"),T(""),T(""),T("\n\n\n"),S("")], 5, 4,
            1, 0, None )

        self.ktest( "mid scroll short", 'page up',
            [T("\n"),T(""),T(""),S(""),T(""),T("\n"),S(""),T("\n")],
            6, 2,    3, 1, None )

        self.ktest( "mid scroll long", 'page up',
            [T("\n"),S(""),T(""),S(""),T(""),T("\n"),S(""),T("\n")],
            6, 2,    1, 0, None )

        self.ktest( "mid scroll perfect", 'page up',
            [T("\n"),S(""),S(""),S(""),T(""),T("\n"),S(""),T("\n")],
            6, 2,    2, 0, None )

        self.ktest( "cursor move up fail short", 'page up',
            [T("\n"),T("\n"),E("","\nab"),T(""),T("")], 2, 1,
            2, 4, (0, 4) )

        self.ktest( "cursor force fail short", 'page up',
            [T("\n"),T("\n"),E("\n","ab"),T(""),T("")], 2, 1,
            0, 0, None )

        odd_e = E("","hi\nab")
        odd_e.set_edit_pos( 2 )
        # disble cursor movement in odd_e object
        odd_e.move_cursor_to_coords = lambda s,c,xy: 0
        self.ktest( "cursor force fail long", 'page up',
            [odd_e,T("\n"),T("\n"),T("\n"),S(""),T("\n")], 4, 2,
            1, -1, None )

        self.ktest( "prefer not cut off", 'page up',
            [S("\n"),T("\n"),S(""),T("\n\n"),S(""),T("\n")], 4, 2,
            2, 1, None )

        self.ktest( "allow cut off", 'page up',
            [S("\n"),T("\n"),T(""),T("\n\n"),S(""),T("\n")], 4, 2,
            0, -1, None )

        self.ktest( "at top fail", 'page up',
            [T("\n\n"),T("\n"),T("\n\n\n")], 0, 0,
            0, 0, None )

        self.ktest( "all visible fail", 'page up',
            [T("a"),T("\n")], 0, 0,
            0, 0, None )

        self.ktest( "current ok fail", 'page up',
            [T("\n\n"),S("hi")], 1, 3,
            1, 3, None )

        self.ktest( "all visible choose top selectable", 'page up',
            [T(""),S("a"),S("b"),S("c")], 3, 3,
            1, 1, None )

        self.ktest( "bring in edge choose top", 'page up',
            [S("b"),T("-"),S("-"),T("c"),S("d"),T("-")],4,3,
            0, 0, None )

        self.ktest( "bring in edge choose top selectable", 'page up',
            [T("b"),S("-"),S("-"),T("c"),S("d"),T("-")],4,3,
            1, 1, None )

    def test4_page_down(self):
        T,S,E = urwid.Text, SelectableText, urwid.Edit

        self.ktest( "unselectable aligned to aligned", 'page down',
            [T("\n\n"),T("\n"),T(""),T("\n\n"),T("\n"),T("")], 2, 4,
            4, 3, None )

        self.ktest( "unselectable unaligned to aligned", 'page down',
            [T("\n\n"),T("\n"),T("\n"),T("\n"),T("\n"),T("")], 2, 4,
            4, 3, None )

        self.ktest( "selectable to unselectable", 'page down',
            [T("\n\n"),S("\n"),T("\n"),T("\n"),T("\n"),T("")], 1, 2,
            4, 4, None )

        self.ktest( "selectable to cut off selectable", 'page down',
            [T("\n\n"),S("\n"),T("\n"),T("\n"),S("\n\n")], 1, 2,
            4, 3, None )

        self.ktest( "seletable to selectable", 'page down',
            [T("\n\n"),S("\n"),T("\n"),S("\n"),T("\n\n")], 1, 1,
            3, 2, None )

        self.ktest( "within very long selectable", 'page down',
            [T("\n"),S("\n\n\n\n\n\n\n\n"),S("")], 1, 2,
            1, -3, None )

        e = E("","\nab\n\n\n\n\ncd\n\n")
        e.set_edit_pos(2)
        self.ktest( "within very long cursor widget", 'page down',
            [T("\n"),e,S("")], 1, 2,
            1, -2, (1, 4) )

        odd_e = E("","ab\n\n\n\n\n\n\n\n\n")
        odd_e.set_edit_pos( 1 )
        # disble cursor movement in odd_e object
        odd_e.move_cursor_to_coords = lambda s,c,xy: 0
        self.ktest( "pathological cursor widget", 'page down',
            [T(""),odd_e,T("")], 1, 1,
            2, 4, None )

        e = E("","\nab\n\n\n\n\ncd\n")
        e.set_edit_pos(2)
        self.ktest( "very long cursor widget snap", 'page down',
            [T("\n"),e,T("")], 1, 2,
            1, -3, (1, 3) )

        self.ktest( "slight scroll selectable", 'page down',
            [S(""),T("\n\n\n"),S(""),T(""),S("\n"),T("\n")], 0, 0,
            2, 4, None )

        self.ktest( "scroll into snap region", 'page down',
            [S(""),T("\n\n\n"),T(""),T(""),S("\n"),T("\n")], 0, 0,
            4, 3, None )

        self.ktest( "mid scroll short", 'page down',
            [T("\n"),S(""),T("\n"),T(""),S(""),T(""),T(""),T("\n")],
            1, 2,    4, 3, None )

        self.ktest( "mid scroll long", 'page down',
            [T("\n"),S(""),T("\n"),T(""),S(""),T(""),S(""),T("\n")],
            1, 2,    6, 4, None )

        self.ktest( "mid scroll perfect", 'page down',
            [T("\n"),S(""),T("\n"),T(""),S(""),S(""),S(""),T("\n")],
            1, 2,    5, 4, None )

        e = E("","hi\nab")
        e.set_edit_pos( 1 )
        self.ktest( "cursor move up fail short", 'page down',
            [T(""),T(""),e,T("\n"),T("\n")], 2, 1,
            2, -1, (1, 0) )


        odd_e = E("","hi\nab")
        odd_e.set_edit_pos( 1 )
        # disble cursor movement in odd_e object
        odd_e.move_cursor_to_coords = lambda s,c,xy: 0
        self.ktest( "cursor force fail short", 'page down',
            [T(""),T(""),odd_e,T("\n"),T("\n")], 2, 2,
            4, 3, None )

        self.ktest( "cursor force fail long", 'page down',
            [T("\n"),S(""),T("\n"),T("\n"),T("\n"),E("hi\n","ab")],
            1, 2,    4, 4, None )

        self.ktest( "prefer not cut off", 'page down',
            [T("\n"),S(""),T("\n\n"),S(""),T("\n"),S("\n")], 1, 2,
            3, 3, None )

        self.ktest( "allow cut off", 'page down',
            [T("\n"),S(""),T("\n\n"),T(""),T("\n"),S("\n")], 1, 2,
            5, 4, None )

        self.ktest( "at bottom fail", 'page down',
            [T("\n\n"),T("\n"),T("\n\n\n")], 2, 1,
            2, 1, None )

        self.ktest( "all visible fail", 'page down',
            [T("a"),T("\n")], 1, 1,
            1, 1, None )

        self.ktest( "current ok fail", 'page down',
            [S("hi"),T("\n\n")], 0, 0,
            0, 0, None )

        self.ktest( "all visible choose last selectable", 'page down',
            [S("a"),S("b"),S("c"),T("")], 0, 0,
            2, 2, None )

        self.ktest( "bring in edge choose last", 'page down',
            [T("-"),S("d"),T("c"),S("-"),T("-"),S("b")],1,1,
            5,4, None )

        self.ktest( "bring in edge choose last selectable", 'page down',
            [T("-"),S("d"),T("c"),S("-"),S("-"),T("b")],1,1,
            4,3, None )


class PaddingTest(unittest.TestCase):
    def ptest(self, desc, align, width, maxcol, left, right,min_width=None):
        p = urwid.Padding(None, align, width, min_width)
        l, r = p.padding_values((maxcol,),False)
        assert (l,r)==(left,right), "%s expected %s but got %s"%(
            desc, (left,right), (l,r))

    def petest(self, desc, align, width):
        try:
            urwid.Padding(None, align, width)
        except urwid.PaddingError, e:
            return
        assert 0, "%s expected error!" % desc

    def test_create(self):
        self.petest("invalid pad",6,5)
        self.petest("invalid pad type",('bad',2),5)
        self.petest("invalid width",'center','42')
        self.petest("invalid width type",'center',('gouranga',4))

    def test_values(self):
        self.ptest("left align 5 7",'left',5,7,0,2)
        self.ptest("left align 7 7",'left',7,7,0,0)
        self.ptest("left align 9 7",'left',9,7,0,0)
        self.ptest("right align 5 7",'right',5,7,2,0)
        self.ptest("center align 5 7",'center',5,7,1,1)
        self.ptest("fixed left",('fixed left',3),5,10,3,2)
        self.ptest("fixed left reduce",('fixed left',3),8,10,2,0)
        self.ptest("fixed left shrink",('fixed left',3),18,10,0,0)
        self.ptest("fixed left, right",
            ('fixed left',3),('fixed right',4),17,3,4)
        self.ptest("fixed left, right, min_width",
            ('fixed left',3),('fixed right',4),10,3,2,5)
        self.ptest("fixed left, right, min_width 2",
            ('fixed left',3),('fixed right',4),10,2,0,8)
        self.ptest("fixed right",('fixed right',3),5,10,2,3)
        self.ptest("fixed right reduce",('fixed right',3),8,10,0,2)
        self.ptest("fixed right shrink",('fixed right',3),18,10,0,0)
        self.ptest("fixed right, left",
            ('fixed right',3),('fixed left',4),17,4,3)
        self.ptest("fixed right, left, min_width",
            ('fixed right',3),('fixed left',4),10,2,3,5)
        self.ptest("fixed right, left, min_width 2",
            ('fixed right',3),('fixed left',4),10,0,2,8)
        self.ptest("relative 30",('relative',30),5,10,1,4)
        self.ptest("relative 50",('relative',50),5,10,2,3)
        self.ptest("relative 130 edge",('relative',130),5,10,5,0)
        self.ptest("relative -10 edge",('relative',-10),4,10,0,6)
        self.ptest("center relative 70",'center',('relative',70),
            10,1,2)
        self.ptest("center relative 70 grow 8",'center',('relative',70),
            10,1,1,8)

    def mctest(self, desc, left, right, size, cx, innercx):
        class Inner:
            def __init__(self, desc, innercx):
                self.desc = desc
                self.innercx = innercx
            def move_cursor_to_coords(self,size,cx,cy):
                assert cx==self.innercx, desc
        i = Inner(desc,innercx)
        p = urwid.Padding(i, ('fixed left',left),
            ('fixed right',right))
        p.move_cursor_to_coords(size, cx, 0)

    def test_cursor(self):
        self.mctest("cursor left edge",2,2,(10,2),2,0)
        self.mctest("cursor left edge-1",2,2,(10,2),1,0)
        self.mctest("cursor right edge",2,2,(10,2),7,5)
        self.mctest("cursor right edge+1",2,2,(10,2),8,5)



class FillerTest(unittest.TestCase):
    def ftest(self, desc, valign, height, maxrow, top, bottom,
            min_height=None):
        f = urwid.Filler(None, valign, height, min_height)
        t, b = f.filler_values((20,maxrow), False)
        assert (t,b)==(top,bottom), "%s expected %s but got %s"%(
            desc, (top,bottom), (t,b))

    def fetest(self, desc, valign, height):
        try:
            urwid.Filler(None, valign, height)
        except urwid.FillerError, e:
            return
        assert 0, "%s expected error!" % desc

    def test_create(self):
        self.fetest("invalid pad",6,5)
        self.fetest("invalid pad type",('bad',2),5)
        self.fetest("invalid width",'middle','42')
        self.fetest("invalid width type",'middle',('gouranga',4))
        self.fetest("invalid combination",('relative',20),
            ('fixed bottom',4))
        self.fetest("invalid combination 2",('relative',20),
            ('fixed top',4))

    def test_values(self):
        self.ftest("top align 5 7",'top',5,7,0,2)
        self.ftest("top align 7 7",'top',7,7,0,0)
        self.ftest("top align 9 7",'top',9,7,0,0)
        self.ftest("bottom align 5 7",'bottom',5,7,2,0)
        self.ftest("middle align 5 7",'middle',5,7,1,1)
        self.ftest("fixed top",('fixed top',3),5,10,3,2)
        self.ftest("fixed top reduce",('fixed top',3),8,10,2,0)
        self.ftest("fixed top shrink",('fixed top',3),18,10,0,0)
        self.ftest("fixed top, bottom",
            ('fixed top',3),('fixed bottom',4),17,3,4)
        self.ftest("fixed top, bottom, min_width",
            ('fixed top',3),('fixed bottom',4),10,3,2,5)
        self.ftest("fixed top, bottom, min_width 2",
            ('fixed top',3),('fixed bottom',4),10,2,0,8)
        self.ftest("fixed bottom",('fixed bottom',3),5,10,2,3)
        self.ftest("fixed bottom reduce",('fixed bottom',3),8,10,0,2)
        self.ftest("fixed bottom shrink",('fixed bottom',3),18,10,0,0)
        self.ftest("fixed bottom, top",
            ('fixed bottom',3),('fixed top',4),17,4,3)
        self.ftest("fixed bottom, top, min_height",
            ('fixed bottom',3),('fixed top',4),10,2,3,5)
        self.ftest("fixed bottom, top, min_height 2",
            ('fixed bottom',3),('fixed top',4),10,0,2,8)
        self.ftest("relative 30",('relative',30),5,10,1,4)
        self.ftest("relative 50",('relative',50),5,10,2,3)
        self.ftest("relative 130 edge",('relative',130),5,10,5,0)
        self.ftest("relative -10 edge",('relative',-10),4,10,0,6)
        self.ftest("middle relative 70",'middle',('relative',70),
            10,1,2)
        self.ftest("middle relative 70 grow 8",'middle',('relative',70),
            10,1,1,8)


class FrameTest(unittest.TestCase):

    def ftbtest(self, desc, focus_part, header_rows, footer_rows, size,
            focus, top, bottom):
        class FakeWidget:
            def __init__(self, rows, want_focus):
                self.ret_rows = rows
                self.want_focus = want_focus
            def rows(self, size, focus=False):
                assert self.want_focus == focus
                return self.ret_rows
        header = footer = None
        if header_rows:
            header = FakeWidget(header_rows,
                focus and focus_part == 'header')
        if footer_rows:
            footer = FakeWidget(footer_rows,
                focus and focus_part == 'footer')

        f = urwid.Frame(None, header, footer, focus_part)

        rval = f.frame_top_bottom(size, focus)
        exp = (top, bottom), (header_rows, footer_rows)
        assert exp == rval, "%s expected %r but got %r"%(
            desc,exp,rval)

    def test(self):
        self.ftbtest("simple", 'body', 0, 0, (9, 10), True, 0, 0)
        self.ftbtest("simple h", 'body', 3, 0, (9, 10), True, 3, 0)
        self.ftbtest("simple f", 'body', 0, 3, (9, 10), True, 0, 3)
        self.ftbtest("simple hf", 'body', 3, 3, (9, 10), True, 3, 3)
        self.ftbtest("almost full hf", 'body', 4, 5, (9, 10),
            True, 4, 5)
        self.ftbtest("full hf", 'body', 5, 5, (9, 10),
            True, 4, 5)
        self.ftbtest("x full h+1f", 'body', 6, 5, (9, 10),
            False, 4, 5)
        self.ftbtest("full h+1f", 'body', 6, 5, (9, 10),
            True, 4, 5)
        self.ftbtest("full hf+1", 'body', 5, 6, (9, 10),
            True, 3, 6)
        self.ftbtest("F full h+1f", 'footer', 6, 5, (9, 10),
            True, 5, 5)
        self.ftbtest("F full hf+1", 'footer', 5, 6, (9, 10),
            True, 4, 6)
        self.ftbtest("F full hf+5", 'footer', 5, 11, (9, 10),
            True, 0, 10)
        self.ftbtest("full hf+5", 'body', 5, 11, (9, 10),
            True, 0, 9)
        self.ftbtest("H full hf+1", 'header', 5, 6, (9, 10),
            True, 5, 5)
        self.ftbtest("H full h+1f", 'header', 6, 5, (9, 10),
            True, 6, 4)
        self.ftbtest("H full h+5f", 'header', 11, 5, (9, 10),
            True, 10, 0)


class PileTest(unittest.TestCase):
    def ktest(self, desc, l, focus_item, key,
            rkey, rfocus, rpref_col):
        p = urwid.Pile( l, focus_item )
        rval = p.keypress( (20,), key )
        assert rkey == rval, "%s key expected %r but got %r" %(
            desc, rkey, rval)
        new_focus = l.index(p.get_focus())
        assert new_focus == rfocus, "%s focus expected %r but got %r" %(
            desc, rfocus, new_focus)
        new_pref = p.get_pref_col((20,))
        assert new_pref == rpref_col, (
            "%s pref_col expected %r but got %r" % (
            desc, rpref_col, new_pref))

    def test_select_change(self):
        T,S,E = urwid.Text, SelectableText, urwid.Edit

        self.ktest("simple up", [S("")], 0, "up", "up", 0, 0)
        self.ktest("simple down", [S("")], 0, "down", "down", 0, 0)
        self.ktest("ignore up", [T(""),S("")], 1, "up", "up", 1, 0)
        self.ktest("ignore down", [S(""),T("")], 0, "down",
            "down", 0, 0)
        self.ktest("step up", [S(""),S("")], 1, "up", None, 0, 0)
        self.ktest("step down", [S(""),S("")], 0, "down",
            None, 1, 0)
        self.ktest("skip step up", [S(""),T(""),S("")], 2, "up",
            None, 0, 0)
        self.ktest("skip step down", [S(""),T(""),S("")], 0, "down",
            None, 2, 0)
        self.ktest("pad skip step up", [T(""),S(""),T(""),S("")], 3,
            "up", None, 1, 0)
        self.ktest("pad skip step down", [S(""),T(""),S(""),T("")], 0,
            "down", None, 2, 0)
        self.ktest("padi skip step up", [S(""),T(""),S(""),T(""),S("")],
            4, "up", None, 2, 0)
        self.ktest("padi skip step down", [S(""),T(""),S(""),T(""),
            S("")], 0, "down", None, 2, 0)
        e = E("","abcd", edit_pos=1)
        e.keypress((20,),"right") # set a pref_col
        self.ktest("pref step up", [S(""),T(""),e], 2, "up",
            None, 0, 2)
        self.ktest("pref step down", [e,T(""),S("")], 0, "down",
            None, 2, 2)
        z = E("","1234")
        self.ktest("prefx step up", [z,T(""),e], 2, "up",
            None, 0, 2)
        assert z.get_pref_col((20,)) == 2
        z = E("","1234")
        self.ktest("prefx step down", [e,T(""),z], 0, "down",
            None, 2, 2)
        assert z.get_pref_col((20,)) == 2


class ColumnsTest(unittest.TestCase):
    def cwtest(self, desc, l, divide, size, exp):
        c = urwid.Columns( l, divide )
        rval = c.column_widths( size )
        assert rval == exp, "%s expected %s, got %s"%(desc,exp,rval)

    def test_widths(self):
        x = urwid.Text("") # sample "column"
        self.cwtest( "simple 1", [x], 0, (20,), [20] )
        self.cwtest( "simple 2", [x,x], 0, (20,), [10,10] )
        self.cwtest( "simple 2+1", [x,x], 1, (20,), [10,9] )
        self.cwtest( "simple 3+1", [x,x,x], 1, (20,), [6,6,6] )
        self.cwtest( "simple 3+2", [x,x,x], 2, (20,), [5,6,5] )
        self.cwtest( "simple 3+2", [x,x,x], 2, (21,), [6,6,5] )
        self.cwtest( "simple 4+1", [x,x,x,x], 1, (25,), [6,5,6,5] )
        self.cwtest( "squish 4+1", [x,x,x,x], 1, (7,), [1,1,1,1] )
        self.cwtest( "squish 4+1", [x,x,x,x], 1, (6,), [1,2,1] )
        self.cwtest( "squish 4+1", [x,x,x,x], 1, (4,), [2,1] )

        self.cwtest( "fixed 3", [('fixed',4,x),('fixed',6,x),
            ('fixed',2,x)], 1, (25,), [4,6,2] )
        self.cwtest( "fixed 3 cut", [('fixed',4,x),('fixed',6,x),
            ('fixed',2,x)], 1, (13,), [4,6] )
        self.cwtest( "fixed 3 cut2", [('fixed',4,x),('fixed',6,x),
            ('fixed',2,x)], 1, (10,), [4] )

        self.cwtest( "mixed 4", [('weight',2,x),('fixed',5,x),
            x, ('weight',3,x)], 1, (14,), [2,5,1,3] )
        self.cwtest( "mixed 4 a", [('weight',2,x),('fixed',5,x),
            x, ('weight',3,x)], 1, (12,), [1,5,1,2] )
        self.cwtest( "mixed 4 b", [('weight',2,x),('fixed',5,x),
            x, ('weight',3,x)], 1, (10,), [2,5,1] )
        self.cwtest( "mixed 4 c", [('weight',2,x),('fixed',5,x),
            x, ('weight',3,x)], 1, (20,), [4,5,2,6] )

    def mctest(self, desc, l, divide, size, col, row, exp, f_col, pref_col):
        c = urwid.Columns( l, divide )
        rval = c.move_cursor_to_coords( size, col, row )
        assert rval == exp, "%s expected %r, got %r"%(desc,exp,rval)
        assert c.focus_col == f_col, "%s expected focus_col %s got %s"%(
            desc, f_col, c.focus_col)
        pc = c.get_pref_col( size )
        assert pc == pref_col, "%s expected pref_col %s, got %s"%(
            desc, pref_col, pc)

    def test_move_cursor(self):
        e, s, x = urwid.Edit("",""),SelectableText(""), urwid.Text("")
        self.mctest("nothing selectbl",[x,x,x],1,(20,),9,0,False,0,None)
        self.mctest("dead on",[x,s,x],1,(20,),9,0,True,1,9)
        self.mctest("l edge",[x,s,x],1,(20,),6,0,True,1,6)
        self.mctest("r edge",[x,s,x],1,(20,),13,0,True,1,13)
        self.mctest("l off",[x,s,x],1,(20,),2,0,True,1,2)
        self.mctest("r off",[x,s,x],1,(20,),17,0,True,1,17)
        self.mctest("l off 2",[x,x,s],1,(20,),2,0,True,2,2)
        self.mctest("r off 2",[s,x,x],1,(20,),17,0,True,0,17)

        self.mctest("l between",[s,s,x],1,(20,),6,0,True,0,6)
        self.mctest("r between",[x,s,s],1,(20,),13,0,True,1,13)
        self.mctest("l between 2l",[s,s,x],2,(22,),6,0,True,0,6)
        self.mctest("r between 2l",[x,s,s],2,(22,),14,0,True,1,14)
        self.mctest("l between 2r",[s,s,x],2,(22,),7,0,True,1,7)
        self.mctest("r between 2r",[x,s,s],2,(22,),15,0,True,2,15)

        # unfortunate pref_col shifting
        self.mctest("l e edge",[x,e,x],1,(20,),6,0,True,1,7)
        self.mctest("r e edge",[x,e,x],1,(20,),13,0,True,1,12)


class LineBoxTest(unittest.TestCase):
    def border(self, tl, t, tr, l, r, bl, b, br):
        return [bytes().join([tl, t, tr]),
                bytes().join([l, B(" "), r]),
                bytes().join([bl, b, br]),]

    def test_linebox_border(self):
        urwid.set_encoding("utf-8")
        t = urwid.Text("")

        l = urwid.LineBox(t).render((3,)).text

        # default
        self.assertEqual(l,
            self.border(B("\xe2\x94\x8c"), B("\xe2\x94\x80"),
                B("\xe2\x94\x90"), B("\xe2\x94\x82"), B("\xe2\x94\x82"),
                B("\xe2\x94\x94"), B("\xe2\x94\x80"), B("\xe2\x94\x98")))

        nums = [B(str(n)) for n in range(8)] 
        b = dict(zip(["tlcorner", "tline", "trcorner", "lline", "rline",
            "blcorner", "bline", "brcorner"], nums))
        l = urwid.LineBox(t, **b).render((3,)).text

        self.assertEqual(l, self.border(*nums))


class BarGraphTest(unittest.TestCase):
    def bgtest(self, desc, data, top, widths, maxrow, exp ):
        rval = calculate_bargraph_display(data,top,widths,maxrow)
        assert rval == exp, "%s expected %r, got %r"%(desc,exp,rval)

    def test1(self):
        self.bgtest('simplest',[[0]],5,[1],1,
            [(1,[(0,1)])] )
        self.bgtest('simpler',[[0],[0]],5,[1,2],5,
            [(5,[(0,3)])] )
        self.bgtest('simple',[[5]],5,[1],1,
            [(1,[(1,1)])] )
        self.bgtest('2col-1',[[2],[0]],5,[1,2],5,
            [(3,[(0,3)]), (2,[(1,1),(0,2)]) ] )
        self.bgtest('2col-2',[[0],[2]],5,[1,2],5,
            [(3,[(0,3)]), (2,[(0,1),(1,2)]) ] )
        self.bgtest('2col-3',[[2],[3]],5,[1,2],5,
            [(2,[(0,3)]), (1,[(0,1),(1,2)]), (2,[(1,3)]) ] )
        self.bgtest('3col-1',[[5],[3],[0]],5,[2,1,1],5,
            [(2,[(1,2),(0,2)]), (3,[(1,3),(0,1)]) ] )
        self.bgtest('3col-2',[[4],[4],[4]],5,[2,1,1],5,
            [(1,[(0,4)]), (4,[(1,4)]) ] )
        self.bgtest('3col-3',[[1],[2],[3]],5,[2,1,1],5,
            [(2,[(0,4)]), (1,[(0,3),(1,1)]), (1,[(0,2),(1,2)]),
             (1,[(1,4)]) ] )
        self.bgtest('3col-4',[[4],[2],[4]],5,[1,2,1],5,
            [(1,[(0,4)]), (2,[(1,1),(0,2),(1,1)]), (2,[(1,4)]) ] )

    def test2(self):
        self.bgtest('simple1a',[[2,0],[2,1]],2,[1,1],2,
            [(1,[(1,2)]),(1,[(1,1),(2,1)]) ] )
        self.bgtest('simple1b',[[2,1],[2,0]],2,[1,1],2,
            [(1,[(1,2)]),(1,[(2,1),(1,1)]) ] )
        self.bgtest('cross1a',[[2,2],[1,2]],2,[1,1],2,
            [(2,[(2,2)]) ] )
        self.bgtest('cross1b',[[1,2],[2,2]],2,[1,1],2,
            [(2,[(2,2)]) ] )
        self.bgtest('mix1a',[[3,2,1],[2,2,2],[1,2,3]],3,[1,1,1],3,
            [(1,[(1,1),(0,1),(3,1)]),(1,[(2,1),(3,2)]),
             (1,[(3,3)]) ] )
        self.bgtest('mix1b',[[1,2,3],[2,2,2],[3,2,1]],3,[1,1,1],3,
            [(1,[(3,1),(0,1),(1,1)]),(1,[(3,2),(2,1)]),
             (1,[(3,3)]) ] )

class SmoothBarGraphTest(unittest.TestCase):
    def sbgtest(self, desc, data, top, exp ):
        urwid.set_encoding('utf-8')
        g = urwid.BarGraph( ['black','red','blue'],
                None, {(1,0):'red/black', (2,1):'blue/red'})
        g.set_data( data, top )
        rval = g.calculate_display((5,3))
        assert rval == exp, "%s expected %r, got %r"%(desc,exp,rval)

    def test1(self):
        self.sbgtest('simple', [[3]], 5,
            [(1, [(0, 5)]), (1, [((1, 0, 6), 5)]), (1, [(1, 5)])] )
        self.sbgtest('boring', [[4,2]], 6,
            [(1, [(0, 5)]), (1, [(1, 5)]), (1, [(2,5)]) ] )
        self.sbgtest('two', [[4],[2]], 6,
            [(1, [(0, 5)]), (1, [(1, 3), (0, 2)]), (1, [(1, 5)]) ] )
        self.sbgtest('twos', [[3],[4]], 6,
            [(1, [(0, 5)]), (1, [((1,0,4), 3), (1, 2)]), (1, [(1,5)]) ] )
        self.sbgtest('twof', [[4],[3]], 6,
            [(1, [(0, 5)]), (1, [(1,3), ((1,0,4), 2)]), (1, [(1,5)]) ] )


class CanvasJoinTest(unittest.TestCase):
    def cjtest(self, desc, l, expected):
        l = [(c, None, False, n) for c, n in l]
        result = list(urwid.CanvasJoin(l).content())

        assert result == expected, "%s expected %r, got %r"%(
            desc, expected, result)

    def test(self):
        C = urwid.TextCanvas
        hello = C([B("hello")])
        there = C([B("there")], [[("a",5)]])
        a = C([B("a")])
        hi = C([B("hi")])
        how = C([B("how")], [[("a",1)]])
        dy = C([B("dy")])
        how_you = C([B("how"), B("you")])

        self.cjtest("one", [(hello, 5)],
            [[(None, None, B("hello"))]])
        self.cjtest("two", [(hello, 5), (there, 5)],
            [[(None, None, B("hello")), ("a", None, B("there"))]])
        self.cjtest("two space", [(hello, 7), (there, 5)],
            [[(None, None, B("hello")),(None,None,B("  ")),
            ("a", None, B("there"))]])
        self.cjtest("three space", [(hi, 4), (how, 3), (dy, 2)],
            [[(None, None, B("hi")),(None,None,B("  ")),("a",None, B("h")),
            (None,None,B("ow")),(None,None,B("dy"))]])
        self.cjtest("four space", [(a, 2), (hi, 3), (dy, 3), (a, 1)],
            [[(None, None, B("a")),(None,None,B(" ")),
            (None, None, B("hi")),(None,None,B(" ")),
            (None, None, B("dy")),(None,None,B(" ")),
            (None, None, B("a"))]])
        self.cjtest("pile 2", [(how_you, 4), (hi, 2)],
            [[(None, None, B('how')), (None, None, B(' ')),
            (None, None, B('hi'))],
            [(None, None, B('you')), (None, None, B(' ')),
            (None, None, B('  '))]])
        self.cjtest("pile 2r", [(hi, 4), (how_you, 3)],
            [[(None, None, B('hi')), (None, None, B('  ')),
            (None, None, B('how'))],
            [(None, None, B('    ')),
            (None, None, B('you'))]])

class CanvasOverlayTest(unittest.TestCase):
    def cotest(self, desc, bgt, bga, fgt, fga, l, r, et):
        bgt = B(bgt)
        fgt = B(fgt)
        bg = urwid.CompositeCanvas(
            urwid.TextCanvas([bgt],[bga]))
        fg = urwid.CompositeCanvas(
            urwid.TextCanvas([fgt],[fga]))
        bg.overlay(fg, l, 0)
        result = list(bg.content())
        assert result == et, "%s expected %r, got %r"%(
            desc, et, result)

    def test1(self):
        self.cotest("left", "qxqxqxqx", [], "HI", [], 0, 6,
            [[(None, None, B("HI")),(None,None,B("qxqxqx"))]])
        self.cotest("right", "qxqxqxqx", [], "HI", [], 6, 0,
            [[(None, None, B("qxqxqx")),(None,None,B("HI"))]])
        self.cotest("center", "qxqxqxqx", [], "HI", [], 3, 3,
            [[(None, None, B("qxq")),(None,None,B("HI")),
            (None,None,B("xqx"))]])
        self.cotest("center2", "qxqxqxqx", [], "HI  ", [], 2, 2,
            [[(None, None, B("qx")),(None,None,B("HI  ")),
            (None,None,B("qx"))]])
        self.cotest("full", "rz", [], "HI", [], 0, 0,
            [[(None, None, B("HI"))]])

    def test2(self):
        self.cotest("same","asdfghjkl",[('a',9)],"HI",[('a',2)],4,3,
            [[('a',None,B("asdf")),('a',None,B("HI")),('a',None,B("jkl"))]])
        self.cotest("diff","asdfghjkl",[('a',9)],"HI",[('b',2)],4,3,
            [[('a',None,B("asdf")),('b',None,B("HI")),('a',None,B("jkl"))]])
        self.cotest("None end","asdfghjkl",[('a',9)],"HI  ",[('a',2)],
            2,3,
            [[('a',None,B("as")),('a',None,B("HI")),
            (None,None,B("  ")),('a',None,B("jkl"))]])
        self.cotest("float end","asdfghjkl",[('a',3)],"HI",[('a',2)],
            4,3,
            [[('a',None,B("asd")),(None,None,B("f")),
            ('a',None,B("HI")),(None,None,B("jkl"))]])
        self.cotest("cover 2","asdfghjkl",[('a',5),('c',4)],"HI",
            [('b',2)],4,3,
            [[('a',None,B("asdf")),('b',None,B("HI")),('c',None,B("jkl"))]])
        self.cotest("cover 2-2","asdfghjkl",
            [('a',4),('d',1),('e',1),('c',3)],
            "HI",[('b',2)], 4, 3,
            [[('a',None,B("asdf")),('b',None,B("HI")),('c',None,B("jkl"))]])

    def test3(self):
        urwid.set_encoding("euc-jp")
        self.cotest("db0","\xA1\xA1\xA1\xA1\xA1\xA1",[],"HI",[],2,2,
            [[(None,None,B("\xA1\xA1")),(None,None,B("HI")),
            (None,None,B("\xA1\xA1"))]])
        self.cotest("db1","\xA1\xA1\xA1\xA1\xA1\xA1",[],"OHI",[],1,2,
            [[(None,None,B(" ")),(None,None,B("OHI")),
            (None,None,B("\xA1\xA1"))]])
        self.cotest("db2","\xA1\xA1\xA1\xA1\xA1\xA1",[],"OHI",[],2,1,
            [[(None,None,B("\xA1\xA1")),(None,None,B("OHI")),
            (None,None,B(" "))]])
        self.cotest("db3","\xA1\xA1\xA1\xA1\xA1\xA1",[],"OHIO",[],1,1,
            [[(None,None,B(" ")),(None,None,B("OHIO")),(None,None,B(" "))]])

class CanvasPadTrimTest(unittest.TestCase):
    def cptest(self, desc, ct, ca, l, r, et):
        ct = B(ct)
        c = urwid.CompositeCanvas(
            urwid.TextCanvas([ct], [ca]))
        c.pad_trim_left_right(l, r)
        result = list(c.content())
        assert result == et, "%s expected %r, got %r"%(
            desc, et, result)

    def test1(self):
        self.cptest("none", "asdf", [], 0, 0,
            [[(None,None,B("asdf"))]])
        self.cptest("left pad", "asdf", [], 2, 0,
            [[(None,None,B("  ")),(None,None,B("asdf"))]])
        self.cptest("right pad", "asdf", [], 0, 2,
            [[(None,None,B("asdf")),(None,None,B("  "))]])

    def test2(self):
        self.cptest("left trim", "asdf", [], -2, 0,
            [[(None,None,B("df"))]])
        self.cptest("right trim", "asdf", [], 0, -2,
            [[(None,None,B("as"))]])


class WidgetSquishTest(unittest.TestCase):
    def wstest(self, w):
        c = w.render((80,0), focus=False)
        assert c.rows() == 0
        c = w.render((80,0), focus=True)
        assert c.rows() == 0
        c = w.render((80,1), focus=False)
        assert c.rows() == 1

    def test_listbox(self):
        self.wstest(urwid.ListBox([]))
        self.wstest(urwid.ListBox([urwid.Text("hello")]))

    def test_bargraph(self):
        self.wstest(urwid.BarGraph(['foo','bar']))

    def test_graphvscale(self):
        self.wstest(urwid.GraphVScale([(0,"hello")], 1))
        self.wstest(urwid.GraphVScale([(5,"hello")], 1))

    def test_solidfill(self):
        self.wstest(urwid.SolidFill())

    def test_filler(self):
        self.wstest(urwid.Filler(urwid.Text("hello")))

    def test_overlay(self):
        self.wstest(urwid.Overlay(
            urwid.BigText("hello",urwid.Thin6x6Font()),
            urwid.SolidFill(),
            'center', None, 'middle', None))
        self.wstest(urwid.Overlay(
            urwid.Text("hello"), urwid.SolidFill(),
            'center',  ('relative', 100), 'middle', None))

    def test_frame(self):
        self.wstest(urwid.Frame(urwid.SolidFill()))
        self.wstest(urwid.Frame(urwid.SolidFill(),
            header=urwid.Text("hello")))
        self.wstest(urwid.Frame(urwid.SolidFill(),
            header=urwid.Text("hello"),
            footer=urwid.Text("hello")))

    def test_pile(self):
        self.wstest(urwid.Pile([urwid.SolidFill()]))
        self.wstest(urwid.Pile([('flow', urwid.Text("hello"))]))
        self.wstest(urwid.Pile([]))

    def test_columns(self):
        self.wstest(urwid.Columns([urwid.SolidFill()]))


def test_all():
    """
    Return a TestSuite with all tests available
    """
    unittests = [
        DecodeOneTest,
        CalcWidthTest,
        ConvertDecSpecialTest,
        WithinDoubleByteTest,
        CalcTextPosTest,
        CalcBreaksCharTest,
        CalcBreaksDBCharTest,
        CalcBreaksWordTest,
        CalcBreaksWordTest2,
        CalcBreaksDBWordTest,
        CalcBreaksUTF8Test,
        CalcBreaksCantDisplayTest,
        SubsegTest,
        CalcTranslateCharTest,
        CalcTranslateWordTest,
        CalcTranslateWordTest2,
        CalcTranslateWordTest3,
        CalcTranslateWordTest4,
        CalcTranslateWordTest5,
        CalcTranslateClipTest,
        CalcTranslateCantDisplayTest,
        CalcPosTest,
        Pos2CoordsTest,
        CanvasCacheTest,
        CanvasTest,
        ShardBodyTest,
        ShardsTrimTest,
        ShardsJoinTest,
        TagMarkupTest,
        TextTest,
        EditTest,
        EditRenderTest,
        ListBoxCalculateVisibleTest,
        ListBoxChangeFocusTest,
        ListBoxRenderTest,
        ListBoxKeypressTest,
        PaddingTest,
        FillerTest,
        FrameTest,
        PileTest,
        ColumnsTest,
        LineBoxTest,
        BarGraphTest,
        SmoothBarGraphTest,
        CanvasJoinTest,
        CanvasOverlayTest,
        CanvasPadTrimTest,
        WidgetSquishTest,
        TermTest,
        ]
    module_doctests = [
        urwid.widget,
        urwid.wimp,
        urwid.decoration,
        urwid.display_common,
        urwid.main_loop,
        urwid.monitored_list,
        urwid.raw_display,
        'urwid.split_repr', # override function with same name
        urwid.util,
        ]
    tests = unittest.TestSuite()
    for t in unittests:
        tests.addTest(unittest.TestLoader().loadTestsFromTestCase(t))
    for m in module_doctests:
        tests.addTest(DocTestSuite(
            m, optionflags=ELLIPSIS | IGNORE_EXCEPTION_DETAIL))
    return tests

if __name__ == '__main__':
    test_support.run_unittest(test_all())
