# HG changeset patch # User Radek Brich # Date 1302468878 -7200 # Node ID ae128c885d0fd459f89be8b902a5fe48f7dd47f2 # Parent d197ca00496f359c31cb82f497900702a77a9db4 New GridLayout. Change cursor behavior (hide on unfocus event). Change resize event to propagate through containers. Change container clipping - allowlayout=false children are clipped without borders. More Widget doc. diff -r d197ca00496f -r ae128c885d0f docs/focus.rst --- a/docs/focus.rst Fri Mar 18 20:14:44 2011 +0100 +++ b/docs/focus.rst Sun Apr 10 22:54:38 2011 +0200 @@ -14,3 +14,5 @@ hide() -> unfocus tab/shift-tab into / out off containers? + +widget.hasfocus() diff -r d197ca00496f -r ae128c885d0f docs/widget.rst --- a/docs/widget.rst Fri Mar 18 20:14:44 2011 +0100 +++ b/docs/widget.rst Sun Apr 10 22:54:38 2011 +0200 @@ -12,20 +12,58 @@ .. attribute:: Widget.parent + Parrent widget. + + .. attribute:: Widget.top + Top widget (same for every widget in one application). + + .. attribute:: Widget.x + Widget.y -.. attribute:: Widget.y + Position inside parent widget. Modified by layout manager. + .. attribute:: Widget.width + Widget.height -.. attribute:: Widget.height + Actual size. Modified by layout manager. + + +.. attribute:: Widget.sizemin + + Minimal size of widget. Under normal circumstances, widget will never be sized smaller than this. + Tuple (w, h). Both must be integers >= 1. + + +.. attribute:: Widget.sizemax + + Maximum size of widget. Widget will never be sized bigger than this. + Tuple (w, h). Integers >= 1 or None (meaning no maximum size or infinite). + + +.. attribute:: Widget.sizereq + + Size request. This is default size of the widget. Will be fulfilled if possible. + Tuple (w, h). Integers >= 1 or None (meaning use minumal size). + .. attribute:: Widget.hidden + Hidden widget does not affect layout. + + .. attribute:: Widget.allowlayout + When false, the widget is not considered in layout. + + +.. attribute:: Widget.layouthints + + Dictionary containing optional parameters for layout managers. + Event management ---------------- diff -r d197ca00496f -r ae128c885d0f tuikit/__init__.py --- a/tuikit/__init__.py Fri Mar 18 20:14:44 2011 +0100 +++ b/tuikit/__init__.py Sun Apr 10 22:54:38 2011 +0200 @@ -6,7 +6,8 @@ from tuikit.container import Container from tuikit.editbox import EditBox from tuikit.editfield import EditField -from tuikit.layout import VerticalLayout +from tuikit.label import Label +from tuikit.layout import VerticalLayout, GridLayout from tuikit.menu import Menu from tuikit.menubar import MenuBar from tuikit.scrollbar import VScrollbar diff -r d197ca00496f -r ae128c885d0f tuikit/backend_curses.py --- a/tuikit/backend_curses.py Fri Mar 18 20:14:44 2011 +0100 +++ b/tuikit/backend_curses.py Sun Apr 10 22:54:38 2011 +0200 @@ -73,6 +73,7 @@ self.screen = screen self.height, self.width = screen.getmaxyx() + self.cursor = None self.clipstack = [] self.colorstack = [] self.inputqueue = [] @@ -238,8 +239,6 @@ def erase(self): - curses.curs_set(False) - self.cursor = None self.screen.erase() @@ -258,6 +257,11 @@ if not self.testclip(x, y): return self.cursor = (y, x) + + + def hidecursor(self): + curses.curs_set(False) + self.cursor = None ## input ## diff -r d197ca00496f -r ae128c885d0f tuikit/common.py --- a/tuikit/common.py Fri Mar 18 20:14:44 2011 +0100 +++ b/tuikit/common.py Sun Apr 10 22:54:38 2011 +0200 @@ -1,5 +1,16 @@ # -*- coding: utf-8 -*- + +class Coords: + def __init__(self, x=0, y=0): + self.x = x + self.y = y + + + def __repr__(self): + return 'Coords(%(x)s,%(y)s)' % self.__dict__ + + class Rect: def __init__(self, x=0, y=0, w=0, h=0): self.x = x diff -r d197ca00496f -r ae128c885d0f tuikit/container.py --- a/tuikit/container.py Fri Mar 18 20:14:44 2011 +0100 +++ b/tuikit/container.py Sun Apr 10 22:54:38 2011 +0200 @@ -15,10 +15,11 @@ self.heightrequest = (None, None) - def add(self, widget): + def add(self, widget, **kw): self.children.append(widget) widget.parent = self widget.settop(self.top) + widget.layouthints.update(kw) def layout(self, layout): @@ -33,24 +34,32 @@ child.settop(top) + def resize(self): + Widget.resize(self) + for child in self.children: + child.emit('resize') + + def draw(self, screen, x=0, y=0): if self.hidden: return - #if self._redraw: - #self.fill(screen, self.y, self.x, self.height, self.width) - self.handle('draw', screen, x, y) + + screen.pushclip(x, y, self.width, self.height) + + Widget.draw(self, screen, x, y) + + for child in [x for x in self.children if not x.allowlayout]: + child.draw(screen, x + child.x, y + child.y) l, t, r, b = self.borders - screen.pushclip(x + l, y + t, self.width - l - r, self.height - t - b) + screen.pushclip(x+l, y+t, self.width-l-r, self.height-t-b) - for child in self.children: - #if self._redraw: - # child._redraw = True + for child in [x for x in self.children if x.allowlayout]: child.draw(screen, x + child.x, y + child.y) screen.popclip() - #self._redraw = False + screen.popclip() def mousedown(self, ev): diff -r d197ca00496f -r ae128c885d0f tuikit/editfield.py --- a/tuikit/editfield.py Fri Mar 18 20:14:44 2011 +0100 +++ b/tuikit/editfield.py Sun Apr 10 22:54:38 2011 +0200 @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- -import curses import locale from .widget import Widget @@ -17,15 +16,20 @@ self.value = value self.maxlen = None # unlimited - self.tw = self.width - 2 # real width of text field (minus space for arrows) - self.cursor = 0 # position of cursor on screen + self.tw = 0 + self.cur = 0 # position of cursor on screen self.pos = 0 # position of cursor in value self.ofs = 0 # position of value beginning on screen + self.connect('resize', self.on_resize) self.connect('draw', self.on_draw) self.connect('keypress', self.on_keypress) + def on_resize(self): + self.tw = self.width - 2 # real width of text field (minus space for arrows) + + def on_draw(self, screen, x, y): # draw value val = self.value + ' ' * self.tw # add spaces to fill rest of field @@ -43,8 +47,7 @@ c = '>' screen.putch(x + self.width-1, y, c) - # move cursor to the position - screen.showcursor(x + 1 + self.cursor, y) + self.cursor = (1 + self.cur, 0) def on_keypress(self, keyname, char): @@ -71,24 +74,24 @@ def move_left(self): - if self.cursor > 1 or (self.cursor == 1 and self.pos == 1): + if self.cur > 1 or (self.cur == 1 and self.pos == 1): # move cursor self.pos -= 1 - self.cursor -= 1 + self.cur -= 1 else: # move content in field - if self.pos > self.cursor: + if self.pos > self.cur: self.pos -= 1 self.ofs -= 1 def move_right(self): if self.pos < len(self.value): - if self.cursor < self.tw - 2 \ - or (self.cursor == self.tw - 2 and self.pos == len(self.value)-1): + if self.cur < self.tw - 2 \ + or (self.cur == self.tw - 2 and self.pos == len(self.value)-1): # move cursor self.pos += 1 - self.cursor += 1 + self.cur += 1 else: # move content in field self.pos += 1 diff -r d197ca00496f -r ae128c885d0f tuikit/layout.py --- a/tuikit/layout.py Fri Mar 18 20:14:44 2011 +0100 +++ b/tuikit/layout.py Sun Apr 10 22:54:38 2011 +0200 @@ -1,16 +1,166 @@ # -*- coding: utf-8 -*- +from .common import Rect + +class Layout: + def _getchildren(self): + return [child for child in self.container.children if child.allowlayout] + + + def _getregion(self): + c = self.container + bl, bt, br, bb = c.borders + return Rect(bl, bt, c.width - bl - br, c.height - bt - bb) + class VerticalLayout: def resize(self): v = 0 c = self.container + bl, bt, br, bb = c.borders for child in c.children: if not child.allowlayout: continue - child.x = 0 - child.width = c.width - child.y = v + child.x = bl + child.width = c.width - bl - br + child.y = bt + v v += child.height child.handle('resize') c.redraw() + + +class GridLayout(Layout): + def __init__(self, numcols=2): + self.numcols = numcols + + + def _fillgrid(self): + ''' fill grid with widgeds ''' + self._grid = [] + rown = 0 + coln = 0 + for child in self._getchildren(): + if coln == 0: + row = [] + for i in range(self.numcols): + row.append({'widget': None, 'colspan': 0, 'rowspan': 0}) + + colspan = 1 + if 'colspan' in child.layouthints: + colspan = child.layouthints['colspan'] + colspan = min(colspan, self.numcols - coln + 1) + + row[coln]['widget'] = child + row[coln]['colspan'] = colspan + + coln += colspan + if coln >= self.numcols: + coln = 0 + self._grid.append(row) + rown += 1 + + # autospan last child + if coln > 0: + row[coln-1]['colspan'] = self.numcols - coln + 1 + self._grid.append(row) + + + def _computesizes(self): + self._colminw = [0] * self.numcols + + # compute min column width for all widgets with colspan = 1 + for row in self._grid: + for coln in range(self.numcols): + w = row[coln]['widget'] + if row[coln]['colspan'] == 1: + self._colminw[coln] = max(w.sizemin[0], self._colminw[coln]) + + # adjust min width for widgets with colspan > 1 + for row in self._grid: + for coln in range(self.numcols): + w = row[coln]['widget'] + colspan = row[coln]['colspan'] + if colspan > 1: + # find min width over spanned columns + totalminw = 0 + for i in range(colspan): + totalminw += self._colminw[coln + i] + # check if spanned widget fits in + if w.sizemin[0] > totalminw: + # need to adjust colminw + addspace = w.sizemin[0] - totalminw + addall = addspace // colspan + rest = addspace % colspan + for i in range(colspan): + self._colminw[coln + i] += addall + if i < rest: + self._colminw[coln + i] += 1 + + self._rowminh = [0] * len(self._grid) + rown = 0 + for row in self._grid: + for col in row: + w = col['widget'] + if w is not None: + self._rowminh[rown] = max(self._rowminh[rown], w.sizemin[1]) + rown += 1 + + self._gridminw = sum(self._colminw) + self._gridminh = sum(self._rowminh) + + + def resize(self): + self._fillgrid() + self._computesizes() + + # enlarge container if needed + lreg = self._getregion() # layout region + cont = self.container + bl, bt, br, bb = cont.borders + if self._gridminw > lreg.w: + cont.width = self._gridminw + bl + br + if self._gridminh > lreg.h: + cont.height = self._gridminh + bt + bb + + # compute actual width of columns + colw = [0] * self.numcols + lreg = self._getregion() # layout region + restw = lreg.w - self._gridminw + resth = lreg.h - self._gridminh + for c in range(self.numcols): + colw[c] = self._colminw[c] + restw // self.numcols + if c < restw % self.numcols: + colw[c] += 1 + + # place widgets + colx = lreg.x + coly = lreg.y + rown = 0 + numrows = len(self._grid) + for row in self._grid: + coln = 0 + rowh = self._rowminh[rown] + resth // numrows + if rown < resth % numrows: + rowh += 1 + + for col in row: + w = col['widget'] + if w is not None: + w.x = colx + w.y = coly + w.width = colw[coln] + w.height = rowh + w.handle('resize') + + colspan = col['colspan'] + if colspan > 1: + for i in range(1,colspan): + w.width += colw[coln + i] + + colx += colw[coln] + + rown += 1 + colx = lreg.x + coly += rowh + + cont.redraw() diff -r d197ca00496f -r ae128c885d0f tuikit/widget.py --- a/tuikit/widget.py Fri Mar 18 20:14:44 2011 +0100 +++ b/tuikit/widget.py Sun Apr 10 22:54:38 2011 +0200 @@ -7,13 +7,19 @@ def __init__(self, width = 10, height = 10): self.parent = None self.top = None - # placing - set by parent widget + # placing - set by parent widget's layout manager self.x = 0 self.y = 0 # size - self.width = width - self.height = height + self._width = width + self._height = height + self.sizemin = (1,1) + self.sizemax = (None, None) + self.sizereq = (10,10) self.allowlayout = True + self.layouthints = {} + # cursor + self.cursor = None # redraw request self._redraw = True self.hidden = False @@ -31,6 +37,25 @@ } + @property + def width(self): + return self._width + + @width.setter + def width(self, value): + self._width = value + self.emit('resize') + + @property + def height(self): + return self._height + + @height.setter + def height(self, value): + self._height = value + self.emit('resize') + + def settop(self, top): self.top = top @@ -79,10 +104,17 @@ def draw(self, screen, x=0, y=0): - #if self._redraw: - if not self.hidden: - self.handle('draw', screen, x, y) - #self._redraw = False + if self.hidden: + return + + self.handle('draw', screen, x, y) + + if self.hasfocus(): + if self.cursor: + cx, cy = self.cursor + screen.showcursor(x + cx, y + cy) + else: + screen.hidecursor() ### focus @@ -113,10 +145,12 @@ def focus(self): + '''handle focus event''' self.handle('focus') def unfocus(self, newfocus=None): + '''handle unfocus event''' self.handle('unfocus', newfocus) diff -r d197ca00496f -r ae128c885d0f tuikit/window.py --- a/tuikit/window.py Fri Mar 18 20:14:44 2011 +0100 +++ b/tuikit/window.py Sun Apr 10 22:54:38 2011 +0200 @@ -23,12 +23,15 @@ self.moving = False self.closebtn = Button('x') + self.closebtn.allowlayout = False self.closebtn.x = self.width - 5 self.closebtn.width = 3 self.closebtn.connect('click', self.on_closebtn) self.closebtn.bg = (4, 'bold') self.closebtn.bghi = (5, 'bold') self.add(self.closebtn) + + self.borders = (1, 1, 1, 1) @property