Rework layouts: Layout is now normal Container which places its children upon resize event.
Drop TopWindow, top is now any subclass of Container.
Add floater concept: floaters are widgets drawn over normal widgets, not clipped by parent.
Add HScrollbar and Scrollbar abstract base class.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/demo_anchorlayout.py Fri Jan 18 22:36:50 2013 +0100
@@ -0,0 +1,65 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import locale
+import os
+
+from tuikit.application import Application
+from tuikit.window import Window
+from tuikit.label import Label
+from tuikit.button import Button
+from tuikit.layout import AnchorLayout
+from tuikit.common import Borders
+
+
+class MyApplication(Application):
+ def __init__(self):
+ Application.__init__(self)
+ self.top = AnchorLayout()
+ self.top.name = 'top'
+ self.top.add_handler('keypress', self.on_top_keypress)
+
+ win = Window()
+ win.name = 'window'
+ win.title = 'AnchorLayout demo'
+ win.resize(80, 25)
+ self.top.add(win, halign='left', valign='top')
+ self.win = win
+
+ button_valign = Button('valign: ' + self.win.hints['valign'].selected)
+ button_valign.name = 'button_valign'
+ button_valign.add_handler('click', self.on_button_align_click)
+ win.add(button_valign, halign='center', margin=Borders(t=2))
+
+ button_halign = Button('halign: ' + self.win.hints['halign'].selected)
+ button_halign.name = 'button_halign'
+ button_halign.add_handler('click', self.on_button_align_click)
+ win.add(button_halign, halign='center', margin=Borders(t=4))
+
+ label_margin = Label(str(self.win.hints['margin']))
+ label_margin.name = 'label_margin'
+ label_margin.add_handler('draw', self.on_label_margin_draw)
+ win.add(label_margin, halign='center', margin=Borders(t=6))
+
+ def on_button_align_click(self, ev):
+ align_type = ev.originator.label.split(':', 1)[0]
+ align = self.win.hints[align_type]
+ align.select_next()
+ ev.originator.label = '%s: %s' % (align_type, align.selected)
+ self.top.emit('resize')
+ return True
+
+ def on_label_margin_draw(self, ev):
+ ev.originator.label = str(self.win.hints['margin'])
+
+ def on_top_keypress(self, ev):
+ if ev.keyname == 'escape':
+ self.terminate()
+ return True
+
+
+if __name__ == '__main__':
+ locale.setlocale(locale.LC_ALL, '')
+ os.environ['ESCDELAY'] = '25' # do not wait 1 second after pressing Escape key
+ app = MyApplication()
+ app.start()
--- a/demo_checkbox.py Thu Jan 10 00:03:34 2013 +0100
+++ b/demo_checkbox.py Fri Jan 18 22:36:50 2013 +0100
@@ -4,16 +4,15 @@
import locale
locale.setlocale(locale.LC_ALL, '')
-from tuikit import *
+from tuikit import Application, VerticalLayout, ComboBox, Checkbox
class MyApplication(Application):
def __init__(self):
Application.__init__(self)
+ self.top = VerticalLayout(homogeneous=False)
self.top.add_handler('keypress', self.on_top_keypress)
- self.top.layout = VerticalLayout(homogeneous=False)
-
combo = ComboBox(items=['abc', 'xyz'])
self.top.add(combo)
--- a/demo_colors.py Thu Jan 10 00:03:34 2013 +0100
+++ b/demo_colors.py Fri Jan 18 22:36:50 2013 +0100
@@ -4,27 +4,27 @@
import locale
locale.setlocale(locale.LC_ALL, '')
-from tuikit import Application, Window, Label, VerticalLayout
+from tuikit import Application, Window, Label, AnchorLayout, VerticalLayout
class MyApplication(Application):
def __init__(self):
- Application.__init__(self)
+ Application.__init__(self, top_layout=AnchorLayout)
self.top.add_handler('keypress', self.on_top_keypress)
- win = Window()
- self.top.add(win)
- win.layout = VerticalLayout()
+ win = Window(inner_layout=VerticalLayout)
+ win.resize(30,10)
+ win.title = 'styles'
for attr in ['blink', 'bold', 'standout', 'underline']:
label = Label(attr)
label.color = 'test-' + attr
- self.top.add(label)
+ win.add(label)
- self.top.layout = VerticalLayout()
+ self.top.add(win)
- def applytheme(self):
- Application.applytheme(self)
+ def apply_theme(self):
+ Application.apply_theme(self)
self.driver.setcolor('test-blink', 'cyan on blue, blink')
self.driver.setcolor('test-bold', 'cyan on blue, bold')
self.driver.setcolor('test-standout', 'cyan on blue, standout')
--- a/demo_editor.py Thu Jan 10 00:03:34 2013 +0100
+++ b/demo_editor.py Fri Jan 18 22:36:50 2013 +0100
@@ -7,7 +7,6 @@
import os
from tuikit.application import Application
-from tuikit.editfield import EditField
from tuikit.textedit import TextEdit
@@ -16,14 +15,9 @@
Application.__init__(self)
self.top.add_handler('keypress', self.on_top_keypress)
- #edit = EditField(50, 'DlouhyTest12')
- #self.top.add(edit)
-
t = open('tuikit/widget.py').read()
- textedit = TextEdit(100, 40, t)
- self.top.add(textedit)
- textedit.x = 2
- self.textedit = textedit
+ textedit = TextEdit(t)
+ self.top.add(textedit, halign='fill', valign='fill')
def on_top_keypress(self, ev):
if ev.keyname == 'escape':
--- a/demo_input.py Thu Jan 10 00:03:34 2013 +0100
+++ b/demo_input.py Fri Jan 18 22:36:50 2013 +0100
@@ -12,9 +12,8 @@
Application.__init__(self)
self.text = ''
- self.textedit = TextEdit(100, 40, self.text)
- self.top.add(self.textedit)
- self.textedit.x = 2
+ self.textedit = TextEdit(self.text)
+ self.top.add(self.textedit, halign='fill', valign='fill')
editbox = self.textedit.editbox
editbox.add_handler('keypress', self.on_any_input)
--- a/demo_layout.py Thu Jan 10 00:03:34 2013 +0100
+++ b/demo_layout.py Fri Jan 18 22:36:50 2013 +0100
@@ -10,12 +10,11 @@
class MyApplication(Application):
def __init__(self):
Application.__init__(self)
+
+ self.top = VerticalLayout(homogeneous=False)
self.top.add_handler('keypress', self.on_top_keypress)
-
#self.top.borders = (1,1,1,1)
- self.top.layout = VerticalLayout(homogeneous=False)
-
self._row_num = 0
self.buildrow()
self.buildrow(expand=True)
@@ -26,9 +25,8 @@
self.buildrow(homogeneous=True, fill=True, spacing=2)
def buildrow(self, homogeneous=False, spacing=0, expand=False, fill=False):
- hbox = Container()
- hbox.sizereq.h = 2
- hbox.layout = HorizontalLayout(homogeneous=homogeneous, spacing=spacing)
+ hbox = HorizontalLayout(homogeneous=homogeneous, spacing=spacing)
+ hbox.resize(h=2)
self._row_num += 1
hbox.name = 'hbox' + str(self._row_num)
self.top.add(hbox)
--- a/demo_treeview.py Thu Jan 10 00:03:34 2013 +0100
+++ b/demo_treeview.py Fri Jan 18 22:36:50 2013 +0100
@@ -9,7 +9,7 @@
class MyApplication(Application):
def __init__(self):
- Application.__init__(self)
+ Application.__init__(self, top_layout=VerticalLayout)
self.top.add_handler('keypress', self.on_top_keypress)
model = TreeModel()
@@ -29,8 +29,6 @@
scroll.add(view)
self.top.add(scroll, expand=True, fill=True)
- self.top.layout = VerticalLayout()
-
def on_top_keypress(self, ev):
if ev.keyname == 'escape':
self.terminate()
--- a/demo_window.py Thu Jan 10 00:03:34 2013 +0100
+++ b/demo_window.py Fri Jan 18 22:36:50 2013 +0100
@@ -8,28 +8,33 @@
from tuikit.application import Application
from tuikit.window import Window
from tuikit.button import Button
+from tuikit import AnchorLayout
+from tuikit.common import Borders
class MyApplication(Application):
def __init__(self):
Application.__init__(self)
+ self.top = AnchorLayout()
+ self.top.name = 'top'
self.top.add_handler('keypress', self.on_top_keypress)
#edit = EditField(50, 'DlouhyTest12')
#self.top.add(edit)
win = Window()
- self.top.add(win)
+ win.title = 'demo_window'
+ win.resize(80, 25)
+ self.top.add(win, halign='left', valign='top')
button = Button('click!')
- win.add(button)
- button.x = 10
- button.y = 7
+# win.add(button, x=10, y=6)
button.add_handler('click', self.on_button_click)
self.button = button
- subwin = Window(8,8)
+ subwin = Window()
+ subwin.name = 'subwin'
win.add(subwin)
@@ -48,9 +53,9 @@
locale.setlocale(locale.LC_ALL, '')
os.environ['ESCDELAY'] = '25' # do not wait 1 second after pressing Escape key
app = MyApplication()
- #app.start()
+ app.start()
- cProfile.run('app.start()', 'demo_window.appstats')
- p = pstats.Stats('demo_window.appstats')
- p.sort_stats('time', 'cumulative').print_stats(20)
+ #cProfile.run('app.start()', 'demo_window.appstats')
+ #p = pstats.Stats('demo_window.appstats')
+ #p.sort_stats('time', 'cumulative').print_stats(20)
--- a/docs/index.rst Thu Jan 10 00:03:34 2013 +0100
+++ b/docs/index.rst Fri Jan 18 22:36:50 2013 +0100
@@ -18,19 +18,3 @@
colors
driver
-.. inheritance-diagram:: tuikit.application
- tuikit.events
- tuikit.widget
- tuikit.container
- tuikit.window
- tuikit.button
- tuikit.label
- tuikit.scrollbar
- tuikit.textedit
- tuikit.editfield
- tuikit.editbox
- tuikit.combobox
- tuikit.menu
- tuikit.menubar
- tuikit.treeview
-
--- a/docs/widget.rst Thu Jan 10 00:03:34 2013 +0100
+++ b/docs/widget.rst Fri Jan 18 22:36:50 2013 +0100
@@ -16,11 +16,11 @@
.. attribute:: x
y
-
+
Position inside parent widget. Modified by layout manager.
-
+
.. attribute:: width
height
-
+
Actual size. Modified by layout manager.
--- a/sdlterm/src/sdlterm.cc Thu Jan 10 00:03:34 2013 +0100
+++ b/sdlterm/src/sdlterm.cc Fri Jan 18 22:36:50 2013 +0100
@@ -371,9 +371,6 @@
bool Terminal::wait_event(Event &event, Uint32 timeout)
{
- static SDL_Event sdl_event;
- bool event_ready = false;
-
// use timer to simulate SDL_WaitEventTimeout, which is not available in SDL 1.2
SDL_TimerID timer_id = NULL;
if (timeout)
@@ -381,6 +378,9 @@
timer_id = SDL_AddTimer(timeout, _wait_event_callback, NULL);
}
+ // loop until we have something to return
+ SDL_Event sdl_event;
+ bool event_ready = false;
while (SDL_WaitEvent(&sdl_event) && !event_ready)
{
switch (sdl_event.type)
--- a/tuikit/__init__.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/__init__.py Fri Jan 18 22:36:50 2013 +0100
@@ -9,7 +9,7 @@
from tuikit.editbox import EditBox
from tuikit.editfield import EditField
from tuikit.label import Label
-from tuikit.layout import VerticalLayout, HorizontalLayout, GridLayout
+from tuikit.layout import VerticalLayout, HorizontalLayout, GridLayout, AnchorLayout
from tuikit.menu import Menu
from tuikit.menubar import MenuBar
from tuikit.pager import Pager
--- a/tuikit/application.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/application.py Fri Jan 18 22:36:50 2013 +0100
@@ -3,7 +3,7 @@
import logging
import time
-from tuikit.container import Container
+from tuikit.layout import AnchorLayout
class Timer:
@@ -51,66 +51,64 @@
timeout[0] = adjusted_delay
-class TopWindow(Container):
-
- '''Top window of an application. Covers entire screen.'''
-
- def __init__(self):
- '''Create top window.'''
- Container.__init__(self)
- self.top = self
- self.timer = Timer()
-
- def draw(self, driver, x=0, y=0):
- """Draw the top window and its children.
-
- This is entry point for full screen redraw.
-
- """
- driver.erase()
- Container.draw(self, driver, x, y)
-
-
class Application:
'''Application class. Defines main loop.'''
- def __init__(self, driver = 'sdl'):
+ def __init__(self, top_layout=AnchorLayout, driver='curses'):
'''Create application.'''
-
- self.top = TopWindow()
- '''Top window.'''
+ self._setup_logging()
+
+ # Top widget
+ self._top = None
+ self._timer = Timer()
+ self.top = top_layout()
self.quit = False
+ #: Driver class instance (render + input), e.g. DriverCurses.
self.driver = self.get_driver_instance(driver)
- '''Driver class instance (render + input), e.g. DriverCurses.'''
+ def _setup_logging(self):
self.log = logging.getLogger('tuikit')
self.log.setLevel(logging.DEBUG)
handler = logging.FileHandler('./tuikit.log')
formatter = logging.Formatter('%(asctime)s %(levelname)-5s %(message)s', '%y-%m-%d %H:%M:%S')
handler.setFormatter(formatter)
self.log.addHandler(handler)
- self.log.info('=== start ===')
+
+ @property
+ def top(self):
+ return self._top
+
+ @top.setter
+ def top(self, value):
+ self._top = value
+ self._top.top = value
+ self._top.timer = self._timer
+ self._top.add_handler('draw', self._on_top_draw)
+
+ def _on_top_draw(self, ev):
+ ev.driver.erase()
def start(self):
'''Start application. Runs main loop.'''
- self.driver.start(self.mainloop)
+ self.log.info('=== start ===')
+ self.driver.start(self.main_loop)
def terminate(self):
'''Terminate application.'''
self.quit = True
- def mainloop(self):
+ def main_loop(self):
'''The main loop.'''
- self.applytheme()
- self.top.size = self.driver.size # link top widget size to screen size
- self.top.emit('resize')
- timer = self.top.timer
+ self.apply_theme()
+ self._top._size = self.driver.size # link top widget size to screen size
+ self._top.emit('resize')
+ timer = self._timer
while True:
- self.top.draw(self.driver)
+ self._top.draw(self.driver, 0, 0)
self.driver.commit()
timeout = timer.nearest_timeout()
@@ -121,13 +119,14 @@
if event[0] == 'quit':
self.quit = True
else:
- self.top.emit(event[0], *event[1:])
+ self._top.emit(event[0], *event[1:])
if self.quit:
break
+ self.log.info('=== quit ===')
- def applytheme(self):
+ def apply_theme(self):
#TODO: allow custom colors:
# e.g. "blue (#2020FF) on black (#101010), underline"
# color in brackets is used when driver supports custom colors
--- a/tuikit/button.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/button.py Fri Jan 18 22:36:50 2013 +0100
@@ -10,18 +10,18 @@
def __init__(self, label=''):
'''Create button with given label, size according to label.'''
- w = len(label) + 4
- h = 1
- Widget.__init__(self, w, h)
+ Widget.__init__(self)
#: Button label.
- self.label = label
+ self._label = ''
#: Text or graphics to be added before label
self.prefix = '['
#: Text or graphics to be added after label
self.suffix = ']'
#: How should label be aligned if button has excess space - center | left | right
self.align = 'center'
+ #: Padding between prefix/suffix and label
+ self.padding = 1
self.allow_focus = True
@@ -29,12 +29,20 @@
self.bghi = 'button-active'
self.highlight = False
- # size
- self.sizereq.w = w
- self.sizereq.h = h
-
self.add_events('click', Event)
+ self.label = label
+
+ @property
+ def label(self):
+ """Button label."""
+ return self._label
+
+ @label.setter
+ def label(self, value):
+ self._label = value
+ w = len(value) + len(self.prefix) + len(self.suffix) + 2 * self.padding
+ self._default_size.update(w, 1)
def on_draw(self, ev):
pad = self.width - len(self.label) - len(self.prefix) - len(self.suffix)
--- a/tuikit/combobox.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/combobox.py Fri Jan 18 22:36:50 2013 +0100
@@ -1,38 +1,40 @@
# -*- coding: utf-8 -*-
-from tuikit.container import Container
+from tuikit.layout import HorizontalLayout
from tuikit.editfield import EditField
from tuikit.button import Button
from tuikit.menu import Menu
+import logging
-class ComboBox(Container):
- def __init__(self, width=15, value='', items=[]):
- Container.__init__(self, width, 1)
-
+class ComboBox(HorizontalLayout):
+ def __init__(self, value='', items=[]):
+ HorizontalLayout.__init__(self)
+ self._default_size.update(15, 1)
self.allow_focus = True
-
self.colorprefix = 'combo:'
- self._edit = EditField(width - 3, value)
- self.add(self._edit)
+ self._edit = EditField(value)
+ self._edit.resize(self.width - 3, 1)
+ self.add(self._edit, expand=True, fill=True)
self._btn = Button('v')
self._btn.prefix = ''
self._btn.suffix = ''
- self._btn.x = width - 3
- self._btn.width = 3
+ self._btn.resize(3, 1)
self._btn.add_handler('click', self._on_btn_click)
- self.add(self._btn)
+ self.add(self._btn, expand=False)
self._menu = Menu(items)
self._menu.hide()
- self._menu.allow_layout = False
-
- def _set_top(self, value):
- Container._set_top(self, value)
- self.top.add(self._menu)
+ self.add_floater(self._menu)
def _on_btn_click(self, ev):
self._menu.show()
+ def on_resize(self, ev):
+ HorizontalLayout.on_resize(self, ev)
+ logging.getLogger('tuikit').info('w = %s', self.width)
+ self._menu.move(self.x, self.y + 1)
+ self._menu.resize(w=self.width)
+
--- a/tuikit/common.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/common.py Fri Jan 18 22:36:50 2013 +0100
@@ -3,6 +3,37 @@
from tuikit.events import Event, Emitter
+class Select:
+ def __init__(self, selected=None):
+ if not hasattr(self, '_options'):
+ raise TypeError('Cannot instantiate Select class.')
+ self._selected = selected or self._options[0]
+
+ def update(self, selected):
+ if selected not in self._options:
+ raise ValueError('Select: %r not in options %r' % (selected, self._options))
+ self._selected = selected
+
+ def select_next(self):
+ i = self._options.index(self._selected)
+ try:
+ self._selected = self._options[i+1]
+ except IndexError:
+ self._selected = self._options[0]
+
+ @property
+ def selected(self):
+ return self._selected
+
+ def __repr__(self):
+ return self.__class__.__name__ + '(%r)' % self._selected
+
+
+def make_select(*args):
+ name = ''.join([x.capitalize() for x in args]) + 'Select'
+ return type(name, (Select,), {'_options': args})
+
+
class Coords:
'''2D coordinates.'''
@@ -28,6 +59,19 @@
def __repr__(self):
return 'Coords(x={0.x},y={0.y})'.format(self)
+ def update(self, x=None, y=None):
+ if isinstance(x, Coords) and y is None:
+ self.x, self.y = x
+ else:
+ if isinstance(x, int):
+ self.x = x
+ elif x is not None:
+ raise ValueError('Coords.update(): first parameter must be int or Coords')
+ if isinstance(y, int):
+ self.y = y
+ elif y is not None:
+ raise ValueError('Coords.update(): second parameter must be int')
+
class Size(Emitter):
@@ -78,6 +122,22 @@
def __repr__(self):
return 'Size(w={0._w},h={0._h})'.format(self)
+ def update(self, w=None, h=None):
+ old_w, old_h = self._w, self._h
+ if isinstance(w, Size) and h is None:
+ self._w, self._h = w
+ else:
+ if isinstance(w, int):
+ self._w = w
+ elif w is not None:
+ raise ValueError('Size.update(): first parameter must be int or Size')
+ if isinstance(h, int):
+ self._h = h
+ elif h is not None:
+ raise ValueError('Size.update(): second parameter must be int')
+ if self._w != old_w or self._h != old_h:
+ self.emit('change')
+
class Rect:
@@ -103,11 +163,13 @@
'''
- def __init__(self, l=0, t=0, r=0, b=0):
+ def __init__(self, l=0, t=0, r=0, b=0, full=None):
self.l = l # left
self.t = t # top
self.r = r # right
self.b = b # bottom
+ if full is not None:
+ self.l = self.t = self.r = self.b = full
def __getitem__(self, key):
try:
@@ -120,6 +182,16 @@
def __repr__(self):
return 'Borders(l={0.l},t={0.t},r={0.r},b={0.b})'.format(self)
+ def update(self, *args, **kwargs):
+ if len(args) == 4:
+ self.l, self.t, self.r, self.b = args
+ elif len(args) == 1 and isinstance(args[0], Borders):
+ self.l, self.t, self.r, self.b = args[0]
+ elif len(args):
+ raise ValueError('Borders.update() takes exactly 4 positional arguments.')
+ for arg in kwargs:
+ setattr(self, arg, kwargs[arg])
+
class ClipStack:
@@ -176,6 +248,11 @@
# http://en.wikipedia.org/wiki/List_of_Unicode_characters#Geometric_shapes
UP_ARROW = '▲' #curses.ACS_UARROW
DOWN_ARROW = '▼' #curses.ACS_DARROW
+ LEFT_ARROW = '◀'
+ RIGHT_ARROW = '▶'
+ CIRCLE = '●'
+ DIAMOND = '◆'
+ MIDDLE_DOT = '·'
# http://en.wikipedia.org/wiki/Box-drawing_characters
LIGHT_SHADE = '░' #curses.ACS_BOARD
--- a/tuikit/container.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/container.py Fri Jan 18 22:36:50 2013 +0100
@@ -2,18 +2,20 @@
from tuikit.widget import Widget
from tuikit.common import Borders, Coords
+import logging
class Container(Widget):
'''Container widget. Base for any widget which can contain other widgets.'''
- def __init__(self, width = 10, height = 10):
+ def __init__(self):
'''Create container of requested size.'''
- Widget.__init__(self, width, height)
+ Widget.__init__(self)
#: List of child widgets.
self.children = []
+ self._hint_class = {}
#: Offset for child widgets
self.offset = Coords()
@@ -36,16 +38,29 @@
self.trap_focus = False # if True, tab cycles inside container
-
def add(self, widget, **kwargs):
'''Add widget into this container.'''
self.children.append(widget)
widget.parent = self
widget.top = self.top
- widget.hints.update(kwargs)
+ widget.hints.update({k:v() for k,v in self._hint_class.items()})
+ for key in kwargs:
+ try:
+ widget.hints[key].update(kwargs[key])
+ except AttributeError:
+ widget.hints[key] = widget.hints[key].__class__(kwargs[key])
if self.focuschild is None and widget.can_focus():
self.focuschild = widget
+ def add_floater(self, widget, **kwargs):
+ widget.floater = True
+ widget._size.update(widget._sizereq)
+ self.add(widget, **kwargs)
+
+ def register_hints(self, *hints):
+ for hint_name, hint_class in zip(hints[::2], hints[1::2]):
+ self._hint_class[hint_name] = hint_class
+
@property
def layout(self):
return self._layout
@@ -57,6 +72,9 @@
self._layout.container = self
+ def move_child(self, child, x, y):
+ pass
+
def _set_top(self, value):
self._top = value
for child in self.children:
@@ -112,23 +130,37 @@
Widget.draw(self, driver, x, y)
- for child in [ch for ch in self.children if not ch.allow_layout]:
- child.draw(driver, x + child.x, y + child.y)
-
l, t, r, b = self.borders
driver.clipstack.push(x+l, y+t, self.width-l-r, self.height-t-b)
- for child in [ch for ch in self.children if ch.allow_layout]:
+ for child in [ch for ch in self.children if not ch.floater]:
child.draw(driver,
x + self.offset.x + child.x,
y + self.offset.y + child.y)
driver.clipstack.pop()
+ # draw floaters - no clipping, no colorprefix
+ if self.parent is None:
+ self.draw_floaters(driver, x, y)
+
if self.colorprefix:
driver.popcolorprefix()
driver.clipstack.pop()
+ def draw_floaters(self, driver, x, y):
+ logging.getLogger('tuikit').info('draw_floaters %s %r %r', self, x ,y )
+ # draw floaters in this container
+ for child in [ch for ch in self.children if ch.floater]:
+ child.draw(driver,
+ x + self.offset.x + child.x,
+ y + self.offset.y + child.y)
+ # delve into child containers, draw their floaters
+ for child in [ch for ch in self.children if not ch.floater and isinstance(ch, Container)]:
+ child.draw_floaters(driver,
+ x + self.offset.x + child.x,
+ y + self.offset.y + child.y)
+
def on_resize(self, ev):
for child in self.children:
child.emit('resize')
@@ -144,9 +176,9 @@
def on_mousedown(self, ev):
handled = False
for child in reversed(self.children):
- if child.enclose(ev.wx, ev.wy):
- childev = ev.childevent(child)
- child.emit('mousedown', childev)
+ if child.enclose(ev.wx - self.offset.x, ev.wy - self.offset.y):
+ child_ev = ev.make_child_event(self, child)
+ child.emit('mousedown', child_ev)
self.mousechild = child
handled = True
break
@@ -154,23 +186,23 @@
def on_mouseup(self, ev):
if self.mousechild:
- childev = ev.childevent(self.mousechild)
- self.mousechild.emit('mouseup', childev)
+ child_ev = ev.make_child_event(self, self.mousechild)
+ self.mousechild.emit('mouseup', child_ev)
self.mousechild = None
return True
def on_mousemove(self, ev):
if self.mousechild:
- childev = ev.childevent(self.mousechild)
- self.mousechild.emit('mousemove', childev)
+ child_ev = ev.make_child_event(self, self.mousechild)
+ self.mousechild.emit('mousemove', child_ev)
return True
def on_mousewheel(self, ev):
handled = False
for child in reversed(self.children):
- if child.enclose(ev.wx, ev.wy):
- childev = ev.childevent(child)
- child.emit('mousewheel', childev)
+ if child.enclose(ev.wx - self.offset.x, ev.wy - self.offset.y):
+ child_ev = ev.make_child_event(self, child)
+ child.emit('mousewheel', child_ev)
handled = True
break
return handled
--- a/tuikit/driver_sdl.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/driver_sdl.py Fri Jan 18 22:36:50 2013 +0100
@@ -131,7 +131,7 @@
if len(self.colorstack):
attr = self.colorstack[-1]
else:
- attr = 0
+ attr = 7
self.sdlterm.set_attr(attr)
def _color_by_name(self, name):
--- a/tuikit/editbox.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/editbox.py Fri Jan 18 22:36:50 2013 +0100
@@ -5,9 +5,9 @@
class EditBox(Widget):
- def __init__(self, width=20, height=20, text=''):
- Widget.__init__(self, width, height)
-
+ def __init__(self, text=''):
+ Widget.__init__(self)
+ self._default_size.update(20, 20)
self.allow_focus = True
self.xofs = 0
--- a/tuikit/editfield.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/editfield.py Fri Jan 18 22:36:50 2013 +0100
@@ -6,8 +6,9 @@
class EditField(Widget):
- def __init__(self, width=10, value=''):
- Widget.__init__(self, width, 1)
+ def __init__(self, value=''):
+ Widget.__init__(self)
+ self._default_size.update(10, 1)
self.allow_focus = True
--- a/tuikit/events.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/events.py Fri Jan 18 22:36:50 2013 +0100
@@ -20,6 +20,9 @@
def __getitem__(self, key):
return self.__dict__[key]
+ def __repr__(self):
+ return 'Event()'
+
class DrawEvent(Event):
def __init__(self, driver, x, y):
@@ -66,7 +69,7 @@
#: Mouse button: left=1, middle=2, right=3, wheelup=4 wheeldown=5
self.button = button
- def childevent(self, child):
+ def make_child_event(self, container, child):
ev = MouseEvent(self.x, self.y, self.button)
# original local coordinates are new parent coordinates
ev.px = self.wx
@@ -97,7 +100,7 @@
It also serves as initializer, other methods of Emitter
will not work if add_events was not called.
- *events -- Arguments must be given in pairs.
+ events -- Arguments must be given in pairs.
Each pair consists of event_name, event_class:
event_name -- a string used in add_handler(), emit() etc.
@@ -185,10 +188,6 @@
is passed to handlers.
"""
- logging.getLogger('tuikit').debug('Emit "%s" on %s %s',
- event_name,
- self.__class__.__name__,
- getattr(self, 'name', None) or id(self))
# create event from specified event class, or use first argument
if len(args) == 1 and isinstance(args[0], Event):
event = args[0]
@@ -197,6 +196,11 @@
# set originator to instance on which emit() was called
event.originator = self
event.event_name = event_name
+ # log
+ logging.getLogger('tuikit').debug('Emit %r %r on %s %s',
+ event_name, event,
+ self.__class__.__name__,
+ getattr(self, 'name', None) or id(self))
# call handlers from first to last, stop if satisfied
for handler in self._event_handlers[event_name]:
handled = handler(event)
--- a/tuikit/label.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/label.py Fri Jan 18 22:36:50 2013 +0100
@@ -5,8 +5,8 @@
class Label(Widget):
def __init__(self, label=''):
- Widget.__init__(self, len(label), 1)
-
+ Widget.__init__(self)
+ self._default_size.update(len(label), 1)
self.label = label
self.color = 'normal'
--- a/tuikit/layout.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/layout.py Fri Jan 18 22:36:50 2013 +0100
@@ -8,50 +8,119 @@
'''
import math
+import logging
-from tuikit.common import Rect
+from tuikit.common import Coords, Size, Rect, Borders, make_select
+from tuikit.container import Container
-class Layout:
- def __init__(self):
- self._container = None
-
- @property
- def container(self):
- return self._container
+class Layout(Container):
+ def _get_children(self):
+ return [child for child in self.children
+ if not child.floater and not child.hidden]
- @container.setter
- def container(self, value):
- self._container = value
- self._container.add_handler('resize', self._on_container_resize)
-
- def _on_container_resize(self, ev):
- self.resize()
-
- def _getchildren(self):
- return [child for child in self.container.children
- if child.allow_layout and not child.hidden]
-
- def _getregion(self):
+ def _get_region(self):
c = self.container
bl, bt, br, bb = c.borders
return Rect(bl, bt, c.width - bl - br, c.height - bt - bb)
+class AnchorLayout(Layout):
+
+ """Attach widgets to borders of container.
+
+ Also allows absolute positioning when possible (not for center, fill).
+
+ """
+
+ def __init__(self):
+ Layout.__init__(self)
+ self.register_hints(
+ 'halign', make_select('left', 'right', 'fill', 'center'),
+ 'valign', make_select('top', 'bottom', 'fill', 'center'),
+ 'margin', Borders,
+ )
+
+ def on_resize(self, ev):
+ for child in self._get_children():
+ reqw = max(child.sizereq.w, child.sizemin.w)
+ reqh = max(child.sizereq.h, child.sizemin.h)
+ ha = child.hints['halign'].selected
+ va = child.hints['valign'].selected
+ margin = child.hints['margin']
+ if ha == 'left':
+ x = margin.l
+ w = reqw
+ if ha == 'right':
+ x = self.width - margin.r - reqw
+ w = reqw
+ if ha == 'fill':
+ x = margin.l
+ w = self.width - margin.l - margin.r
+ if ha == 'center':
+ x = (self.width - reqw) // 2
+ w = reqw
+ if va == 'top':
+ y = margin.t
+ h = reqh
+ if va == 'bottom':
+ y = self.height - margin.b - reqh
+ h = reqh
+ if va == 'fill':
+ y = margin.t
+ h = self.height - margin.t - margin.b
+ if va == 'center':
+ y = (self.height - reqh) // 2
+ h = reqh
+ child._pos.update(x=x, y=y)
+ child._size.update(w=w, h=h)
+
+ def move_child(self, child, x, y):
+ if not child in self.children:
+ raise ValueError('AnchorLayout.move(): Cannot move foreign child.')
+ margin = child.hints['margin']
+ ha = child.hints['halign'].selected
+ va = child.hints['valign'].selected
+ ofsx = x - child.x
+ ofsy = y - child.y
+ if ha == 'left':
+ margin.l += ofsx
+ newx = margin.l
+ elif ha == 'right':
+ margin.r -= ofsx
+ newx = self.width - margin.r - child.sizereq.w
+ else:
+ # do not move when halign is center,fill
+ newx = child.x
+ if va == 'top':
+ margin.t += ofsy
+ newy = margin.t
+ elif va == 'bottom':
+ margin.b -= ofsy
+ newy = self.height - margin.b - child.sizereq.h
+ else:
+ # do not move when valign is center,fill
+ newy = child.y
+ child._pos.update(x=newx, y=newy)
+
+
class LinearLayout(Layout):
def __init__(self, homogeneous=False, spacing=0):
Layout.__init__(self)
self.homogeneous = homogeneous
self.spacing = spacing
+ self.register_hints(
+ 'expand', bool,
+ 'fill', bool,
+ )
def _resize(self, ax1, ax2):
- children = self._getchildren()
- c = self.container
- b1 = c.borders[ax1]
- b2 = c.borders[ax2]
+ children = self._get_children()
+ b1 = self.borders[ax1]
+ b2 = self.borders[ax2]
# available space
- space1 = c.size[ax1] - b1 - c.borders[ax1+2]
- space2 = c.size[ax2] - b2 - c.borders[ax2+2]
+ space1 = self._size[ax1] - b1 - self.borders[ax1+2]
+ space2 = self._size[ax2] - b2 - self.borders[ax2+2]
# all space minus spacing
space_to_divide = space1 - (len(children) - 1) * self.spacing
@@ -59,7 +128,7 @@
# reduce by space acquired by children
space_to_divide -= sum([child.sizereq[ax1] for child in children])
# number of children with expanded hint
- expanded_num = len([ch for ch in children if ch.hint('expand')])
+ expanded_num = len([ch for ch in children if ch.hints['expand']])
else:
# all children are implicitly expanded
expanded_num = len(children)
@@ -70,40 +139,42 @@
offset = 0.
for child in children:
- child.position[ax1] = b1 + int(offset)
- child.position[ax2] = b2
- child.size[ax1] = child.sizereq[ax1]
- child.size[ax2] = space2
+ pos = Coords()
+ pos[ax1] = b1 + int(offset)
+ pos[ax2] = b2
+ size = Size()
+ size[ax1] = max(child.sizereq[ax1], child.sizemin[ax1])
+ size[ax2] = space2
- if child.hint('expand') or self.homogeneous:
+ if child.hints['expand'] or self.homogeneous:
maxsize = int(round(space_child + math.modf(offset)[0], 2))
offset += space_child + self.spacing
if not self.homogeneous:
maxsize += child.sizereq[ax1]
offset += child.sizereq[ax1]
- if child.hint('fill'):
- child.size[ax1] = maxsize
+ if child.hints['fill']:
+ size[ax1] = maxsize
else:
- child.position[ax1] += int((maxsize - child.size[ax1])/2)
+ pos[ax1] += int((maxsize - size[ax1])/2)
else:
- offset += child.size[ax1]
+ offset += size[ax1]
- child.emit('resize')
- c.redraw()
+ child._pos.update(pos)
+ child._size.update(size)
class VerticalLayout(LinearLayout):
- def resize(self):
- ax1 = 1 # primary dimension - y
- ax2 = 0 # secondary dimension - x
+ def on_resize(self, ev):
+ ax1 = 1 # primary dimension = y
+ ax2 = 0 # secondary dimension = x
self._resize(ax1, ax2)
class HorizontalLayout(LinearLayout):
- def resize(self):
- ax1 = 0 # primary dimension - x
- ax2 = 1 # secondary dimension - y
+ def on_resize(self, ev):
+ ax1 = 0 # primary dimension = x
+ ax2 = 1 # secondary dimension = y
self._resize(ax1, ax2)
--- a/tuikit/menu.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/menu.py Fri Jan 18 22:36:50 2013 +0100
@@ -2,13 +2,15 @@
from tuikit.widget import Widget
from tuikit.events import GenericEvent
+import logging
class Menu(Widget):
def __init__(self, items=[]):
Widget.__init__(self)
- self.width = max([len(x[0]) for x in items if x is not None]) + 4
- self.height = len(items) + 2
+ w = max([len(x[0]) for x in items if x is not None]) + 4
+ h = len(items) + 2
+ self._default_size.update(w, h)
self.allow_focus = True
@@ -21,6 +23,7 @@
self.add_events('activate', GenericEvent)
def on_draw(self, ev):
+ logging.getLogger('tuikit').info('menu draw %s %s %s', ev, ev.x, ev.y)
ev.driver.pushcolor(self.bg)
ev.driver.frame(ev.x, ev.y, self.width, self.height)
i = 1
--- a/tuikit/scrollbar.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/scrollbar.py Fri Jan 18 22:36:50 2013 +0100
@@ -2,67 +2,133 @@
from tuikit.widget import Widget
from tuikit.events import Event
+from tuikit.common import UnicodeGraphics
-class VScrollbar(Widget):
- def __init__(self, height=10):
- Widget.__init__(self, 1, height)
+class Scrollbar(Widget):
- self._max = height - 3
- self._pos = 0
- self._thumbpos = 0
+ """Abstract base class for scrollbars."""
+
+ def __init__(self):
+ Widget.__init__(self)
- # interval for continuous scroll when user holds mouse on scrollbar arrow
- self.scroll_delay = 0.150
- self.scroll_interval = 0.030
+ # Scrolling range is 0 .. _scroll_max
+ self._scroll_max = self._get_length() - 3
+ # Current position of scrollbar in scrolling range.
+ self._scroll_pos = 0
+ # Current position of thumb on scrollbar - used for draw.
+ self._thumb_pos = 0
+ # Auxilliary variable, True when user holds mouse on thumb.
+ self._dragging = False
+ # Auxilliary variable, 'up' or 'down' depending on last move direction.
+ self._move = None
- self.dragging = False
- self.move = None
+ #: delay before continuous scrolling when user holds mouse on scrollbar arrow
+ self.scroll_delay = 0.150
+ #: interval for continuous scrolling
+ self.scroll_interval = 0.030
+ #: scrollbar styling
+ unigraph = UnicodeGraphics()
+ self.track_char = unigraph.MIDDLE_DOT
+ self.thumb_char = unigraph.CIRCLE
+ # change event is emitted when user moves scrollbar (even programmatically)
self.add_events('change', Event)
@property
- def max(self):
+ def scroll_max(self):
"""Maximum for scrolling position."""
- return self._max
+ return self._scroll_max
- @max.setter
- def max(self, value):
- self._max = value
- self._update_thumbpos()
+ @scroll_max.setter
+ def scroll_max(self, value):
+ self._scroll_max = value
+ self._update_thumb_pos()
@property
- def pos(self):
+ def scroll_pos(self):
"""Scrolling position.
Integer number between 0 and 'max'.
"""
- return self._pos
+ return self._scroll_pos
- @pos.setter
- def pos(self, value):
- if self._pos != value:
- self._pos = value
- self._update_thumbpos()
+ @scroll_pos.setter
+ def scroll_pos(self, value):
+ if self._scroll_pos != value:
+ self._scroll_pos = value
+ self._update_thumb_pos()
self.emit('change')
- def _update_thumbpos(self):
- self._thumbpos = 0
- if self._max and self._pos <= self._max:
- self._thumbpos = int(round(self._pos / self._max * (self.height - 3)))
+ def move_up(self):
+ """Move scrolling position up/left."""
+ if self._scroll_pos > 0:
+ self.scroll_pos = self._scroll_pos - 1
+ self._move = 'up'
+
+ def move_down(self):
+ """Move scrolling position down/right."""
+ if self._scroll_pos < self._scroll_max:
+ self.scroll_pos = self._scroll_pos + 1
+ self._move = 'down'
+
+ def drag(self, mouse_pos):
+ """Scroll using mouse drag on thumb.
+
+ Args:
+ mouse_pos: new position of mouse, in range 0 .. self._get_length()
+
+ """
+ new_pos = int(round((mouse_pos - 1) / (self._get_length() - 3) * self._scroll_max))
+ if new_pos < 0:
+ new_pos = 0
+ if new_pos > self._scroll_max:
+ new_pos = self._scroll_max
+ if self._scroll_pos != new_pos:
+ self.scroll_pos = new_pos
+
+ def _get_length(self):
+ """Return length of scrollbar.
+
+ This will be widget height for vertical scrollbar,
+ width for horizontal scrollbar.
+
+ """
+ raise NotImplementedError()
+
+ def _update_thumb_pos(self):
+ """Update value of internal variable _thumb_pos."""
+ self._thumb_pos = 0
+ if self._scroll_max and self._scroll_pos <= self._scroll_max:
+ self._thumb_pos = int(round(self._scroll_pos / self._scroll_max * (self._get_length() - 3)))
self.redraw()
+ def _continuous_scroll(self):
+ if self._move == 'up':
+ self.move_up()
+ if self._move == 'down':
+ self.move_down()
+ self.add_timeout(self.scroll_interval, self._continuous_scroll)
+
+
+class VScrollbar(Scrollbar):
+ def __init__(self):
+ Scrollbar.__init__(self)
+ self._default_size.update(1, 20)
+
def on_draw(self, ev):
+ ev.driver.pushcolor('normal')
ev.driver.putch(ev.x, ev.y, ev.driver.unigraph.UP_ARROW)
- for i in range(ev.y + 1, ev.y + self.height - 1):
- ev.driver.putch(ev.x, i, ev.driver.unigraph.LIGHT_SHADE)
- ev.driver.putch(ev.x, ev.y + 1 + self._thumbpos, ev.driver.unigraph.BLOCK)
+ for i in range(1, self.height - 1):
+ ev.driver.putch(ev.x, ev.y + i, self.track_char)
+ ev.driver.putch(ev.x, ev.y + 1 + self._thumb_pos, self.thumb_char)
ev.driver.putch(ev.x, ev.y + self.height - 1, ev.driver.unigraph.DOWN_ARROW)
+ ev.driver.popcolor()
def on_mousedown(self, ev):
- self.dragging = False
- self.move = None
+ self._dragging = False
+ self._move = None
# arrow buttons
if ev.wy == 0 or ev.wy == self.height - 1:
if ev.wy == 0:
@@ -72,47 +138,72 @@
self.add_timeout(self.scroll_delay, self._continuous_scroll)
return
# thumb bar
- if ev.wy == 1 + self._thumbpos:
- self.dragging = True
- return
-
- def on_mouseup(self, ev):
- if self.dragging:
- self.drag(ev.wy)
- self.dragging = False
- return
- if self.move:
- self.remove_timeout(self._continuous_scroll)
- self.move = None
+ if ev.wy == 1 + self._thumb_pos:
+ self._dragging = True
return
def on_mousemove(self, ev):
- if self.dragging:
+ if self._dragging:
self.drag(ev.wy)
- def move_up(self):
- if self._pos > 0:
- self.pos = self._pos - 1
- self.move = 'up'
+ def on_mouseup(self, ev):
+ if self._dragging:
+ self.drag(ev.wy)
+ self._dragging = False
+ return
+ if self._move:
+ self.remove_timeout(self._continuous_scroll)
+ self._move = None
+ return
+
+ def _get_length(self):
+ return self.height
+
- def move_down(self):
- if self._pos < self._max:
- self.pos = self._pos + 1
- self.move = 'down'
+class HScrollbar(Scrollbar):
+ def __init__(self):
+ Scrollbar.__init__(self)
+ self._default_size.update(20, 1)
+
+ def on_draw(self, ev):
+ ev.driver.pushcolor('normal')
+ ev.driver.putch(ev.x, ev.y, ev.driver.unigraph.LEFT_ARROW)
+ for i in range(1, self.width - 1):
+ ev.driver.putch(ev.x + i, ev.y, self.track_char)
+ ev.driver.putch(ev.x + 1 + self._thumb_pos, ev.y, self.thumb_char)
+ ev.driver.putch(ev.x + self.width - 1, ev.y, ev.driver.unigraph.RIGHT_ARROW)
+ ev.driver.popcolor()
- def drag(self, wy):
- newpos = int(round((wy - 1) / (self.height - 3) * self._max))
- if newpos < 0:
- newpos = 0
- if newpos > self._max:
- newpos = self._max
- if self._pos != newpos:
- self.pos = newpos
+ def on_mousedown(self, ev):
+ self._dragging = False
+ self._move = None
+ # arrow buttons
+ if ev.wx == 0 or ev.wx == self.width - 1:
+ if ev.wx == 0:
+ self.move_up()
+ else:
+ self.move_down()
+ self.add_timeout(self.scroll_delay, self._continuous_scroll)
+ return
+ # thumb bar
+ if ev.wx == 1 + self._thumb_pos:
+ self._dragging = True
+ return
- def _continuous_scroll(self):
- if self.move == 'up':
- self.move_up()
- if self.move == 'down':
- self.move_down()
- self.add_timeout(self.scroll_interval, self._continuous_scroll)
+ def on_mousemove(self, ev):
+ if self._dragging:
+ self.drag(ev.wx)
+ def on_mouseup(self, ev):
+ if self._dragging:
+ self.drag(ev.wx)
+ self._dragging = False
+ return
+ if self._move:
+ self.remove_timeout(self._continuous_scroll)
+ self._move = None
+ return
+
+ def _get_length(self):
+ return self.width
+
--- a/tuikit/scrollview.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/scrollview.py Fri Jan 18 22:36:50 2013 +0100
@@ -12,12 +12,13 @@
"""
- def __init__(self, width=20, height=20):
- Container.__init__(self, width, height)
+ def __init__(self):
+ Container.__init__(self)
+ self._default_size.update(20, 20)
- self.vscroll = VScrollbar(height)
+ self.vscroll = VScrollbar()
self.vscroll.add_handler('change', self._on_vscroll_change)
- self.vscroll.allow_layout = False
+ self.vscroll.evade_layout = 2
self.add(self.vscroll)
def add(self, widget, **kwargs):
@@ -27,8 +28,8 @@
widget.add_handler('spotmove', self._on_child_spotmove)
def on_resize(self, ev):
- self.vscroll.x = self.size.w - 1
- self.vscroll.height = self.height
+ self.vscroll.move(x = self.size.w - 1)
+ self.vscroll.resize(h = self.height)
self._update_vscroll_max()
def _on_vscroll_change(self, ev):
--- a/tuikit/textedit.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/textedit.py Fri Jan 18 22:36:50 2013 +0100
@@ -1,26 +1,28 @@
# -*- coding: utf-8 -*-
-from tuikit.container import Container
+from tuikit.layout import AnchorLayout
from tuikit.editbox import EditBox
-from tuikit.scrollbar import VScrollbar
+from tuikit.scrollbar import VScrollbar, HScrollbar
+from tuikit.common import Borders
-class TextEdit(Container):
- def __init__(self, width=20, height=20, text=''):
- Container.__init__(self, width, height)
+class TextEdit(AnchorLayout):
+ def __init__(self, text=''):
+ AnchorLayout.__init__(self)
+ self._default_size.update(20, 20)
- self.editbox = EditBox(width-2, height-2, text)
- self.add(self.editbox)
- self.editbox.x = 1
- self.editbox.y = 1
+ self.editbox = EditBox(text)
self.editbox.add_handler('scroll', self.on_editbox_scroll)
self.editbox.add_handler('areasize', self.on_editbox_areasize)
+ self.add(self.editbox, halign='fill', valign='fill', margin=Borders(full=1))
- self.vscroll = VScrollbar(height - 2)
- self.add(self.vscroll)
- self.vscroll.x = width - 1
- self.vscroll.y = 1
+ self.vscroll = VScrollbar()
self.vscroll.add_handler('change', self.on_vscroll_change)
+ self.add(self.vscroll, halign='right', valign='fill', margin=Borders(t=1, b=1))
+
+ self.hscroll = HScrollbar()
+ self.hscroll.add_handler('change', self.on_hscroll_change)
+ self.add(self.hscroll, halign='fill', valign='bottom', margin=Borders(l=1, r=1))
self.on_editbox_areasize(None)
@@ -34,15 +36,18 @@
ev.driver.frame(ev.x, ev.y, self.width, self.height)
def on_editbox_scroll(self, ev):
- self.vscroll.pos = self.editbox.yofs
+ self.vscroll.scroll_pos = self.editbox.yofs
def on_editbox_areasize(self, ev):
smax = len(self.editbox.lines) - self.editbox.height
if smax < 0:
smax = 0
- self.vscroll.max = smax
+ self.vscroll.scroll_max = smax
def on_vscroll_change(self, ev):
- self.editbox.set_yofs(self.vscroll.pos)
+ self.editbox.set_yofs(self.vscroll.scroll_pos)
self.editbox.redraw()
+ def on_hscroll_change(self, ev):
+ self.editbox.xofs = self.hscroll.scroll_pos
+ self.editbox.redraw()
--- a/tuikit/treeview.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/treeview.py Fri Jan 18 22:36:50 2013 +0100
@@ -175,8 +175,10 @@
class TreeView(Widget):
"""Tree view displays data from tree model."""
- def __init__(self, model=None, width=20, height=20):
- Widget.__init__(self, width, height)
+ def __init__(self, model=None):
+ Widget.__init__(self)
+ self._default_size.update(20, 20)
+
self.allow_focus = True
# model
--- a/tuikit/widget.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/widget.py Fri Jan 18 22:36:50 2013 +0100
@@ -3,12 +3,14 @@
from tuikit.events import Emitter, Event, DrawEvent, FocusEvent, KeyboardEvent, MouseEvent
from tuikit.common import Coords, Size
+import logging
+
class Widget(Emitter):
'''Base class for all widgets.'''
- def __init__(self, width = 10, height = 10):
+ def __init__(self):
#: Widget name is used for logging etc. Not visible anywhere.
self.name = None
@@ -18,28 +20,30 @@
#: Top widget (same for every widget in one application).
self._top = None
- # Position inside parent widget. Modified by layout manager.
- self.position = Coords()
+ #: Floating widget
+ self.floater = False
- # Actual size. Modified by layout manager.
- self.size = Size(width, height)
-
+ ### placing and size
+ # Position inside parent widget. Modified by layout manager.
+ self._pos = Coords()
+ # Actual size. Modified only by layout manager.
+ self._size = Size(10, 10)
+ self._size.add_handler('change', lambda ev: self.emit('resize'))
+ #: Default (natural) size of Widget.
+ self._default_size = Size(1, 1)
+ self._default_size.add_handler('change', lambda ev: self._sizereq.update(self._default_size))
+ #: Size request. Equals to default size, unless changed by user.
+ #: Size request will be fulfilled by layout manager when possible.
+ self._sizereq = Size(1, 1)
+ self._sizereq.add_handler('change', lambda ev: self.emit('sizereq'))
#: Minimal size of widget. Under normal circumstances
#: widget will never be sized smaller than this.
#: Tuple (w, h). Both must be integers >= 1.
self.sizemin = Size(1, 1)
-
#: 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).
self.sizemax = Size(None, None)
- #: Size request. This is default size of the widget. Will be fulfilled if possible.
- #: Size(w, h). Integers >= 1 or None (meaning use minumal size).
- self._sizereq = Size(10, 10)
- self._sizereq.add_handler('change', lambda ev: self.emit('sizereq'))
-
- #: When false, the widget is not considered in layout.
- self.allow_layout = True
#: Allow keyboard focus for this widget.
self.allow_focus = False
@@ -75,41 +79,39 @@
'focus', FocusEvent,
'unfocus', FocusEvent)
+ ### placing and size
@property
def x(self):
- return self.position.x
-
- @x.setter
- def x(self, value):
- self.position.x = value
+ return self._pos.x
@property
def y(self):
- return self.position.y
+ return self._pos.y
- @y.setter
- def y(self, value):
- self.position.y = value
-
+ def move(self, x, y):
+ if self.floater:
+ self._pos.update(x, y)
+ if self.parent:
+ self.parent.move_child(self, x, y)
@property
def width(self):
- return self.size.w
-
- @width.setter
- def width(self, value):
- self.size.w = value
- self.emit('resize')
+ return self._size.w
@property
def height(self):
- return self.size.h
+ return self._size.h
+
+ def resize(self, w=None, h=None):
+ """Set size request.
- @height.setter
- def height(self, value):
- self.size.h = value
- self.emit('resize')
+ It's up to parent container if request will be fulfilled.
+
+ """
+ self._sizereq.update(w, h)
+ #if self.parent:
+ # self.parent.emit('resize')
@property
def sizereq(self):
@@ -121,6 +123,15 @@
"""
return self._sizereq
+ def on_sizereq(self, ev):
+ # floater is not managet by layout,
+ # always set its size to sizereq
+ if self.floater:
+ logging.getLogger('tuikit').info('xy')
+ self._size.update(self._sizereq)
+
+ ### misc
+
@property
def spot(self):
"""Spot of visibility.
@@ -220,10 +231,6 @@
###
- def hint(self, key):
- if key in self.hints:
- return self.hints[key]
-
def enclose(self, x, y):
if self.hidden:
return False
--- a/tuikit/window.py Thu Jan 10 00:03:34 2013 +0100
+++ b/tuikit/window.py Fri Jan 18 22:36:50 2013 +0100
@@ -2,45 +2,48 @@
from tuikit.container import Container
from tuikit.button import Button
-
+from tuikit.layout import AnchorLayout
+from tuikit.common import Borders
-class Window(Container):
+import logging
+
+class Window(AnchorLayout):
'''Window widget.
It represents part of screen with border, close button and contents.
Window can be moved, resized or closed by user.'''
- def __init__(self, width=40, height=10):
+ def __init__(self, inner_layout=AnchorLayout):
'''Create window of requested size.'''
- Container.__init__(self, width, height)
+ AnchorLayout.__init__(self)
+ self.sizemin.update(8, 3)
#: Window title.
self.title = ''
- self._closebutton = True
-
#: Allow user to resize window.
self.resizable = True
-
+ self._resizing = False
#: Allow user to move window.
self.movable = True
-
- self.resizing = False
- self.moving = False
+ self._moving = False
self.closebtn = Button('x')
- self.closebtn.allow_layout = False
- self.closebtn.x = self.width - 5
- self.closebtn.width = 3
+ self.closebtn.sizereq.w = 3
self.closebtn.add_handler('click', self.on_closebtn_click)
self.closebtn.bg = 'controls'
self.closebtn.bghi = 'controls-active'
- self.add(self.closebtn)
+ Container.add(self, self.closebtn, halign='right', margin=Borders(r=1))
+ self._closebutton = True
- self.borders = (1, 1, 1, 1)
self.colorprefix = 'window:'
+ self._inner = inner_layout()
+ Container.add(self, self._inner, halign='fill', valign='fill', margin=Borders(1,1,1,1))
+
+ def add(self, widget, **kwargs):
+ self._inner.add(widget, **kwargs)
@property
def closebutton(self):
@@ -58,7 +61,7 @@
ev.driver.frame(ev.x, ev.y, self.width, self.height)
if self.resizable:
- if self.resizing:
+ if self._resizing:
ev.driver.pushcolor('controls-active')
else:
ev.driver.pushcolor('controls')
@@ -66,7 +69,17 @@
ev.driver.popcolor()
if self.title:
- ev.driver.puts(ev.x + (self.width - len(self.title))//2, ev.y, self.title)
+ title = ' ' + self.title + ' '
+ left = max(1, (self.width - len(title))//2)
+ right = self.width - len(title) - left
+ maxlen = self.width - 5
+ if left > 1 and right < 4:
+ # move space from left to right to make space for close button
+ left -= 1
+ if len(title) > maxlen:
+ title = title[:maxlen]
+ title = title[:-1] + ' '
+ ev.driver.puts(ev.x + left, ev.y, title)
ev.driver.fill(ev.x+1, ev.y+1, self.width-2, self.height-2)
ev.driver.popcolor()
@@ -75,24 +88,23 @@
def after_mousedown(self, ev):
self.dragstart = (ev.wx, ev.wy)
if self.resizable and ev.wx >= self.width - 1 and ev.wy >= self.height - 1:
- self.resizing = True
+ self._resizing = True
elif self.movable:
- self.moving = True
+ self._moving = True
self.origsize = (self.width, self.height)
self.redraw(True)
def after_mouseup(self, ev):
- if self.resizing:
- self.width = self.origsize[0] + ev.wx - self.dragstart[0]
- self.height = self.origsize[1] + ev.wy - self.dragstart[1]
- self.resizing = False
- self.emit('resize')
- elif self.moving:
- self.x = ev.px - self.dragstart[0]
- self.y = ev.py - self.dragstart[1]
- self.moving = False
+ if self._resizing:
+ self.resize(self.origsize[0] + ev.wx - self.dragstart[0],
+ self.origsize[1] + ev.wy - self.dragstart[1])
+ self._resizing = False
+ elif self._moving:
+ self.move(ev.px - self.dragstart[0],
+ ev.py - self.dragstart[1])
+ self._moving = False
self.redraw(True)
@@ -105,21 +117,16 @@
#if x > self.parent.width-self.width:
# return
- if self.resizing:
- self.width = self.origsize[0] + ev.wx - self.dragstart[0]
- self.height = self.origsize[1] + ev.wy - self.dragstart[1]
- self.emit('resize')
- elif self.moving:
- self.x = ev.px - self.dragstart[0]
- self.y = ev.py - self.dragstart[1]
+ if self._resizing:
+ self.resize(self.origsize[0] + ev.wx - self.dragstart[0],
+ self.origsize[1] + ev.wy - self.dragstart[1])
+ elif self._moving:
+ self.move(ev.px - self.dragstart[0],
+ ev.py - self.dragstart[1])
self.redraw(True)
- def on_resize(self, ev):
- self.closebtn.x = self.width - 5
-
-
def on_closebtn_click(self, ev):
self.hide()