Update FixedLayout. Add demo launcher.
authorRadek Brich <radek.brich@devl.cz>
Mon, 16 Feb 2015 21:17:43 +0100
changeset 117 8680c2333546
parent 116 165b5d65e1cb
child 118 8c7970520632
Update FixedLayout. Add demo launcher.
.hgignore
INSPIRATION
Makefile
demo.py
demos/01_buffer.py
demos/02_curses.py
demos/03_application.py
demos/04_texteditor.py
demos/05_fixedlayout.py
demos/05_gridlayout.py
demos/06_gridlayout.py
demos/__init__.py
demos/base.py
demos/demobase.py
tuikit/core/application.py
tuikit/core/widget.py
tuikit/driver/driver.py
tuikit/layouts/fixed.py
tuikit/layouts/offset.py
tuikit/widgets/scrollview.py
--- 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
--- 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
--- /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
--- /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 <name>\n" % sys.argv[0])
+    print("Where <name> 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())
--- 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
 
--- 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
 
--- 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):
--- 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()
--- /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()
--- 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
--- /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
--- /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
--- 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'
--- 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.
--- 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.
--- 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()
--- 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 | <num> ; <num> >= 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)
--- 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]))
--- 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):