コード例 #1
0
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)
コード例 #2
0
ファイル: container.py プロジェクト: 0xPr0xy/blogger-cli
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)
コード例 #3
0
    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
コード例 #4
0
ファイル: listbox.py プロジェクト: jmcb/urwid
    def __init__(self, contents):
        """
        contents -- list to copy into this object

        Changes made to this object (when it is treated as a list) are
        detected automatically and will cause ListBox objects using
        this list walker to be updated.
        """
        if not type(contents) == list and not hasattr(contents, '__getitem__'):
            raise ListWalkerError, "SimpleListWalker expecting list like object, got: %r"%(contents,)
        MonitoredList.__init__(self, contents)
        self.focus = 0
コード例 #5
0
    def __init__(self, contents):
        """
        contents -- list to copy into this object

        Changes made to this object (when it is treated as a list) are
        detected automatically and will cause ListBox objects using
        this list walker to be updated.
        """
        if not type(contents) == list and not hasattr(contents, '__getitem__'):
            raise ListWalkerError, "SimpleListWalker expecting list like object, got: %r"%(contents,)
        MonitoredList.__init__(self, contents)
        self.focus = 0
コード例 #6
0
    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
コード例 #7
0
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)
コード例 #8
0
 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)
コード例 #9
0
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()