# HG changeset patch # User Radek Brich # Date 1424117863 -3600 # Node ID 8680c23335467dd7d24548e3c340785a55e7b2a3 # Parent 165b5d65e1cbbd6f433fca46f79d64223049bd76 Update FixedLayout. Add demo launcher. diff -r 165b5d65e1cb -r 8680c2333546 .hgignore --- a/.hgignore Sun Feb 15 17:50:24 2015 +0100 +++ b/.hgignore Mon Feb 16 21:17:43 2015 +0100 @@ -2,13 +2,12 @@ ^tuikit/.*\.pyc$ ^tuikit\.conf$ ^docs/_build -tuikit\.log +^tuikit\.log$ ^build ^sdlterm/build ^sdlterm/font ^sdlterm/cython/sdlterm.cpp$ ^sdlterm/test_ -^sdlterm\..*\.so$ ^(.*/)?\.c?project$ ^(.*/)?\.pydevproject$ ^\.settings diff -r 165b5d65e1cb -r 8680c2333546 INSPIRATION --- a/INSPIRATION Sun Feb 15 17:50:24 2015 +0100 +++ b/INSPIRATION Mon Feb 16 21:17:43 2015 +0100 @@ -22,8 +22,8 @@ http://developer.gnome.org/gtk3/stable/ http://python-gtk-3-tutorial.readthedocs.org/en/latest/layout.html -Anchor Layout in QML: - http://harmattan-dev.nokia.com/docs/library/html/qt4/qml-anchor-layout.html +Qt Layout: + http://qt-project.org/doc/qt-4.8/layout.html PDCurses: http://pdcurses.sourceforge.net/doc/PDCurses.txt diff -r 165b5d65e1cb -r 8680c2333546 Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Mon Feb 16 21:17:43 2015 +0100 @@ -0,0 +1,4 @@ +.PHONY: build_ext + +build_ext: + python3 setup.py build_ext --inplace diff -r 165b5d65e1cb -r 8680c2333546 demo.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demo.py Mon Feb 16 21:17:43 2015 +0100 @@ -0,0 +1,39 @@ +#!/usr/bin/env python3 + +from demos.base import setup +import sys +import os +import re + + +def list_demos(): + return sorted(fname for fname in os.listdir('demos') + if re.match(r'[0-9]+_.*\.py', fname)) + + +def print_usage(names): + print("Usage: %s \n" % sys.argv[0]) + print("Where is one of (or part of):") + print('\n'.join([' '+name for name in names])) + + +if __name__ == '__main__': + setup() + names = list_demos() + + if len(sys.argv) != 2: + print_usage(names) + sys.exit(0) + + arg = sys.argv[1] + name = None + for name in names: + if arg in name: + break + else: + print("Demo '%s' not found." % arg) + sys.exit(1) + + print("Executing %s..." % name) + with open('demos/' + name, 'r') as f: + exec(f.read()) diff -r 165b5d65e1cb -r 8680c2333546 demos/01_buffer.py --- a/demos/01_buffer.py Sun Feb 15 17:50:24 2015 +0100 +++ b/demos/01_buffer.py Mon Feb 16 21:17:43 2015 +0100 @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import demobase - from tuikit.core.buffer import Buffer from tuikit.driver.driver import Driver diff -r 165b5d65e1cb -r 8680c2333546 demos/02_curses.py --- a/demos/02_curses.py Sun Feb 15 17:50:24 2015 +0100 +++ b/demos/02_curses.py Mon Feb 16 21:17:43 2015 +0100 @@ -1,7 +1,5 @@ #!/usr/bin/env python3 -import demobase - from tuikit.core.buffer import Buffer from tuikit.driver.curses import CursesDriver diff -r 165b5d65e1cb -r 8680c2333546 demos/03_application.py --- a/demos/03_application.py Sun Feb 15 17:50:24 2015 +0100 +++ b/demos/03_application.py Mon Feb 16 21:17:43 2015 +0100 @@ -1,28 +1,20 @@ #!/usr/bin/env python3 -import demobase - from tuikit.core.application import Application from tuikit.widgets.label import Label from tuikit.widgets.button import Button from tuikit.widgets.textfield import TextField label = Label('Hello there!') -label.posreq.update(20, 10) - button1 = Button() -button1.posreq.update(20, 20) button2 = Button() -button2.posreq.update(30, 20) - field = TextField('text field') -field.posreq.update(20, 30) app = Application() -app.root_window.add(label) -app.root_window.add(button1) -app.root_window.add(button2) -app.root_window.add(field) +app.root_window.add(label, 20, 10) +app.root_window.add(button1, 20, 20) +app.root_window.add(button2, 30, 20) +app.root_window.add(field, 20, 30) app.root_window.focus_widget = field def on_keypress(ev): diff -r 165b5d65e1cb -r 8680c2333546 demos/04_texteditor.py --- a/demos/04_texteditor.py Sun Feb 15 17:50:24 2015 +0100 +++ b/demos/04_texteditor.py Mon Feb 16 21:17:43 2015 +0100 @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import demobase +import sys from tuikit.core.application import Application #from tuikit.scrollview import ScrollView @@ -14,7 +14,7 @@ self.window_manager.sig_keypress.connect(self.on_wm_keypress) #self.top.add_handler('keypress', self.on_top_keypress) - t = open('../tuikit/core/widget.py').read() + t = open(sys.argv[0]).read() editbox = TextBox(t) #scroll = ScrollView() @@ -28,7 +28,6 @@ self.stop() return True - if __name__ == '__main__': app = MyApplication() app.start() diff -r 165b5d65e1cb -r 8680c2333546 demos/05_fixedlayout.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/05_fixedlayout.py Mon Feb 16 21:17:43 2015 +0100 @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 + +from tuikit.core.application import Application +from tuikit.widgets.button import Button + +app = Application() +app.add(Button('left=0'), left=0) +app.add(Button('left=5'), left=5, top=2) +app.add(Button('right=0'), right=0) +app.add(Button('right=5'), right=5, top=2) +app.add(Button('center=x'), center='x', top=3) +app.add(Button('center=x, left=5'), center='x', left=5, top=5) +app.add(Button('center=x, right=5'), center='x', right=5, top=7) +app.add(Button('center=xy'), center='xy') +app.add(Button('fill=x'), fill='x', top=9) +app.add(Button('fill=x, left=5'), fill='x', left=5, top=11) +app.add(Button('fill=x, right=5'), fill='x', right=5, top=13) +app.add(Button('fill=x, left=5, right=5'), fill='x', left=5, right=5, top=15) + +app.window_manager.sig_keypress.connect(lambda ev: app.stop()) +app.start() diff -r 165b5d65e1cb -r 8680c2333546 demos/05_gridlayout.py --- a/demos/05_gridlayout.py Sun Feb 15 17:50:24 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,26 +0,0 @@ -#!/usr/bin/env python3 - -import demobase - -from tuikit.layouts.grid import GridLayout -from tuikit.widgets.label import Label - -l1 = Label('Hello') -l1.sizemin.update(10, 1) - -grid = GridLayout() -grid.add(l1, 1, 1) -grid.update(10, 10) - -print(grid._grid_size) -print(grid._grid) - -for row in range(grid.row_count): - for col in range(grid.column_count): - w = grid.get_widget_at(row, col) - name = w.name if w else '--' - print(name.center(16), end='') - print() - -print(l1.pos) -print(l1.size) \ No newline at end of file diff -r 165b5d65e1cb -r 8680c2333546 demos/06_gridlayout.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/06_gridlayout.py Mon Feb 16 21:17:43 2015 +0100 @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 + +from tuikit.layouts.grid import GridLayout +from tuikit.widgets.label import Label + +l1 = Label('Hello') +l1.sizemin.update(10, 1) + +grid = GridLayout() +grid.add(l1, 1, 1) +grid.update(10, 10) + +print(grid._grid_size) +print(grid._grid) + +for row in range(grid.row_count): + for col in range(grid.column_count): + w = grid.get_widget_at(row, col) + name = w.name if w else '--' + print(name.center(16), end='') + print() + +print(l1.pos) +print(l1.size) \ No newline at end of file diff -r 165b5d65e1cb -r 8680c2333546 demos/__init__.py diff -r 165b5d65e1cb -r 8680c2333546 demos/base.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/demos/base.py Mon Feb 16 21:17:43 2015 +0100 @@ -0,0 +1,22 @@ +# Set system locale (needed for ncurses) +import locale +locale.setlocale(locale.LC_ALL, '') + +# Setup logging +import logging +logger = logging.getLogger('tuikit') +logger.setLevel(logging.DEBUG) +handler = logging.FileHandler(filename='tuikit.log') +handler.setLevel(logging.DEBUG) +formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s') +handler.setFormatter(formatter) +logger.addHandler(handler) + +# Escape key code is also used for escape sequences. After escape code, +# terminal waits for rest of sequence. This delay is 1 second by default. +# Let's hope that our terminal is fast enough to handle the sequences in 200ms. +import os +os.environ['ESCDELAY'] = '200' + +def setup(): + pass diff -r 165b5d65e1cb -r 8680c2333546 demos/demobase.py --- a/demos/demobase.py Sun Feb 15 17:50:24 2015 +0100 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,23 +0,0 @@ -# Path to root directory containing tuikit package -import sys -sys.path.append('..') - -# Set system locale (needed for ncurses) -import locale -locale.setlocale(locale.LC_ALL, '') - -# Setup logging -import logging -logger = logging.getLogger('tuikit') -logger.setLevel(logging.DEBUG) -handler = logging.FileHandler(filename='tuikit.log') -handler.setLevel(logging.DEBUG) -formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s %(message)s') -handler.setFormatter(formatter) -logger.addHandler(handler) - -# Escape key code is also used for escape sequences. After escape code, -# terminal waits for rest of sequence. This delay is 1 second by default. -# Let's hope that our terminal is fast enough to handle the sequences in 200ms. -import os -os.environ['ESCDELAY'] = '200' diff -r 165b5d65e1cb -r 8680c2333546 tuikit/core/application.py --- a/tuikit/core/application.py Sun Feb 15 17:50:24 2015 +0100 +++ b/tuikit/core/application.py Mon Feb 16 21:17:43 2015 +0100 @@ -29,6 +29,8 @@ self.set_driver(driver) self.set_theme(default_theme) self.window_manager.add(self.root_window) + # Convenience method + self.add = self.root_window.add def set_driver(self, driver_name): """Select driver to be used for rendering and input. diff -r 165b5d65e1cb -r 8680c2333546 tuikit/core/widget.py --- a/tuikit/core/widget.py Sun Feb 15 17:50:24 2015 +0100 +++ b/tuikit/core/widget.py Mon Feb 16 21:17:43 2015 +0100 @@ -29,8 +29,6 @@ #: Actual size. Modified by layout manager. self._size = Size(10, 10) - #: Requested position. Layout manager will use this when placing the widget. - self.posreq = Point() #: Requested size. Layout manager will use this when placing the widget. self.sizereq = Size(1, 1) #: Minimal size of widget. Widget will never be sized smaller than this. diff -r 165b5d65e1cb -r 8680c2333546 tuikit/driver/driver.py --- a/tuikit/driver/driver.py Sun Feb 15 17:50:24 2015 +0100 +++ b/tuikit/driver/driver.py Mon Feb 16 21:17:43 2015 +0100 @@ -27,12 +27,6 @@ """Clean up the screen etc.""" pass - def __enter__(self): - self.init() - - def __exit__(self, exc_type, exc_val, exc_tb): - self.close() - ## drawing ## def erase(self): @@ -56,14 +50,6 @@ """Set attribute to be used for subsequent draw operations.""" pass - def _parse_attr_desc(self, attr_desc): - parts = attr_desc.split(',') - fgbg = parts[0].split(' on ', 1) - fg = fgbg[0].strip().lower() - bg = fgbg[1:] and fgbg[1].strip().lower() or 'default' - attrs = (part.strip().lower() for part in parts[1:]) - return fg, bg, attrs - ## cursor ## def showcursor(self, x, y): @@ -84,3 +70,22 @@ """ return [] + + ## convenience implementations ## + + def _parse_attr_desc(self, attr_desc): + """Convenience implementation of attribute parsing. Not part of API.""" + parts = attr_desc.split(',') + fgbg = parts[0].split(' on ', 1) + fg = fgbg[0].strip().lower() + bg = fgbg[1:] and fgbg[1].strip().lower() or 'default' + attrs = (part.strip().lower() for part in parts[1:]) + return fg, bg, attrs + + ## with statement support ## + + def __enter__(self): + self.init() + + def __exit__(self, exc_type, exc_val, exc_tb): + self.close() diff -r 165b5d65e1cb -r 8680c2333546 tuikit/layouts/fixed.py --- a/tuikit/layouts/fixed.py Sun Feb 15 17:50:24 2015 +0100 +++ b/tuikit/layouts/fixed.py Mon Feb 16 21:17:43 2015 +0100 @@ -3,38 +3,74 @@ class FixedLayout(Layout): - """Widgets are placed on fixed position as specified in `sizereq`. + """Widgets are placed on fixed position as specified in hints. - Align hints (kwargs to :meth:`add`): - * halign = 'left' (default) | 'right' | 'fill' | 'center' - * valign = 'top' (default) | 'bottom' | 'fill' | 'center' + Position can be relative to any side or center of parent widget. """ def __init__(self): Layout.__init__(self) - self._widget_hints = {} # Widget : (halign, valign) + self._widget_hints = {} # Widget : (left, top, right, bottom, xrel, yrel) + + def add(self, widget, left=None, top=None, right=None, bottom=None, + center=None, fill=None): + """Add widget to layout. - def add(self, widget, *args, **kwargs): + Place hints: + * left, right, top, bottom = None | ; >= 0 + - Fix Widget position to parent sides. + - If both left and right (or top and bottom) are set, the widget + will be stretched to fill full area minus specified space. + * center, fill = None | 'x' | 'y' | 'xy' + - Center widget in x, y or both axes. + - Fill is shortcut for setting both positions in same axis. + + """ Layout.add(self, widget) - assert len(args) == 0, \ - "FixedLayout does not support positional hint args: %s" % args - assert all(key in ('halign', 'valign') for key in kwargs.keys()), \ - "Unsupported hints: %s" % tuple(kwargs.keys()) - halign = kwargs.get('halign', 'left') - valign = kwargs.get('valign', 'top') - self._widget_hints[widget] = (halign, valign) + # Internally, coordinate relation is marked as: + # '+': from left or top + # '-': from right or bottom + # 'C': from center + # 'F': from both sides (fill) + xrel, yrel = '+', '+' + fill, center = fill or '', center or '' + if left is None and right is not None: + xrel = '-' + if top is None and bottom is not None: + yrel = '-' + if 'x' in center: + xrel = 'C' + if 'y' in center: + yrel = 'C' + if 'x' in fill: + xrel = 'F' + if 'y' in fill: + yrel = 'F' + self._widget_hints[widget] = (left or 0, top or 0, + right or 0, bottom or 0, + xrel, yrel) def update(self, w, h): for widget in self._managed_widgets: - halign, valign = self._widget_hints[widget] - px, py = widget.posreq + left, top, right, bottom, xrel, yrel = self._widget_hints[widget] sw, sh = widget.sizereq - if halign == 'right': px = w - px - if valign == 'bottom': py = h - py - if halign == 'fill': px = 0; sw = w - if valign == 'fill': py = 0; sh = h - if halign == 'center': px = (w - sw) // 2 - if valign == 'center': py = (h - sh) // 2 + ox, oy = 0, 0 # origin + if xrel == '-': + ox = w - sw + if yrel == '-': + oy = h - sh + if xrel == 'C': + ox = (w - sw) // 2 + if yrel == 'C': + oy = (h - sh) // 2 + px = ox + left - right + py = oy + top - bottom + if xrel == 'F': + px = left + sw = w - left - right + if yrel == 'F': + py = top + sh = h - top - bottom widget.resize(sw, sh) widget.pos.update(px, py) diff -r 165b5d65e1cb -r 8680c2333546 tuikit/layouts/offset.py --- a/tuikit/layouts/offset.py Sun Feb 15 17:50:24 2015 +0100 +++ b/tuikit/layouts/offset.py Mon Feb 16 21:17:43 2015 +0100 @@ -9,13 +9,18 @@ def __init__(self): Layout.__init__(self) self._offset = Point() + self._widget_pos = {} @property def offset(self): """Offset of child widgets.""" return self._offset + def add(self, widget, x=0, y=0): + Layout.add(self, widget) + self._widget_pos[widget] = (x, y) + def update(self, _w, _h): for widget in self._managed_widgets: widget.resize(*widget.sizereq) - widget.pos.update(*(widget.posreq + self.offset)) + widget.pos.update(*(self.offset + self._widget_pos[widget])) diff -r 165b5d65e1cb -r 8680c2333546 tuikit/widgets/scrollview.py --- a/tuikit/widgets/scrollview.py Sun Feb 15 17:50:24 2015 +0100 +++ b/tuikit/widgets/scrollview.py Mon Feb 16 21:17:43 2015 +0100 @@ -21,11 +21,6 @@ Container.__init__(self, OffsetLayout) -class Scrollview(Container): - - def __init__(self): - Container.__init__(self) - class Scrolling: def __init__(self):