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.
--- 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()
--- 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
----------------
--- 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
--- 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 ##
--- 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
--- 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):
--- 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
--- 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()
--- 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)
--- 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