# HG changeset patch # User Radek Brich # Date 1358597121 -3600 # Node ID 2a0e04091898e418e79e60e84f2462575a3f763f # Parent 2f61931520c9119365663571536dc2b94cee93b6 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. diff -r 2f61931520c9 -r 2a0e04091898 demo_layout.py --- 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) diff -r 2f61931520c9 -r 2a0e04091898 demo_menu.py --- 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': diff -r 2f61931520c9 -r 2a0e04091898 tests/make_select.py --- /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) + diff -r 2f61931520c9 -r 2a0e04091898 tuikit/__init__.py --- 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 diff -r 2f61931520c9 -r 2a0e04091898 tuikit/application.py --- 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() diff -r 2f61931520c9 -r 2a0e04091898 tuikit/common.py --- 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] + diff -r 2f61931520c9 -r 2a0e04091898 tuikit/container.py --- 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 + diff -r 2f61931520c9 -r 2a0e04091898 tuikit/events.py --- 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 diff -r 2f61931520c9 -r 2a0e04091898 tuikit/layout.py --- 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] diff -r 2f61931520c9 -r 2a0e04091898 tuikit/menu.py --- 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): diff -r 2f61931520c9 -r 2a0e04091898 tuikit/menubar.py --- 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 - diff -r 2f61931520c9 -r 2a0e04091898 tuikit/scrollbar.py --- 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): diff -r 2f61931520c9 -r 2a0e04091898 tuikit/widget.py --- 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()