Ian Ward wrote:
I think that's a great idea. Florian Festi mentioned a similar discomfort with the way Columns and Piles align widgets, but suggested adding alignment attributes to the widgets as a solution. I like your approach better because it lends itself more to Urwid's style of widget-reuse and small, simple widget classes.

I look forward to your patch :-)
Hi there,

I've been experimenting with the best way to implement this approach, and the attached patch is what I eventually came up with. I've created a "hint" module that contains a set of decorator classes that hints to containing classes how the widget might best be displayed. A quick example:

import urwid
import urwid.hint as hint

...

pile = urwid.Pile([
    hint.Fixed(widget1, size = 5),
    hint.Flow(widget2),
    widget3,
    hint.Weight(widget4, 2.0)
])

I've adapted the Columns and Pile widgets to work with the layout hint wrappers. The code is essentially mostly unchanged; I just replaced all of the tuple-handling code with isinstance checks, etc. I also updated the unit tests in test_urwid.py.

I'm undecided on the syntax, but it seems reasonable.

What are your thoughts?

--
James Reeves



Index: test_urwid.py
===================================================================
--- test_urwid.py	(revision 137)
+++ test_urwid.py	(working copy)
@@ -23,6 +23,7 @@
 from __future__ import nested_scopes
 
 import urwid
+import urwid.hint as hint
 
 import unittest
 from test import test_support
@@ -1914,21 +1915,21 @@
 		self.cwtest( "squish 4+1", [x,x,x,x], 1, (6,), [1,2,1] )
 		self.cwtest( "squish 4+1", [x,x,x,x], 1, (4,), [2,1] )
 		
-		self.cwtest( "fixed 3", [('fixed',4,x),('fixed',6,x),
-			('fixed',2,x)], 1, (25,), [4,6,2] )
-		self.cwtest( "fixed 3 cut", [('fixed',4,x),('fixed',6,x),
-			('fixed',2,x)], 1, (13,), [4,6] )
-		self.cwtest( "fixed 3 cut2", [('fixed',4,x),('fixed',6,x),
-			('fixed',2,x)], 1, (10,), [4] )
+		self.cwtest( "fixed 3", [hint.Fixed(x,4),hint.Fixed(x,6),
+			urwid.hint.Fixed(x,2)], 1, (25,), [4,6,2] )
+		self.cwtest( "fixed 3 cut", [hint.Fixed(x,4),hint.Fixed(x,6),
+			urwid.hint.Fixed(x,2)], 1, (13,), [4,6] )
+		self.cwtest( "fixed 3 cut2", [hint.Fixed(x,4),hint.Fixed(x,6),
+			urwid.hint.Fixed(x,2)], 1, (10,), [4] )
 		
-		self.cwtest( "mixed 4", [('weight',2,x),('fixed',5,x),
-			x, ('weight',3,x)], 1, (14,), [2,5,1,3] )
-		self.cwtest( "mixed 4 a", [('weight',2,x),('fixed',5,x),
-			x, ('weight',3,x)], 1, (12,), [1,5,1,2] )
-		self.cwtest( "mixed 4 b", [('weight',2,x),('fixed',5,x),
-			x, ('weight',3,x)], 1, (10,), [2,5,1] )
-		self.cwtest( "mixed 4 c", [('weight',2,x),('fixed',5,x),
-			x, ('weight',3,x)], 1, (20,), [4,5,2,6] )
+		self.cwtest( "mixed 4", [hint.Weight(x,2),hint.Fixed(x,5),
+			x, urwid.hint.Weight(x,3)], 1, (14,), [2,5,1,3] )
+		self.cwtest( "mixed 4 a", [hint.Weight(x,2),hint.Fixed(x,5),
+			x, urwid.hint.Weight(x,3)], 1, (12,), [1,5,1,2] )
+		self.cwtest( "mixed 4 b", [hint.Weight(x,2),hint.Fixed(x,5),
+			x, urwid.hint.Weight(x,3)], 1, (10,), [2,5,1] )
+		self.cwtest( "mixed 4 c", [hint.Weight(x,2),hint.Fixed(x,5),
+			x, urwid.hint.Weight(x,3)], 1, (20,), [4,5,2,6] )
 	
 	def mctest(self, desc, l, divide, size, col, row, exp, f_col, pref_col):
 		c = urwid.Columns( l, divide )
Index: urwid/__init__.py
===================================================================
--- urwid/__init__.py	(revision 137)
+++ urwid/__init__.py	(working copy)
@@ -42,3 +42,4 @@
 from graphics import *
 from canvas import *
 from font import *
+import hint
Index: urwid/hint.py
===================================================================
--- urwid/hint.py	(revision 0)
+++ urwid/hint.py	(revision 0)
@@ -0,0 +1,25 @@
+class LayoutHint:
+	"""Decorator that gives Pile and Columns widgets a hint at how the contained
+	widgets should be arranged."""
+
+	def __init__(self, widget):
+		self.w = widget
+
+	def __getattr__(self, name):
+		"""Call getattr on wrapped widget."""
+		return getattr(self.w, name)
+
+class Flow(LayoutHint):
+	"""Hints that the contained widget should be treated as a flow widget."""
+
+class Weight(LayoutHint):
+	"""Uses a weighting value to hint at the contained widget's size."""
+	def __init__(self, widget, weight):
+		LayoutHint.__init__(self, widget)
+		self.weight = weight
+
+class Fixed(LayoutHint):
+	"""Hints that the contained widget should be a fixed size."""
+	def __init__(self, widget, size):
+		LayoutHint.__init__(self, widget)
+		self.size = size
Index: urwid/widget.py
===================================================================
--- urwid/widget.py	(revision 137)
+++ urwid/widget.py	(working copy)
@@ -23,6 +23,7 @@
 
 from util import *
 from canvas import *
+import hint
 
 try: sum # old python?
 except: sum = lambda l: reduce(lambda a,b: a+b, l, 0)
@@ -700,13 +701,13 @@
 
 	def keypress(self,(maxcol,),key):
 		"""Handle editing keystrokes.  Return others."""
-	        if key in list("0123456789"):
+		if key in list("0123456789"):
 			# trim leading zeros
-	                while self.edit_pos > 0 and self.edit_text[:1] == "0":
-	                        self.set_edit_pos( self.edit_pos - 1)
-	                        self.set_edit_text(self.edit_text[1:])
+			while self.edit_pos > 0 and self.edit_text[:1] == "0":
+				self.set_edit_pos( self.edit_pos - 1)
+				self.set_edit_text(self.edit_text[1:])
 
-	        unhandled = Edit.keypress(self,(maxcol,),key)
+		unhandled = Edit.keypress(self,(maxcol,),key)
 
 		return unhandled
 
@@ -2115,50 +2116,38 @@
 	pass
 		
 class Pile(Widget): # either FlowWidget or BoxWidget
-	def __init__(self, widget_list, focus_item=None):
+	def __init__(self, widget_list, focus_item = None):
 		"""
 		widget_list -- list of widgets
 		focus_item -- widget or integer index, if None the first
 			selectable widget will be chosen.
 
-		widget_list may also contain tuples such as:
-		('flow', widget) always treat widget as a flow widget
-		('fixed', height, widget) give this box widget a fixed height
-		('weight', weight, widget) if the pile is treated as a box
+		widget objects in widget_list may be wrapped in a LayoutHint:
+		hint.Flow(widget) always treat widget as a flow widget
+		hint.Fixed(widget, size) give this box widget a fixed height
+		hint.Weight(widget, weight) if the pile is treated as a box
 			widget then treat widget as a box widget with a
 			height based on its relative weight value, otherwise
 			treat widget as a flow widget
 		
-		widgets not in a tuple are the same as ('weight', 1, widget)
+		widgets not in a tuple are the same as hint.Weight(widget, 1)
 
 		If the pile is treated as a box widget there must be at least
 		one 'weight' tuple in widget_list.
 		"""
 		self.__super.__init__()
+
 		self.widget_list = MonitoredList(widget_list)
-		self.item_types = []
-		for i in range(len(widget_list)):
-			w = widget_list[i]
-			if type(w) != type(()):
-				self.item_types.append(('weight',1))
-			elif w[0] == 'flow':
-				f, widget = w
-				self.widget_list[i] = widget
-				self.item_types.append((f,None))
-				w = widget
-			elif w[0] in ('fixed', 'weight'):
-				f, height, widget = w
-				self.widget_list[i] = widget
-				self.item_types.append((f,height))
-				w = widget
-			else:
-				raise PileError, "widget list item invalid %s" % `w`
-			if focus_item is None and w.selectable():
-				focus_item = i
 		self.widget_list.set_modified_callback(self._invalidate)
-		
+
 		if focus_item is None:
-			focus_item = 0
+			for w in self.widget_list:
+				if w.selectable():
+					focus_item = w
+					break
+		if focus_item is None:
+			focus_item = self.widget_list[0]
+
 		self.set_focus(focus_item)
 		self.pref_col = None
 
@@ -2194,15 +2183,16 @@
 		Return a size appropriate for passing to self.widget_list[i]
 		"""
 		maxcol = size[0]
-		f, height = self.item_types[i]
-		if f=='fixed':
-			return (maxcol, height)
-		elif f=='weight' and len(size)==2:
+		w = self.widget_list[i]
+		if isinstance(w, hint.Fixed):
+			return (maxcol, w.size)
+		elif isinstance(w, hint.Flow) or len(size) == 1:
+			return (maxcol,)
+		else:
 			if not item_rows:
 				item_rows = self.get_item_rows(size, focus)
 			return (maxcol, item_rows[i])
-		else:
-			return (maxcol,)
+
 					
 	def get_item_rows(self, size, focus):
 		"""
@@ -2218,10 +2208,9 @@
 		
 		if remaining is None:
 			# pile is a flow widget
-			for (f, height), w in zip(
-				self.item_types, self.widget_list):
-				if f == 'fixed':
-					l.append( height )
+			for w in self.widget_list:
+				if isinstance(w, hint.Fixed):
+					l.append( w.size )
 				else:
 					l.append( w.rows( (maxcol,), focus=focus
 						and self.focus_item == w ))
@@ -2230,18 +2219,21 @@
 		# pile is a box widget
 		# do an extra pass to calculate rows for each widget
 		wtotal = 0
-		for (f, height), w in zip(self.item_types, self.widget_list):
-			if f == 'flow':
+		for w in self.widget_list:
+			if isinstance(w, hint.Flow):
 				rows = w.rows((maxcol,), focus=focus and
 					self.focus_item == w )
 				l.append(rows)
 				remaining -= rows
-			elif f == 'fixed':
-				l.append(height)
-				remaining -= height
+			elif isinstance(w, hint.Fixed):
+				l.append(w.size)
+				remaining -= w.size
+			elif isinstance(w, hint.Weight):
+				l.append(None)
+				wtotal += w.weight
 			else:
 				l.append(None)
-				wtotal += height
+				wtotal += 1.0
 
 		if wtotal == 0:
 			raise PileError, "No weighted widgets found for Pile treated as a box widget"
@@ -2249,15 +2241,15 @@
 		if remaining < 0: 
 			remaining = 0
 
-		i = 0
-		for (f, height), li in zip(self.item_types, l):
-			if li is None:
-				rows = int(float(remaining)*height
-					/wtotal+0.5)
+		for i, w in enumerate(self.widget_list):
+			if l[i] is None:
+				weight = 1
+				if isinstance(w, hint.Weight):
+					weight = w.weight			
+				rows = int(float(remaining) * weight / wtotal + 0.5)
 				l[i] = rows
 				remaining -= rows
-				wtotal -= height
-			i += 1
+				wtotal -= weight
 		return l
 		
 	
@@ -2270,14 +2262,13 @@
 		item_rows = None
 		
 		combinelist = []
-		i = 0
-		for (f, height), w in zip(self.item_types, self.widget_list):
+		for i, w in enumerate(self.widget_list):
 			item_focus = self.focus_item == w
 			canv = None
-			if f == 'fixed':
-				canv = w.render( (maxcol, height),
+			if isinstance(w, hint.Fixed):
+				canv = w.render( (maxcol, w.size),
 					focus=focus and item_focus)
-			elif f == 'flow' or len(size)==1:
+			elif isinstance(w, hint.Flow) or len(size) == 1:
 				canv = w.render( (maxcol,), 
 					focus=focus and	item_focus)
 			else:	
@@ -2290,7 +2281,6 @@
 						focus=focus and	item_focus )
 			if canv:
 				combinelist.append((canv, i, item_focus))
-			i+=1
 
 		return CanvasCombine(combinelist)
 	
@@ -2301,12 +2291,13 @@
 		if not hasattr(self.focus_item,'get_cursor_coords'):
 			return None
 		
-		i = self.widget_list.index(self.focus_item)
-		f, height = self.item_types[i]
+		w = self.focus_item
+		i = self.widget_list.index(w)
 		item_rows = None
 		maxcol = size[0]
-		if f == 'fixed' or (f=='weight' and len(size)==2):
-			if f == 'fixed':
+		if isinstance(w, hint.Fixed) or (len(size) == 2 and
+				not isinstance(w, hint.Flow)):
+			if isinstance(w, hint.Fixed):
 				maxrow = height
 			else:
 				if item_rows is None:
@@ -2344,7 +2335,7 @@
 			item_rows = self.get_item_rows( size, focus=True )
 
 		i = self.widget_list.index(self.focus_item)
-		f, height = self.item_types[i]
+
 		if self.focus_item.selectable():
 			tsize = self.get_item_size(size,i,True,item_rows)
 			key = self.focus_item.keypress( tsize, key )
@@ -2369,7 +2360,6 @@
 			if not hasattr(self.focus_item,'move_cursor_to_coords'):
 				return
 
-			f, height = self.item_types[j]
 			rows = item_rows[j]
 			if key=='up':
 				rowlist = range(rows-1, -1, -1)
@@ -2469,11 +2459,11 @@
 			whose maxrow is set to the maximum of the rows 
 			required by columns not listed in box_columns.
 
-		widget_list may also contain tuples such as:
-		('fixed', width, widget) give this column a fixed width
-		('weight', weight, widget) give this column a relative weight
+		widget objects in widget_list may be wrapped in a LayoutHint:
+		hint.Fixed(widget, size) give this box widget a fixed height
+		hint.Weight(widget, weight) give this column a relative weight
 
-		widgets not in a tuple are the same as ('weight', 1, widget)	
+		widgets not in a tuple are the same as hint.Weight(widget, 1)
 
 		box_columns is ignored when this widget is being used as a
 		box widget because in that case all columns are treated as box
@@ -2481,27 +2471,18 @@
 		"""
 		self.__super.__init__()
 		self.widget_list = MonitoredList(widget_list)
-		self.column_types = []
-		for i in range(len(widget_list)):
-			w = widget_list[i]
-			if type(w) != type(()):
-				self.column_types.append(('weight',1))
-			elif w[0] in ('fixed', 'weight'):
-				f,width,widget = w
-				self.widget_list[i] = widget
-				self.column_types.append((f,width))
-				w = widget
-			else:
-				raise ColumnsError, "widget list item invalid: %s" % `w`
-			if focus_column is None and w.selectable():
-				focus_column = i
-				
 		self.widget_list.set_modified_callback(self._invalidate)
-		
-		self.dividechars = dividechars
+
 		if focus_column is None:
+			for i, w in enumerate(self.widget_list):
+				if w.selectable():
+					focus_column = i
+					break
+		if focus_column is None:
 			focus_column = 0
-		self.focus_col = focus_column
+
+		self.focus_col = focus_column 	
+		self.dividechars = dividechars
 		self.pref_col = None
 		self.min_width = min_width
 		self.box_columns = box_columns
@@ -2545,24 +2526,15 @@
 		maxcol = size[0]
 		if maxcol == self._cache_maxcol:
 			return self._cache_column_widths
-
-		col_types = self.column_types
-		# hack to support old practice of editing self.widget_list
-		# directly
-		lwl, lct = len(self.widget_list), len(self.column_types)
-		if lwl > lct:
-			col_types = col_types + [('weight',1)] * (lwl-lct)
-			
-		widths=[]
-		
+	
+		widths = []
 		weighted = []
 		shared = maxcol + self.dividechars
 		growable = 0
 		
-		i = 0
-		for t, width in col_types:
-			if t == 'fixed':
-				static_w = width
+		for i, w in enumerate(self.widget_list):
+			if isinstance(w, hint.Fixed):
+				static_w = w.size
 			else:
 				static_w = self.min_width
 				
@@ -2571,11 +2543,11 @@
 		
 			widths.append( static_w )	
 			shared -= static_w + self.dividechars
-			if t != 'fixed':
-				weighted.append( (width,i) )
+			if isinstance(w, hint.Weight):
+				weighted.append( (w.weight, i) )
+			elif not isinstance(w, hint.Fixed):
+				weighted.append( (1, i) )
 		
-			i += 1
-		
 		if shared:
 			# divide up the remaining space between weighted cols
 			weighted.sort()
_______________________________________________
Urwid mailing list
[email protected]
http://lists.excess.org/mailman/listinfo/urwid

Reply via email to