Rework MenuBar. Add MenuButton. Add mouse event cascading to floaters.
LinearLayout: spacing now applies to all children, not just those with expand.
Fix Window resize request inside layouts.
UnicodeGraphics: prepare for styling/theming.
--- a/demo_layout.py Fri Jan 18 22:36:50 2013 +0100
+++ b/demo_layout.py Sat Jan 19 13:05:21 2013 +0100
@@ -17,6 +17,7 @@
self._row_num = 0
self.buildrow()
+ self.buildrow(spacing=1)
self.buildrow(expand=True)
self.buildrow(expand=True, fill=True)
self.buildrow(homogeneous=True)
--- a/demo_menu.py Fri Jan 18 22:36:50 2013 +0100
+++ b/demo_menu.py Sat Jan 19 13:05:21 2013 +0100
@@ -4,28 +4,22 @@
import locale
locale.setlocale(locale.LC_ALL, '')
-from tuikit import Application, MenuBar, Menu, Window, VerticalLayout
+from tuikit import Application, MenuBar, MenuButton, Menu, Window
class MyApplication(Application):
def __init__(self):
Application.__init__(self)
- self.top.add_handler('keypress', self.on_top_keypress)
-
- menubar = MenuBar()
- self.top.add(menubar)
+ self.top.add_handler('keypress', self.on_top_keypress, last=True)
helpwin = Window()
+ helpwin.title = 'About'
+ helpwin.hide()
self.top.add(helpwin)
- helpwin.x = 10
- helpwin.y = 5
- helpwin.allow_layout = False
- helpwin.hidden = True
- helpwin.title = 'About'
+ helpwin.move(10, 5)
#helpwin.closebutton = False
#helpwin.resizable = False
-
filemenu = Menu([
('New', None),
None,
@@ -34,21 +28,21 @@
None,
('Quit', self.terminate),
])
- self.top.add(filemenu)
-
editmenu = Menu([('Copy', None), ('Paste', None)])
helpmenu = Menu([('About', helpwin)])
- self.top.add(editmenu)
- self.top.add(helpmenu)
-
- menubar.setitems([
+ menubar_items = [
('File', filemenu),
('Edit', editmenu),
('Help', helpmenu),
- ])
+ ]
- self.top.layout = VerticalLayout()
+ menubar = MenuBar(menubar_items)
+ self.top.add(menubar, halign='fill')
+
+ menu = Menu([('Copy', None), ('Paste', None)])
+ menubtn = MenuButton('MenuButton', menu)
+ self.top.add(menubtn, halign='center', valign='center')
def on_top_keypress(self, ev):
if ev.keyname == 'escape':
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/make_select.py Sat Jan 19 13:05:21 2013 +0100
@@ -0,0 +1,24 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+import sys
+sys.path.append('..')
+
+from tuikit.common import *
+
+XSel = make_select('left', 'right', 'bottom', 'top')
+print(XSel)
+
+sel = XSel()
+print('XSel().selected:', sel.selected)
+
+try:
+ sel.update('center')
+except ValueError as e:
+ print('Error:', e)
+
+try:
+ pure_sel = Select()
+except TypeError as e:
+ print('Error:', e)
+
--- a/tuikit/__init__.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/__init__.py Sat Jan 19 13:05:21 2013 +0100
@@ -11,7 +11,7 @@
from tuikit.label import Label
from tuikit.layout import VerticalLayout, HorizontalLayout, GridLayout, AnchorLayout
from tuikit.menu import Menu
-from tuikit.menubar import MenuBar
+from tuikit.menubar import MenuBar, MenuButton
from tuikit.pager import Pager
from tuikit.scrollbar import VScrollbar
from tuikit.scrollview import ScrollView
--- a/tuikit/application.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/application.py Sat Jan 19 13:05:21 2013 +0100
@@ -58,7 +58,7 @@
def __init__(self, top_layout=AnchorLayout, driver='curses'):
'''Create application.'''
self._setup_logging()
-
+
# Top widget
self._top = None
self._timer = Timer()
--- a/tuikit/common.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/common.py Sat Jan 19 13:05:21 2013 +0100
@@ -274,3 +274,17 @@
LTEE = '├'
RTEE = '┤'
+ char_map = {
+ # scrollbar
+ 'sb_thumb' : CIRCLE,
+ 'sb_htrack' : LINE[8],
+ 'sb_vtrack' : LINE[10],
+ 'sb_left' : LEFT_ARROW,
+ 'sb_right' : RIGHT_ARROW,
+ 'sb_up' : UP_ARROW,
+ 'sb_down' : DOWN_ARROW,
+ }
+
+ def get_char(self, name):
+ return self.char_map[name]
+
--- a/tuikit/container.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/container.py Sat Jan 19 13:05:21 2013 +0100
@@ -50,7 +50,7 @@
except AttributeError:
widget.hints[key] = widget.hints[key].__class__(kwargs[key])
if self.focuschild is None and widget.can_focus():
- self.focuschild = widget
+ widget.set_focus()
def add_floater(self, widget, **kwargs):
widget.floater = True
@@ -81,7 +81,7 @@
child.top = value
- def focus_next(self):
+ def focus_next(self, step=1):
"""Focus next child.
Sets focus to next child, if there is one
@@ -104,16 +104,31 @@
idx_new = idx_current
cycled = False
while True:
- idx_new += 1
+ idx_new += step
if idx_new >= len(self.children):
idx_new = 0
cycled = True
+ if idx_new < 0: # for focus_previous
+ idx_new = len(self.children) - 1
+ cycled = True
if idx_current == idx_new:
return False
if self.children[idx_new].can_focus():
self.children[idx_new].set_focus()
return self.trap_focus or not cycled
+ def focus_previous(self):
+ """Focus previous child."""
+ self.focus_next(-1)
+
+ def on_focus(self, ev):
+ if self.focuschild:
+ self.focuschild.emit('focus', ev)
+
+ def on_unfocus(self, ev):
+ if self.focuschild:
+ self.focuschild.emit('unfocus', ev)
+
def draw(self, driver, x, y):
"""Draw the container and its children.
@@ -149,8 +164,8 @@
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
+ #logging.getLogger('tuikit').info('draw_floaters %s %r %r', self, x ,y )
+ # draw our floaters
for child in [ch for ch in self.children if ch.floater]:
child.draw(driver,
x + self.offset.x + child.x,
@@ -166,23 +181,14 @@
child.emit('resize')
def on_keypress(self, ev):
- if self.focuschild is not None:
- handled = self.focuschild.emit('keypress', ev)
- if handled:
+ if self.focuschild is not None and not self.focuschild.hidden:
+ if self.focuschild.emit('keypress', ev):
return True
if ev.keyname == 'tab':
return self.focus_next()
def on_mousedown(self, ev):
- handled = False
- for child in reversed(self.children):
- 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
- return handled
+ self.cascade_mouse_event(ev)
def on_mouseup(self, ev):
if self.mousechild:
@@ -198,12 +204,47 @@
return True
def on_mousewheel(self, ev):
- handled = False
- for child in reversed(self.children):
+ self.cascade_mouse_event(ev)
+
+ def cascade_mouse_event(self, ev):
+ """Resend mouse event to child under cursor.
+
+ Args:
+ ev: Original mouse event. Event type and cursor
+ position is extracted from this.
+
+ Returns:
+ Boolean value. True when event was handled by a child.
+
+ """
+ if self.parent is None:
+ if self.floaters_mouse_event(ev):
+ return True
+ for child in reversed([ch for ch in self.children if not ch.floater]):
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
+ child.emit(ev.event_name, child_ev)
+ if ev.event_name == 'mousedown':
+ self.mousechild = child
+ return True
+ return False
+ def floaters_mouse_event(self, ev):
+ """Resend mouse event to our floaters and any floaters in child containers."""
+ # delve into child containers, test their floaters
+ for child in reversed([ch for ch in self.children \
+ if not ch.floater and isinstance(ch, Container)]):
+ child_ev = ev.make_child_event(self, child)
+ if child.floaters_mouse_event(child_ev):
+ if ev.event_name == 'mousedown':
+ self.mousechild = child
+ return True
+ # test our floaters
+ for child in reversed([ch for ch in self.children if ch.floater]):
+ if child.enclose(ev.wx - self.offset.x, ev.wy - self.offset.y):
+ child_ev = ev.make_child_event(self, child)
+ child.emit(ev.event_name, child_ev)
+ if ev.event_name == 'mousedown':
+ self.mousechild = child
+ return True
+
--- a/tuikit/events.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/events.py Sat Jan 19 13:05:21 2013 +0100
@@ -71,6 +71,8 @@
def make_child_event(self, container, child):
ev = MouseEvent(self.x, self.y, self.button)
+ ev.originator = self.originator
+ ev.event_name = self.event_name
# original local coordinates are new parent coordinates
ev.px = self.wx
ev.py = self.wy
--- a/tuikit/layout.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/layout.py Sat Jan 19 13:05:21 2013 +0100
@@ -15,6 +15,13 @@
class Layout(Container):
+ def add(self, widget, **kwargs):
+ Container.add(self, widget, **kwargs)
+ widget.add_handler('sizereq', self.on_child_sizereq)
+
+ def on_child_sizereq(self, ev):
+ self.emit('resize')
+
def _get_children(self):
return [child for child in self.children
if not child.floater and not child.hidden]
@@ -146,9 +153,10 @@
size[ax1] = max(child.sizereq[ax1], child.sizemin[ax1])
size[ax2] = space2
+ offset += self.spacing
if child.hints['expand'] or self.homogeneous:
maxsize = int(round(space_child + math.modf(offset)[0], 2))
- offset += space_child + self.spacing
+ offset += space_child
if not self.homogeneous:
maxsize += child.sizereq[ax1]
offset += child.sizereq[ax1]
--- a/tuikit/menu.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/menu.py Sat Jan 19 13:05:21 2013 +0100
@@ -18,12 +18,11 @@
self.highlight = 'menu-active'
self.items = items
self.selected = items[0]
- self.menubar = None
self.add_events('activate', GenericEvent)
def on_draw(self, ev):
- logging.getLogger('tuikit').info('menu draw %s %s %s', ev, ev.x, ev.y)
+ #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
@@ -46,10 +45,13 @@
def on_keypress(self, ev):
if ev.keyname == 'up':
self.move_selected(-1)
- if ev.keyname == 'down':
+ return True
+ if ev.keyname in ('down', 'tab'):
self.move_selected(+1)
+ return True
if ev.keyname == 'enter':
self.run_selected()
+ return True
self.redraw()
def on_mousedown(self, ev):
--- a/tuikit/menubar.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/menubar.py Sat Jan 19 13:05:21 2013 +0100
@@ -1,101 +1,82 @@
# -*- coding: utf-8 -*-
-from tuikit.widget import Widget
+from tuikit.layout import HorizontalLayout
+from tuikit.button import Button
+from tuikit.container import Container
-class MenuBar(Widget):
+class MenuButton(Container, Button):
+ def __init__(self, label='', menu=None):
+ Container.__init__(self)
+ Button.__init__(self, label)
+ if menu:
+ self.menu = menu
+
+ @property
+ def menu(self):
+ return self.children[0]
+
+ @menu.setter
+ def menu(self, widget):
+ self.add_floater(widget)
+ widget.move(0, 1)
+ widget.hide()
+
+ def on_click(self, ev):
+ self.menu.show()
+
+ def on_unfocus(self, ev):
+ self.menu.hide()
+
+ def show_menu(self):
+ if not self.menu.hidden:
+ return False
+ self.menu.show()
+ return True
+
+
+class MenuBar(HorizontalLayout):
def __init__(self, items = []):
- Widget.__init__(self, 0, 1)
-
+ HorizontalLayout.__init__(self, spacing=4)
+ self._default_size.update(20, 1)
self.allow_focus = True
self.bg = 'menu'
self.highlight = 'menu-active'
- self.setitems(items)
- self.selected = None
-
- def setitems(self, items):
- self.items = items
+ for title, widget in items:
+ self.add_menu(title, widget)
- i = 0
- for item in self.items:
- if isinstance(item[1], Widget):
- item[1].x = i
- item[1].y = self.y + 1
- item[1].allow_layout = False
- item[1].hidden = True
- item[1].add_handler('focus', self.on_submenu_focus)
- item[1].menubar = self
- i += len(item[0]) + 4
+ def add_menu(self, title, widget):
+ btn = MenuButton(title)
+ btn.menu = widget
+ btn.bg = self.bg
+ btn.bghi = self.highlight
+ btn.prefix = ''
+ btn.suffix = ''
+ self.add(btn)
def on_draw(self, ev):
ev.driver.pushcolor(self.bg)
- i = 0
- for item in self.items:
- if self.selected == item:
- ev.driver.pushcolor(self.highlight)
- ev.driver.puts(ev.x + i, ev.y, ' ' + item[0] + ' ')
- ev.driver.popcolor()
- else:
- ev.driver.puts(ev.x + i, ev.y, ' ' + item[0] + ' ')
- i += len(item[0]) + 4
- if i < self.width:
- ev.driver.puts(ev.x + i, ev.y, ' ' * (self.width - i))
+ ev.driver.puts(ev.x, ev.y, ' ' * self.width)
ev.driver.popcolor()
-
def on_keypress(self, ev):
+ # do we have some menu dropped down?
+ menu_visible = not self.focuschild.menu.hidden
if ev.keyname == 'left':
- self.move_selected(-1)
+ res = self.focus_previous()
+ if menu_visible:
+ return self.focuschild.show_menu()
+ return res
elif ev.keyname == 'right':
- self.move_selected(+1)
- else:
- if self.selected:
- if isinstance(self.selected[1], Widget):
- self.selected[1].emit('keypress', ev.keyname, ev.char)
-
-
- def move_selected(self, offset):
- if self.selected:
- i = self.items.index(self.selected)
- item = self.items[(i + offset) % len(self.items)]
- self.unselect()
- self.select(item)
- self.redraw()
-
- def on_mousedown(self, ev):
- self._select_xy(ev.wx, ev.wy)
-
- def on_mousemove(self, ev):
- self._select_xy(ev.wx, ev.wy)
+ res = self.focus_next()
+ if menu_visible:
+ return self.focuschild.show_menu()
+ return res
+ elif ev.keyname == 'down':
+ return self.focuschild.show_menu()
+ elif menu_visible and ev.keyname == 'escape':
+ self.focuschild.menu.hide()
+ return True
- def _select_xy(self, wx, wy):
- i = 0
- self.unselect()
- for item in self.items:
- w = len(item[0]) + 4
- if wx >= i and wx < i + w:
- self.select(item)
- i += w
-
- def on_unfocus(self, ev):
- if self.selected and ev.new == self.selected[1]:
- return
- self.unselect()
-
- def on_submenu_focus(self, ev):
- self.set_focus()
-
- def select(self, item):
- self.selected = item
- if isinstance(item[1], Widget):
- item[1].hidden = False
- item[1].redraw()
-
- def unselect(self):
- if self.selected:
- if isinstance(self.selected[1], Widget):
- self.selected[1].hidden = True
- self.selected = None
-
--- a/tuikit/scrollbar.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/scrollbar.py Sat Jan 19 13:05:21 2013 +0100
@@ -2,7 +2,6 @@
from tuikit.widget import Widget
from tuikit.events import Event
-from tuikit.common import UnicodeGraphics
class Scrollbar(Widget):
@@ -27,10 +26,6 @@
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)
@@ -118,12 +113,13 @@
self._default_size.update(1, 20)
def on_draw(self, ev):
+ ug = ev.driver.unigraph
ev.driver.pushcolor('normal')
- ev.driver.putch(ev.x, ev.y, ev.driver.unigraph.UP_ARROW)
+ ev.driver.putch(ev.x, ev.y, ug.get_char('sb_up'))
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.putch(ev.x, ev.y + i, ug.get_char('sb_vtrack'))
+ ev.driver.putch(ev.x, ev.y + 1 + self._thumb_pos, ug.get_char('sb_thumb'))
+ ev.driver.putch(ev.x, ev.y + self.height - 1, ug.get_char('sb_down'))
ev.driver.popcolor()
def on_mousedown(self, ev):
@@ -166,12 +162,13 @@
self._default_size.update(20, 1)
def on_draw(self, ev):
+ ug = ev.driver.unigraph
ev.driver.pushcolor('normal')
- ev.driver.putch(ev.x, ev.y, ev.driver.unigraph.LEFT_ARROW)
+ ev.driver.putch(ev.x, ev.y, ug.get_char('sb_left'))
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.putch(ev.x + i, ev.y, ug.get_char('sb_htrack'))
+ ev.driver.putch(ev.x + 1 + self._thumb_pos, ev.y, ug.get_char('sb_thumb'))
+ ev.driver.putch(ev.x + self.width - 1, ev.y, ug.get_char('sb_right'))
ev.driver.popcolor()
def on_mousedown(self, ev):
--- a/tuikit/widget.py Fri Jan 18 22:36:50 2013 +0100
+++ b/tuikit/widget.py Sat Jan 19 13:05:21 2013 +0100
@@ -110,8 +110,6 @@
"""
self._sizereq.update(w, h)
- #if self.parent:
- # self.parent.emit('resize')
@property
def sizereq(self):
@@ -200,7 +198,6 @@
return (self.parent.has_focus() \
and self.parent.focuschild == self)
-
def set_focus(self):
"""Focus the widget.
@@ -213,7 +210,9 @@
See also grab_focus() which cares about parents.
"""
- if self.has_focus() or not self.can_focus():
+ if self.parent is None:
+ return
+ if not self.can_focus() or self.parent.focuschild == self:
return
oldfocuschild = self.parent.focuschild
self.parent.focuschild = self
@@ -221,12 +220,11 @@
oldfocuschild.emit('unfocus', new=self)
self.emit('focus', old=oldfocuschild)
-
def grab_focus(self):
"""Focus the widget and its parents."""
- self.set_focus()
if self.parent and not self.parent.has_focus():
self.parent.grab_focus()
+ self.set_focus()
###
@@ -257,8 +255,6 @@
def hide(self):
'''Hide widget. Convenience method.'''
self.hidden = True
- if self.parent and self.has_focus():
- self.parent.focus_next()
self.redraw()