Git Repositories

Correct most of the problems with urwid.
authorCyrille Pontvieux <jrd@enialis.net>
Mon, 24 Mar 2014 15:08:48 +0000 (16:08 +0100)
committerCyrille Pontvieux <jrd@enialis.net>
Mon, 24 Mar 2014 15:08:48 +0000 (16:08 +0100)
Now the focus gain/focus lost is handled correctly with urwid_more.
At least with the keyboard. With the mouse there still are duplicate
events. To correctly handle the focus in urwid, one must write another
library, urwid is full of dirtly hacks and shortcuts.

All in all, label validation is now ok, Lilo table is usable and most of
all, contextual help is now working good.

src/bootsetup.py
src/lib/bootsetup_curses.py
src/lib/gathercurses.py
src/lib/testurwidmore.py [new file with mode: 0755]
src/lib/urwid_more.py
src/resources/bootsetup.glade

index 50f282e..bf6eb33 100755 (executable)
@@ -34,7 +34,7 @@ class BootSetup:
     self._isTest = isTest
     self._useTestData = useTestData
     print "BootSetup v{ver}".format(ver = version)
-  
+
   @abc.abstractmethod
   def run_setup(self):
     """
@@ -87,7 +87,8 @@ def die(s, exit = 1):
     sys.exit(exit)
 
 if __name__ == '__main__':
-  os.chdir(os.path.dirname(__file__))
+  if os.path.dirname(__file__):
+    os.chdir(os.path.dirname(__file__))
   is_graphic = bool(os.environ.get('DISPLAY'))
   is_test = False
   use_test_data = False
@@ -121,7 +122,7 @@ if __name__ == '__main__':
           target_partition = arg
         else:
           die(_("Unrecognized parameter '{0}'.").format(arg))
-  if not bootloader or bootloader not in ['lilo', 'grub2', '_']:
+  if bootloader not in ('lilo', 'grub2', '_', None):
     die(_("bootloader parameter should be lilo, grub2 or '_', given {0}.").format(bootloader))
   if bootloader == '_':
     bootloader = None
index b058262..b0e3a65 100644 (file)
@@ -13,9 +13,9 @@ import os
 import sys
 import gettext
 import re
+import urwid_more as urwidm
 from gathercurses import *
 from bootsetup import *
-import urwid_more as urwidm
 
 class BootSetupCurses(BootSetup):
   
index d9e511b..c318b59 100644 (file)
@@ -24,7 +24,7 @@ class GatherCurses:
   """
   UI in curses/urwid to gather information about the configuration to setup.
   """
-  
+
   # Other potential color schemes can be found at:
   # http://excess.org/urwid/wiki/RecommendedPalette
   _palette = [
@@ -77,7 +77,7 @@ boot partitions:{boot_partitions}
     self.ui = urwidm.raw_display.Screen()
     self.ui.set_mouse_tracking()
     self._palette.extend(bootsetup._palette)
-  
+
   def run(self):
     self._createMainView()
     self._createHelpView()
@@ -91,7 +91,7 @@ boot partitions:{boot_partitions}
       self._radioGrub2.set_state(True)
       self._mainView.body.set_focus(self._mbrDeviceSectionPosition)
     self._loop.run()
-  
+
   def _infoDialog(self, message):
     self._bootsetup.info_dialog(message, parent = self._loop.widget)
 
@@ -101,15 +101,16 @@ boot partitions:{boot_partitions}
   def _updateScreen(self):
     if self._loop and self._loop.screen._started:
       self._loop.draw_screen()
-    
+
   def _onHelpFocusGain(self, widget, context):
-    print "help context", context, widget
-    raw_input("")
     self._helpCtx = context
     return True
   def _onHelpFocusLost(self, widget):
     self._helpCtx = ''
     return True
+  def _installHelpContext(self, widget, context):
+    urwidm.connect_signal(widget, 'focusgain', self._onHelpFocusGain, context)
+    urwidm.connect_signal(widget, 'focuslost', self._onHelpFocusLost)
 
   def _createComboBox(self, label, elements):
     l = [urwidm.TextMultiValues(el) if isinstance(el, list) else el for el in elements]
@@ -117,7 +118,7 @@ boot partitions:{boot_partitions}
     comboBox.set_combo_attrs('combobody', 'combofocus')
     comboBox.cbox.sensitive_attr = ('focusable', 'focus_combo')
     return comboBox
-  
+
   def _createComboBoxEdit(self, label, elements):
     l = [urwidm.TextMultiValues(el) if isinstance(el, list) else el for el in elements]
     comboBox = urwidm.ComboBoxEdit(label, l)
@@ -150,14 +151,15 @@ boot partitions:{boot_partitions}
 +---------------------------------------+
 | Bootloader: (×) LiLo (_) Grub2        |
 | MBR Device:  |_____________ ↓|        | <== ComboBox thanks to wicd
-| Grub2 files: |_____________ ↓|        | --|
-|           <Edit config>               | --+- <== Grub2 only
-| +-----------------------------------+ | --+
+| Grub2 files: |_____________ ↓|        | --
+|           <Edit config>               | --}- <== Grub2 only
+|                                       |
+| +-----------------------------------+ | --
 | |Dev.|FS  |Type |Label      |Actions| |   |
 | |sda1|ext4|Salix|Salix14____|<↑><↓> | |   |
 | |sda5|xfs |Arch |ArchLinux__|<↑><↓> | |   +- <== LiLo only
 | +-----------------------------------+ |   |
-| <Edit config>    <Undo custom config> | --+
+| <Edit config>    <Undo custom config> | --
 |            <Install>                  |
 +=======================================+
 | H: Help, A: About, Q: Quit            | <== Action keyboard thanks to wicd
@@ -191,16 +193,14 @@ a boot menu if several operating systems are available on the same computer.")
     self._radioLiLo = self._createRadioButton(radioGroupBootloader, "LiLo", state = False, on_state_change = self._onLiLoChange)
     self._radioGrub2 = self._createRadioButton(radioGroupBootloader, "Grub2", state = False, on_state_change = self._onGrub2Change)
     bootloaderTypeSection = urwidm.ColumnsMore([lblBootloader, self._radioLiLo, self._radioGrub2], focus_column = 1)
-    urwidm.connect_signal(bootloaderTypeSection, 'focusgain', self._onHelpFocusGain, 'type')
-    urwidm.connect_signal(bootloaderTypeSection, 'focuslost', self._onHelpFocusLost)
+    self._installHelpContext(bootloaderTypeSection, 'type')
     # mbr device section
     mbrDeviceSection = self._createMbrDeviceSectionView()
     # bootloader section
     self._bootloaderSection = urwidm.WidgetPlaceholderMore(urwidm.Text(""))
     # install section
     btnInstall = self._createButton(_("_Install bootloader").replace("_", ""), on_press = self._onInstall)
-    urwidm.connect_signal(btnInstall, 'focusgain', self._onHelpFocusGain, 'install')
-    urwidm.connect_signal(btnInstall, 'focuslost', self._onHelpFocusLost)
+    self._installHelpContext(btnInstall, 'install')
     installSection = self._createCenterButtonsWidget([btnInstall])
     # body
     bodyList = [urwidm.Divider(), txtIntro, urwidm.Divider('─', bottom = 1), bootloaderTypeSection, mbrDeviceSection, urwidm.Divider(), self._bootloaderSection, urwidm.Divider('─', top = 1, bottom = 1), installSection]
@@ -210,7 +210,7 @@ a boot menu if several operating systems are available on the same computer.")
     frame = urwidm.FrameMore(body, header, footer, focus_part = 'body')
     frame.attr = 'body'
     self._mainView = frame
-  
+
   def _createHelpView(self):
     bodyPile = urwidm.PileMore([urwidm.Divider(), urwidm.TextMore("Help")])
     bodyPile.attr = 'body'
@@ -260,8 +260,7 @@ a boot menu if several operating systems are available on the same computer.")
   def _createMbrDeviceSectionView(self):
     comboBox = self._createComboBoxEdit(_("Install bootloader on:"), self.cfg.disks)
     urwidm.connect_signal(comboBox, 'change', self._onMBRChange)
-    urwidm.connect_signal(comboBox, 'focusgain', self._onHelpFocusGain, 'mbr')
-    urwidm.connect_signal(comboBox, 'focuslost', self._onHelpFocusLost)
+    self._installHelpContext(comboBox, 'mbr')
     return comboBox
 
   def _createBootloaderSectionView(self):
@@ -273,10 +272,12 @@ a boot menu if several operating systems are available on the same computer.")
       listFS = [urwidm.TextMore(listFSTitle)]
       listType = [urwidm.TextMore(_("Operating system"))]
       listLabel = [urwidm.TextMore(listLabelTitle)]
-      listAction = [urwidm.TextMore("")]
-      for l in (listDev, listFS, listType, listLabel, listAction):
+      listActionUp = [urwidm.TextMore("")]
+      listActionDown = [urwidm.TextMore("")]
+      for l in (listDev, listFS, listType, listLabel, listActionUp, listActionDown):
         l[0].sensitive_attr = 'strong'
       self._labelPerDevice = {}
+      self.cfg.boot_partitions.append(['dev', 'fs', '', 'type', 'label'])
       for p in self.cfg.boot_partitions:
         dev = p[0]
         fs = p[1]
@@ -288,32 +289,32 @@ a boot menu if several operating systems are available on the same computer.")
         self._labelPerDevice[dev] = label
         editLabel = self._createEdit(edit_text = label, wrap = urwidm.CLIP)
         urwidm.connect_signal(editLabel, 'change', self._onLabelChange, dev)
-        urwidm.connect_signal(editLabel, 'focuslost', self._onLabelFocusLost, dev)
         urwidm.connect_signal(editLabel, 'focusgain', self._onHelpFocusGain, 'lilotable')
-        urwidm.connect_signal(editLabel, 'focuslost', self._onHelpFocusLost)
+        urwidm.connect_signal(editLabel, 'focuslost', self._onLabelFocusLost, dev)
         listLabel.append(editLabel)
         btnUp = self._createButton("↑", on_press = self._moveLineUp, user_data = p[0])
+        self._installHelpContext(btnUp, 'liloup')
+        listActionUp.append(btnUp)
         btnDown = self._createButton("↓", on_press = self._moveLineDown, user_data = p[0])
-        urwidm.connect_signal(btnUp, 'focusgain', self._onHelpFocusGain, 'liloup')
-        urwidm.connect_signal(btnUp, 'focuslost', self._onHelpFocusLost)
-        urwidm.connect_signal(btnDown, 'focusgain', self._onHelpFocusGain, 'lilodown')
-        urwidm.connect_signal(btnDown, 'focuslost', self._onHelpFocusLost)
-        listAction.append(urwidm.GridFlowMore([btnUp, btnDown], cell_width = 5, h_sep = 1, v_sep = 1, align = "center"))
+        self._installHelpContext(btnDown, 'lilodown')
+        listActionDown.append(btnDown)
       colDev = urwidm.PileMore(listDev)
       colFS = urwidm.PileMore(listFS)
       colType = urwidm.PileMore(listType)
       colLabel = urwidm.PileMore(listLabel)
-      colAction = urwidm.PileMore(listAction)
-      self._liloTable = urwidm.ColumnsMore([('fixed', max(6, len(listDevTitle)), colDev), ('fixed', max(6, len(listFSTitle)), colFS), colType, ('fixed', max(self._liloMaxChars + 1, len(listLabelTitle)), colLabel), ('fixed', 11, colAction)], dividechars = 1)
+      colActionUp = urwidm.PileMore(listActionUp)
+      colActionDown = urwidm.PileMore(listActionDown)
+      urwidm.connect_signal(colLabel, 'focuslost', self._onLiloColumnFocusLost, [colLabel, colActionUp, colActionDown])
+      urwidm.connect_signal(colActionUp, 'focuslost', self._onLiloColumnFocusLost, [colLabel, colActionUp, colActionDown])
+      urwidm.connect_signal(colActionDown, 'focuslost', self._onLiloColumnFocusLost, [colLabel, colActionUp, colActionDown])
+      self._liloTable = urwidm.ColumnsMore([('fixed', max(6, len(listDevTitle)), colDev), ('fixed', max(6, len(listFSTitle)), colFS), colType, ('fixed', max(self._liloMaxChars + 1, len(listLabelTitle)), colLabel), ('fixed', 5, colActionUp), ('fixed', 5, colActionDown)], dividechars = 1)
       self._liloTableLines = urwidm.LineBoxMore(self._liloTable)
       self._liloTableLines.sensitive_attr = "strong"
       self._liloTableLines.unsensitive_attr = "unfocusable"
       self._liloBtnEdit = self._createButton(_("_Edit configuration").replace("_", ""), on_press = self._editLiLoConf)
-      urwidm.connect_signal(self._liloBtnEdit, 'focusgain', self._onHelpFocusGain, 'liloedit')
-      urwidm.connect_signal(self._liloBtnEdit, 'focuslost', self._onHelpFocusLost)
+      self._installHelpContext(self._liloBtnEdit, 'liloedit')
       self._liloBtnCancel = self._createButton(_("_Undo configuration").replace("_", ""), on_press = self._cancelLiLoConf)
-      urwidm.connect_signal(self._liloBtnCancel, 'focusgain', self._onHelpFocusGain, 'lilocancel')
-      urwidm.connect_signal(self._liloBtnCancel, 'focuslost', self._onHelpFocusLost)
+      self._installHelpContext(self._liloBtnCancel, 'lilocancel')
       self._liloButtons = self._createCenterButtonsWidget([self._liloBtnEdit, self._liloBtnCancel])
       pile = urwidm.PileMore([self._liloTableLines, self._liloButtons])
       self._updateLiLoButtons()
@@ -321,16 +322,19 @@ a boot menu if several operating systems are available on the same computer.")
     elif self.cfg.cur_bootloader == 'grub2':
       comboBox = self._createComboBox(_("Install Grub2 files on:"), self.cfg.partitions)
       urwidm.connect_signal(comboBox, 'change', self._onGrub2FilesChange)
-      urwidm.connect_signal(comboBox, 'focusgain', self._onHelpFocusGain, 'partition')
-      urwidm.connect_signal(comboBox, 'focuslost', self._onHelpFocusLost)
+      self._installHelpContext(comboBox, 'partition')
       self._grub2BtnEdit = self._createButton(_("_Edit configuration").replace("_", ""), on_press = self._editGrub2Conf)
-      urwidm.connect_signal(self._grub2BtnEdit, 'focusgain', self._onHelpFocusGain, 'grub2edit')
-      urwidm.connect_signal(self._grub2BtnEdit, 'focuslost', self._onHelpFocusLost)
+      self._installHelpContext(self._grub2BtnEdit, 'grub2edit')
       pile = urwidm.PileMore([comboBox, self._createCenterButtonsWidget([self._grub2BtnEdit])])
       self._onGrub2FilesChange(comboBox, comboBox.selected_item[0], None)
       return pile
     else:
       return urwidm.Text("")
+  def _onLiloColumnFocusLost(self, widget, columnWidgets):
+    pos = widget.get_focus_pos()
+    for cw in columnWidgets:
+      cw.focus_item = cw.widget_list[pos] # set focus item directly without using set_focus method to prevent FG/FL events
+    return True
 
   def _changeBootloaderSection(self):
     self._bootloaderSection.original_widget = self._createBootloaderSectionView()
@@ -538,7 +542,7 @@ click on this button to install your bootloader.")
       os.remove(lilocfg)
     self._custom_lilo = False
     self._updateLiLoButtons()
+
   def _set_sensitive_rec(self, w, state):
     w.sensitive = state
     if hasattr(w, "widget_list"):
@@ -577,7 +581,7 @@ click on this button to install your bootloader.")
       self._grub2_conf = False
     self._grub2BtnEdit.sensitive = self._grub2_conf
     self._updateScreen()
-  
+
   def _editGrub2Conf(self, button):
     partition = os.path.join("/dev", self.cfg.cur_boot_partition)
     if sltl.isMounted(partition):
diff --git a/src/lib/testurwidmore.py b/src/lib/testurwidmore.py
new file mode 100755 (executable)
index 0000000..d4c8abf
--- /dev/null
@@ -0,0 +1,105 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: set et ai sta sw=2 ts=2 tw=0:
+from __future__ import unicode_literals
+
+import urwid_more as urwidm
+palette = [
+    ('body', 'light gray', 'black'),
+    ('header', 'white', 'dark blue'),
+    ('footer', 'light green', 'black'),
+    ('footer_key', 'yellow', 'black'),
+    ('strong', 'white', 'black'),
+    ('copyright', 'light blue', 'black'),
+    ('authors', 'light cyan', 'black'),
+    ('translators', 'light green', 'black'),
+    ('focusable', 'light green', 'black'),
+    ('unfocusable', 'dark blue', 'black'),
+    ('focus', 'black', 'dark green'),
+    ('focus_edit', 'yellow', 'black'),
+    ('focus_icon', 'yellow', 'black'),
+    ('focus_radio', 'yellow', 'black'),
+    ('focus_combo', 'black', 'dark green'),
+    ('combobody', 'light gray', 'dark blue'),
+    ('combofocus', 'black', 'brown'),
+    ('error', 'white', 'dark red'),
+    ('focus_error', 'light red', 'black'),
+    ('important', 'yellow', 'black', 'bold'),
+    ('info', 'white', 'dark blue', 'bold'),
+    ('error', 'white', 'dark red', 'bold'),
+  ]
+ui = urwidm.raw_display.Screen()
+ui.set_mouse_tracking()
+def handleKeys(key):
+  if not isinstance(key, tuple): # only keyboard input
+    key = key.lower()
+    if key in ('q', 'f10'):
+      raise urwidm.ExitMainLoop()
+def focusGain(widget, context):
+  print "\nFocus Gain on help", context, widget
+  raw_input('')
+  return True
+def focusLost(widget, context):
+  print "\nFocus Lost on help", context, widget
+  raw_input('')
+  return True
+def connectFocus(widget, context):
+  urwidm.connect_signal(widget, 'focusgain', focusGain, context)
+  urwidm.connect_signal(widget, 'focuslost', focusLost, context)
+
+header = urwidm.Text('Title')
+footer = urwidm.Text('Q or F10 to quit')
+
+btn1 = urwidm.ButtonMore('btn1')
+connectFocus(btn1, 'btn1')
+
+btn2 = urwidm.ButtonMore('btn2')
+connectFocus(btn2, 'btn2')
+
+edit = urwidm.EditMore('edit')
+connectFocus(edit, 'edit')
+
+cb = urwidm.CheckBoxMore('checkbox')
+connectFocus(cb, 'checkbox')
+
+radioGrp = []
+radio1 = urwidm.RadioButtonMore(radioGrp, 'radio1')
+connectFocus(radio1, 'radio1')
+radio2 = urwidm.RadioButtonMore(radioGrp, 'radio2')
+connectFocus(radio2, 'radio2')
+cols = urwidm.ColumnsMore([radio1, radio2])
+connectFocus(cols, 'cols')
+
+txt1 = urwidm.TextMore('text1')
+btn3 = urwidm.ButtonMore('btn3')
+connectFocus(btn3, 'btn3')
+btn4 = urwidm.ButtonMore('btn4')
+connectFocus(btn4, 'btn4')
+gf = urwidm.GridFlowMore([txt1, btn3, btn4], 10, 2, 2, 'center')
+connectFocus(gf, 'gf')
+
+btn5 = urwidm.ButtonMore('btn5')
+connectFocus(btn5, 'btn5')
+btn6 = urwidm.ButtonMore('btn6')
+connectFocus(btn6, 'btn6')
+pile = urwidm.PileMore([btn5, btn6], btn6)
+connectFocus(pile, 'pile')
+cols2 = urwidm.ColumnsMore([urwidm.TextMore('text2'), pile])
+connectFocus(cols2, 'cols2')
+
+bodyList = [
+    cb,
+    cols2,
+    btn1,
+    cols,
+    btn2,
+    gf,
+  ]
+
+body = urwidm.ListBoxMore(urwidm.SimpleListWalker(bodyList))
+body.attr = 'body'
+frame = urwidm.FrameMore(body, header, footer, focus_part = 'body')
+frame.attr = 'body'
+mainView = frame
+loop = urwidm.MainLoop(mainView, palette, handle_mouse = True, unhandled_input = handleKeys, pop_ups = True)
+loop.run()
index 206e1fc..3ffaa9d 100644 (file)
@@ -1,6 +1,6 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# vim: set et ai sta sw=2 ts=2 tw=0:
+# vim: set et ai sw=2 sts=2 ts=2 tw=0:
 """
 More widgets for Urwid
 Based on the work on curses_misc.py in Wicd.
@@ -11,9 +11,28 @@ __copyright__ = 'Copyright 2013-2014, Salix OS, 2008-2009 Andrew Psaltis'
 __license__ = 'GPL2+'
 
 from urwid import *
+from urwid.signals import _signals as urwid_signals
+from urwid.canvas import apply_text_layout as urwid_apply_text_layout
+from urwid.util import is_mouse_press as urwid_is_mouse_press
+from urwid.util import is_mouse_event as urwid_is_mouse_event
 import gettext
 import re
 
+import traceback
+import time
+import pprint
+def st(instance = None):
+  loc = locals()
+  stack = traceback.format_stack()
+  with open('urwid.log', 'a') as f:
+    f.write('{0}: {1}'.format(time.asctime(), ''.join(stack)))
+    f.write('instance: {0}\n'.format(type(instance)))
+    f.write('locals: {0}\n---\n\n'.format(pprint.pformat(loc)))
+try:
+  _
+except NameError:
+  gettext.install('')
+
 class FocusEventWidget(Widget):
   signals = ['focusgain', 'focuslost'] # will be used by the metaclass of Widget to call register_signal
   _has_focus = False
@@ -24,60 +43,70 @@ class FocusEventWidget(Widget):
     return True
   def _can_loose_focus(self):
     return True
+  def get_focused_subwidget(self):
+    return None
+  def _can_gain_focus_rec(self):
+    ret = self._can_gain_focus()
+    subw = self.get_focused_subwidget()
+    if subw and isinstance(subw, FocusEventWidget):
+      ret &= subw._can_gain_focus_rec()
+    return ret
+  def _can_loose_focus_rec(self):
+    ret = self._can_loose_focus()
+    subw = self.get_focused_subwidget()
+    if subw and isinstance(subw, FocusEventWidget):
+      ret &= subw._can_loose_focus_rec()
+    return ret
   def _emit_focus_event(self, name, *args):
     """
     Return True if there is no callback, or if all callback answer True
     """
     result = True
-    signal_obj = signals._signals
+    signal_obj = urwid_signals
     d = getattr(self, signal_obj._signal_attr, {})
     for callback, user_arg in d.get(name, []):
-      args_copy = (self,) + args
+      args_copy = [self]
+      args_copy.extend(args)
       if user_arg is not None:
-        args_copy = args_copy + (user_arg,)
+        args_copy.append(user_arg)
       result &= bool(callback(*args_copy))
     return result
-  def emit_focusgain(self):
+  def _emit_focusgain(self):
     """
     Return True if there is no callback, or if all callback answer True
     """
     return self._emit_focus_event('focusgain')
-  def emit_focuslost(self):
+  def _emit_focuslost(self):
     """
     Return True if there is no callback, or if all callback answer True
     """
     return self._emit_focus_event('focuslost')
-  def gain_focus(self):
-    ret = self._can_gain_focus()
-    if ret:
-      ret = self.emit_focusgain()
-    if ret:
-      self._has_focus = True
+  def _emit_focusgain_rec(self):
+    ret = self._emit_focusgain()
+    subw = self.get_focused_subwidget()
+    if subw and isinstance(subw, FocusEventWidget):
+      ret &= subw._emit_focusgain_rec()
     return ret
-  def loose_focus(self):
-    ret = self._can_loose_focus()
-    if ret:
-      ret = self.emit_focuslost()
-    if ret:
-      self._has_focus = False
+  def _emit_focuslost_rec(self):
+    ret = True
+    subw = self.get_focused_subwidget()
+    if subw and isinstance(subw, FocusEventWidget):
+      ret = subw._emit_focuslost_rec()
+    ret &= self._emit_focuslost()
     return ret
-  def _gain_focus_with_subwidget(self, subwidget):
-    ret = self._can_gain_focus()
+  def gain_focus(self):
+    st(self)
+    ret = self._can_gain_focus_rec()
     if ret:
-      if subwidget and isinstance(subwidget, FocusEventWidget):
-        ret = subwidget.gain_focus()
-      else:
-        ret = self.emit_focusgain()
+      ret = self._emit_focusgain_rec()
     if ret:
       self._has_focus = True
     return ret
-  def _loose_focus_with_subwidget(self, subwidget):
+  def loose_focus(self):
+    st(self)
     ret = self._can_loose_focus()
     if ret:
-      if subwidget and isinstance(subwidget, FocusEventWidget):
-        ret = subwidget.loose_focus()
-      else:
-        ret = self.emit_focuslost()
+      ret = self._emit_focuslost_rec()
     if ret:
       self._has_focus = False
     return ret
@@ -258,10 +287,8 @@ class WidgetWrapMore(More, WidgetWrap):
       return self._w._can_loose_focus()
     else:
       return FocusEventWidget._can_loose_focus(self)
-  def gain_focus(self):
-    return self._gain_focus_with_subwidget(self._w)
-  def loose_focus(self):
-    return self._loose_focus_with_subwidget(self._w)
+  def get_focused_subwidget(self):
+    return self._w
 
 class WidgetDecorationMore(More, WidgetDecoration):
   def __init__(self, original_widget):
@@ -284,10 +311,8 @@ class WidgetDecorationMore(More, WidgetDecoration):
       return self._original_widget._can_loose_focus()
     else:
       return FocusEventWidget._can_loose_focus(self)
-  def gain_focus(self):
-    return self._gain_focus_with_subwidget(self._original_widget)
-  def loose_focus(self):
-    return self._loose_focus_with_subwidget(self._original_widget)
+  def get_focused_subwidget(self):
+    return self._original_widget
 
 class WidgetPlaceholderMore(WidgetDecorationMore, WidgetPlaceholder):
   _default_sensitive_attr = 'body'
@@ -353,6 +378,7 @@ class FrameMore(More, Frame):
     More.__init__(self)
     self._selectable = True
     Frame.__init__(self, body, header, footer, focus_part)
+    self.set_focus('body')
   def render(self, size, focus = False):
     """Render frame and return it."""
     (maxcol, maxrow) = size
@@ -410,10 +436,8 @@ class FrameMore(More, Frame):
           ok = focus_w.gain_focus()
     if ok:
       Frame.set_focus(self, part)
-  def gain_focus(self):
-    return self._gain_focus_with_subwidget(self._get_focus_widget(self.get_focus()))
-  def loose_focus(self):
-    return self._loose_focus_with_subwidget(self._get_focus_widget(self.get_focus()))
+  def get_focused_subwidget(self):
+    return self._get_focus_widget(self.get_focus())
 
 class PileMore(More, Pile):
   _default_sensitive_attr = 'body'
@@ -476,7 +500,7 @@ class PileMore(More, Pile):
     ok = True
     if not hasattr(self, "focus_item"):
       Pile.set_focus(self, item)
-    if self.has_focus:
+    if self.focus_item:
       focus_w = self.get_focus()
       if type(item) == int:
         new_focus_w = self.widget_list[item]
@@ -490,10 +514,17 @@ class PileMore(More, Pile):
             ok = new_focus_w.gain_focus()
     if ok:
       Pile.set_focus(self, item)
-  def gain_focus(self):
-    return self._gain_focus_with_subwidget(self.get_focus())
-  def loose_focus(self):
-    return self._loose_focus_with_subwidget(self.get_focus())
+  def get_focused_subwidget(self):
+    return self.get_focus()
+  def get_focus_pos(self):
+    """
+    Return the 1-based position of the select item in focus or 0 if none.
+    """
+    fw = self.get_focus()
+    pos = 0
+    if fw:
+      pos = self.widget_list.index(fw)
+    return pos
   def render(self, size, focus = False):
     return self.canvas_with_attr(self.__super.render(size, focus), focus)
 
@@ -505,18 +536,49 @@ class ColumnsMore(More, Columns):
     Columns.__init__(self, widget_list, dividechars, focus_column, min_width, box_columns)
   def selectable(self):
     return Columns.selectable(self) and self.sensitive
+  def keypress(self, size, key):
+    ret = None
+    if self.focus_col is None:
+      ret = key
+    else:
+      widths = self.column_widths(size)
+      if self.focus_col < 0 or self.focus_col >= len(widths):
+        ret = key
+      else:
+        i = self.focus_col
+        mc = widths[i]
+        w = self.widget_list[i]
+        if self._command_map[key] not in ('cursor up', 'cursor down', 'cursor page up', 'cursor page down'):
+          self.pref_col = None
+        key = w.keypress((mc,) + size[1:], key)
+        if self._command_map[key] not in ('cursor left', 'cursor right'):
+          ret = key
+        else:
+          if self._command_map[key] == 'cursor left':
+            candidates = range(i-1, -1, -1) # count backwards to 0
+          else: # key == 'right'
+            candidates = range(i+1, len(widths))
+          for j in candidates:
+            if not self.widget_list[j].selectable():
+              continue
+            self.set_focus_column(j)
+            #ret = None
+            ret = key
+            break
+          else:
+            ret = key
+    return ret
   def set_focus_column(self, num):
     """Set the column in focus by its index in self.widget_list."""
     ok = True
-    if self.has_focus:
-      if self.get_focus_column():
-        focus_w = self.get_focus()
-        if isinstance(focus_w, FocusEventWidget):
-          ok = focus_w.loose_focus()
-      if ok:
-        focus_w = self.widget_list[num]
-        if isinstance(focus_w, FocusEventWidget):
-          ok = focus_w.gain_focus()
+    if self.get_focus_column():
+      focus_w = self.get_focus()
+      if isinstance(focus_w, FocusEventWidget):
+        ok = focus_w.loose_focus()
+    if ok:
+      focus_w = self.widget_list[num]
+      if isinstance(focus_w, FocusEventWidget):
+        ok = focus_w.gain_focus()
     if ok:
       Columns.set_focus_column(self, num)
   def set_focus(self, item):
@@ -540,6 +602,8 @@ class ColumnsMore(More, Columns):
       self.focus_col = position
       self._invalidate()
     return ok
+  def get_focused_subwidget(self):
+    return self.get_focus()
   def mouse_event(self, size, event, button, col, row, focus):
     """
     Send event to appropriate column.
@@ -557,17 +621,13 @@ class ColumnsMore(More, Columns):
         continue
       focus = focus and self.focus_col == i
       ok = True
-      if util.is_mouse_press(event) and button == 1:
+      if urwid_is_mouse_press(event) and button == 1:
         if w.selectable():
           ok = self.set_focus(w)
       if not ok or not hasattr(w,'mouse_event'):
         return False
       return w.mouse_event((end-x,)+size[1:], event, button, col - x, row, focus)
     return False
-  def gain_focus(self):
-    return self._gain_focus_with_subwidget(self.get_focus())
-  def loose_focus(self):
-    return self._loose_focus_with_subwidget(self.get_focus())
   def render(self, size, focus = False):
     return self.canvas_with_attr(self.__super.render(size, focus), focus)
 
@@ -580,6 +640,10 @@ class GridFlowMore(More, GridFlow):
   def __init__(self, cells, cell_width, h_sep, v_sep, align):
     More.__init__(self)
     GridFlow.__init__(self, cells, cell_width, h_sep, v_sep, align)
+    for cell in cells:
+      if cell.selectable():
+        self.focus_cell = cell
+        break
   def selectable(self):
     return GridFlow.selectable(self) and self.sensitive
   def generate_display_widget(self, size):
@@ -631,30 +695,27 @@ class GridFlowMore(More, GridFlow):
       row = self._padding_widget_class(cols, self.align, rwidth)
       out.append(row)
       s += bpr
-    return self._pile_widget_class(out, f)    
+    return self._pile_widget_class(out, f)
   def set_focus(self, cell):
     """
-    Set the cell in focus.  
+    Set the cell in focus.
     cell -- widget or integer index into self.cells
     """
     ok = True
     if self.has_focus:
       focus_w = self.get_focus()
-      if focus_w and isinstance(focus_w, FocusEventWidget):
+      if type(cell) == int:
+        focus_w_next = self.cells[cell]
+      else:
+        focus_w_next = cell
+      if focus_w and focus_w != focus_w_next and isinstance(focus_w, FocusEventWidget):
         ok = focus_w.loose_focus()
-      if ok:
-        if type(cell) == int:
-          focus_w = self.cells[cell]
-        else:
-          focus_w = cell
-        if isinstance(focus_w, FocusEventWidget):
-          ok = focus_w.gain_focus()
+        if ok and isinstance(focus_w_next, FocusEventWidget):
+          ok = focus_w_next.gain_focus()
     if ok:
       GridFlow.set_focus(self, cell)
-  def gain_focus(self):
-    return self._gain_focus_with_subwidget(self.get_focus())
-  def loose_focus(self):
-    return self._loose_focus_with_subwidget(self.get_focus())
+  def get_focused_subwidget(self):
+    return self.get_focus()
   def render(self, size, focus = False):
     return self.canvas_with_attr(self.__super.render(size, focus), focus)
 
@@ -666,10 +727,8 @@ class OverlayMore(More, Overlay):
     Overlay.__init__(self, top_w, bottom_w, align, width, valign, height, min_width, min_height)
   def selectable(self):
     return Overlay.selectable(self) and self.sensitive
-  def gain_focus(self):
-    return self._gain_focus_with_subwidget(self.top_w)
-  def loose_focus(self):
-    return self._loose_focus_with_subwidget(self.top_w)
+  def get_focused_subwidget(self):
+    return self.top_w
   def render(self, size, focus = False):
     return self.canvas_with_attr(self.__super.render(size, focus), focus)
 
@@ -693,6 +752,8 @@ class ListBoxMore(More, ListBox):
     if ok:
       ListBox.change_focus(self, size, position, offset_inset, coming_from, cursor_coords, snap_rows)
     return ok
+  def get_focused_subwidget(self):
+    return self.get_focus()[0]
   def mouse_event(self, size, event, button, col, row, focus):
     """
     Pass the event to the contained widgets.
@@ -715,18 +776,24 @@ class ListBoxMore(More, ListBox):
     else:
       return False
     focus = focus and w == focus_widget
-    ok = True
-    if util.is_mouse_press(event) and button == 1:
+    ret = False
+    if urwid_is_mouse_press(event) and button == 1:
       if w.selectable():
-        ok = self.change_focus((maxcol,maxrow), w_pos, wrow)
-    if not ok or not hasattr(w,'mouse_event'):
+        ret = self.change_focus((maxcol, maxrow), w_pos, wrow)
+      else:
+        ret = True
+    if ret and hasattr(w, 'mouse_event'):
+      return w.mouse_event((maxcol,), event, button, col, row - wrow, focus)
+    else:
       return False
-    return w.mouse_event((maxcol,), event, button, col, row-wrow, focus)
-  def gain_focus(self):
-    return self._gain_focus_with_subwidget(self.get_focus()[0])
-  def loose_focus(self):
-    return self._loose_focus_with_subwidget(self.get_focus()[0])
   def render(self, size, focus = False):
+    # hack to trigger a focus_gain on the first selectable widget
+    if self.set_focus_pending == 'first selectable':
+      for i, w in enumerate(self.body):
+        if w.selectable():
+          self.change_focus(size, i)
+          break
+    # render with attribute wrapping
     return self.canvas_with_attr(self.__super.render(size, focus), focus)
 
 class LineBoxMore(WidgetDecorationMore, LineBox):
@@ -858,7 +925,7 @@ class ComboBox(PopUpLauncherMore):
         return self.__super.keypress(size, key)
     def mouse_event(self, size, event, button, col, row, focus):
       ret = self.__super.mouse_event(size, event, button, col, row, focus)
-      if util.is_mouse_press(event) and button == 1 and col > 1 and col < size[0] - 1 and row < size[1] - 1:
+      if urwid_is_mouse_press(event) and button == 1 and col > 1 and col < size[0] - 1 and row < size[1] - 1:
         self.validate()
       return ret
     def close(self):
@@ -892,7 +959,7 @@ class ComboBox(PopUpLauncherMore):
   _default_unsensitive_attr = ('body', '')
   DOWN_ARROW = "↓"
   signals = ['displaycombo', 'change']
-  
+
   def __init__(self, label = '', items = None, use_enter = True, focus_index = 0):
     """
     label       : bit of text that preceeds the combobox.  If it is "", then ignore it
@@ -1001,7 +1068,7 @@ class ComboBox(PopUpLauncherMore):
     maxtxt = len(self.cbox.text) + 1
     if self.label.text:
       maxtxt += len(self.label.text) + 1
-    if util.is_mouse_press(event) and button == 1 and col > maxtxt and col <= maxtxt + len(self.DOWN_ARROW):
+    if urwid_is_mouse_press(event) and button == 1 and col > maxtxt and col <= maxtxt + len(self.DOWN_ARROW):
       self._emit("displaycombo")
   def displaycombo(self, src):
     self.open_pop_up()
@@ -1028,7 +1095,7 @@ class ComboBox(PopUpLauncherMore):
     Return True if there is no callback, or if all callback answer True
     """
     result = True
-    signal_obj = signals._signals
+    signal_obj = urwid_signals
     d = getattr(self, signal_obj._signal_attr, {})
     for callback, user_arg in d.get('change', []):
       args = (self, pos, text)
@@ -1098,7 +1165,7 @@ class TextMultiValues(SelText):
     text = self._fullText
     attr = self.get_text()[1]
     trans = self.get_line_translation(maxcol, (text, attr))
-    return self.canvas_with_attr(canvas.apply_text_layout(text, attr, trans, maxcol), focus)
+    return self.canvas_with_attr(urwid_apply_text_layout(text, attr, trans, maxcol), focus)
   def _update_cache_translation(self, maxcol, ta):
     if ta:
       text, attr = ta
@@ -1195,7 +1262,7 @@ class Dialog2(WidgetWrapMore):
         while not keys:
           keys = ui.get_input()
         for k in keys:
-          if util.is_mouse_event(k):
+          if urwid_is_mouse_event(k):
             event, button, col, row = k
             overlay.mouse_event(size, event, button, col, row, focus = True)
           else:
@@ -1227,6 +1294,7 @@ class TextDialog(Dialog2):
       self.add_buttons(buttons)
     else:
       self.add_buttons([buttons])
+    self.frame.set_focus('footer')
   def unhandled_key(self, size, k):
     """ Handle keys. """
     if k in ('up', 'page up', 'down', 'page down'):
index 91d7f5c..451daa7 100755 (executable)
@@ -40,7 +40,7 @@ the Free Software Foundation, Inc., 51 Franklin Street,
 Fifth Floor, Boston, MA 02110-1301, USA.</property>
     <property name="authors">Cyrille Pontvieux &lt;jrd~at~enialis~dot~net&gt;
 Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
-    <property name="documenters"></property>
+    <property name="documenters"/>
     <property name="translator_credits" translatable="yes" comments="Define your name and email here.">translator_name &lt;translator@email.com&gt;</property>
     <property name="logo">bootsetup.png</property>
     <signal name="delete-event" handler="on_about_dialog_close" swapped="no"/>
@@ -209,7 +209,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">False</property>
-                <property name="use_action_appearance">False</property>
                 <property name="relief">none</property>
                 <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
                 <signal name="enter-notify-event" handler="on_about_button_enter_notify_event" swapped="no"/>
@@ -266,7 +265,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
             <property name="visible">True</property>
             <property name="can_focus">False</property>
             <signal name="enter-notify-event" handler="on_bootloader_type_enter_notify_event" swapped="no"/>
-            <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
             <child>
               <object class="GtkHButtonBox" id="hbuttonbox_type">
                 <property name="visible">True</property>
@@ -278,7 +276,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
                     <property name="receives_default">False</property>
-                    <property name="use_action_appearance">False</property>
                     <property name="draw_indicator">True</property>
                     <property name="group">radiobutton_lilo</property>
                   </object>
@@ -294,10 +291,9 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
                     <property name="receives_default">False</property>
-                    <property name="use_action_appearance">False</property>
                     <property name="active">True</property>
                     <property name="draw_indicator">True</property>
-                    <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
+                    <signal name="enter-notify-event" handler="on_bootloader_type_enter_notify_event" swapped="no"/>
                     <signal name="clicked" handler="on_bootloader_type_clicked" swapped="no"/>
                   </object>
                   <packing>
@@ -312,10 +308,8 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                     <property name="visible">True</property>
                     <property name="can_focus">True</property>
                     <property name="receives_default">False</property>
-                    <property name="use_action_appearance">False</property>
                     <property name="draw_indicator">True</property>
                     <property name="group">radiobutton_lilo</property>
-                    <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
                     <signal name="clicked" handler="on_bootloader_type_clicked" swapped="no"/>
                   </object>
                   <packing>
@@ -499,7 +493,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                             <property name="sensitive">False</property>
                             <property name="can_focus">True</property>
                             <property name="receives_default">True</property>
-                            <property name="use_action_appearance">False</property>
                             <signal name="enter-notify-event" handler="on_up_button_enter_notify_event" swapped="no"/>
                             <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
                             <signal name="clicked" handler="on_up_button_clicked" swapped="no"/>
@@ -523,7 +516,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                             <property name="sensitive">False</property>
                             <property name="can_focus">True</property>
                             <property name="receives_default">True</property>
-                            <property name="use_action_appearance">False</property>
                             <signal name="enter-notify-event" handler="on_down_button_enter_notify_event" swapped="no"/>
                             <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
                             <signal name="clicked" handler="on_down_button_clicked" swapped="no"/>
@@ -565,7 +557,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                         <property name="sensitive">False</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">True</property>
-                        <property name="use_action_appearance">False</property>
                         <signal name="enter-notify-event" handler="on_lilo_undo_button_enter_notify_event" swapped="no"/>
                         <signal name="clicked" handler="on_lilo_undo_button_clicked" swapped="no"/>
                         <child>
@@ -613,7 +604,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                         <property name="sensitive">False</property>
                         <property name="can_focus">True</property>
                         <property name="receives_default">True</property>
-                        <property name="use_action_appearance">False</property>
                         <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
                         <signal name="enter-notify-event" handler="on_lilo_edit_button_enter_notify_event" swapped="no"/>
                         <signal name="clicked" handler="on_lilo_edit_button_clicked" swapped="no"/>
@@ -747,7 +737,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                     <property name="sensitive">False</property>
                     <property name="can_focus">True</property>
                     <property name="receives_default">True</property>
-                    <property name="use_action_appearance">False</property>
                     <signal name="enter-notify-event" handler="on_grub2_edit_button_enter_notify_event" swapped="no"/>
                     <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
                     <signal name="clicked" handler="on_grub2_edit_button_clicked" swapped="no"/>
@@ -829,7 +818,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                 <property name="has_focus">True</property>
                 <property name="can_default">True</property>
                 <property name="receives_default">True</property>
-                <property name="use_action_appearance">False</property>
                 <property name="use_stock">True</property>
                 <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
                 <signal name="enter-notify-event" handler="on_button_quit_enter_notify_event" swapped="no"/>
@@ -847,7 +835,6 @@ Pierrick Le Brun &lt;akuna~at~salixos~dot~org&gt;</property>
                 <property name="sensitive">False</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
-                <property name="use_action_appearance">False</property>
                 <signal name="leave-notify-event" handler="on_leave_notify_event" swapped="no"/>
                 <signal name="enter-notify-event" handler="on_execute_button_enter_notify_event" swapped="no"/>
                 <signal name="clicked" handler="on_execute_button_clicked" swapped="no"/>